달력

4

« 2024/4 »

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
2009. 1. 29. 15:29

MSSQL 계정 password 유실 Work/SQL Server2009. 1. 29. 15:29

가끔 password를 유실하는 경우가 있다.
이때는 SSMS (SQL Server Management Studio)에 SYSTEM 혹은 Windows 계정으로 연결 후 보안 / 로그인 / 해당 계정 의 [속성] 에서 password를 다시 설정 할 수 있다.

Stored procedure로도 할 수 있는데, sp_password 를 사용하면 된다.
:
Posted by Elick
2009. 1. 29. 14:52

MSSQL Database 분리 Work/SQL Server2009. 1. 29. 14:52

간단하게 database를 복사하거나 backup, 이동하고 싶은 경우 아래의 system stored procedure를 사용하여 작업을 하게된다.
exec sp_detach_db [database name] -- database 분리
-- file 이동
exec sp_attach_db [database name], [mdf file name (full path)], [ldf file name (full path)] -- database 연결

간혹 사고로 ldf를 유실했을 경우...혹은 뜻하지 않게 mdf만 존재하는 경우는 아래처럼 작업하면 된다.
exec sp_attach_single_file_db [database name], [mdf file name (full path)]

하지만, 위의 stored procedure를 사용할 수 있는 조건이 까다롭다.
 1. Data file과 log file이 한 개만 있는 database일 것.
 2. 복제된 database는 안된다.
 3. 다음 version의 MSSQL에서 사라질지도 모른다.

위의 stored procedure로 해결이 안될 경우 아래 처럼 CREATE DATABASE 의 FOR ATTACH_REBUILD_LOG 를 사용하여 작업한다.
CREATE DATABASE [database name] 
ON (NAME='[database name]', FILENAME='[[mdf file name (full path)]]') FOR ATTACH_REBUILD_LOG

그래도 복구가 안되는 경우는 다음의 절차로 작업한다.

 1. 동일한 database name으로 DB를 생성.

 2. MSSQL Server 중지 후 mdf file을 덮어 쓰기.

 3. MSSQL SQL 시작.

 4. system catalog변경을 위해서 allow update 수정.
use master
GO
sp_configure 'allow update', 1
reconfigure with override
GO

 5. 해당 database를 Emergency Mode(응급복구모드)로 변경.
ALTER DATABASE [database name] SET EMERGENCY

 6. SQL 서비스 다시 시작.
     
 7. log 다시 생성.
ALTER DATABASE dbname REBUILD LOG ON (NAME='[log name]', FILENAME='[ldf file name (full path)]')

 8. 일반모드 변경 후 system catalog 원상복귀.
ALTER DATABASE [database name] SET ONLINE
GO
use master
GO
sp_configure 'allow update', 0
reconfigure with override
GO

그래도 복구를 할 수 없다면... 미안하다고 하고 자리를 피한다.

:
Posted by Elick
2009. 1. 29. 13:18

CTE ( Common Table Expression ) 설명 Work/SQL Server2009. 1. 29. 13:18

CTE 개념 설명...
역시나 링크 사라질까바.. 복사...


-------------------------------------------------------------------
공용 테이블 식
John Papa

코드 다운로드 위치: DataPoints2007_10.exe (150 KB) 
Browse the Code Online
 목차
뷰, 파생 테이블 및 CTE 
CTE의 구조 
CTE의 이해 
재귀의 규칙 
간단한 재귀 연습 
요약 

개발자가 작업 중인 프로젝트 중에는 기본적인 SELECT/FROM/WHERE 문의 유형을 벗어난 복잡한 SQL 문 작성을 포함하는 것이 많습니다. 이러한 시나리오 중 하나로 FROM 절 내에서 인라인 뷰라고도 하는 파생 테이블을 사용하는 Transact-SQL(T-SQL) 쿼리를 작성하는 예가 있습니다. 개발자는 이러한 일반적인 방법으로 하나의 SELECT 문에서 행 집합을 얻은 다음, 즉시 이 행 집합을 다른 테이블, 뷰 및 사용자 정의 함수에 조인할 수 있습니다. 다른 옵션은 파생 테이블 대신 뷰를 사용하는 것입니다. 이러한 옵션은 각기 장단점이 있습니다.
SQL Server™ 2005를 사용하는 경우 필자는 CTE(공용 테이블 식)이라는 세 번째 옵션을 선호합니다. CTE를 사용하면 성능을 저해하지 않고도 코드 가독성과 관리 용이성을 개선할 수 있습니다. 또한 SQL Server 이전 버전에 비해 T-SQL로 재귀 코드를 작성하기가 훨씬 쉬워졌습니다.
이번 달 칼럼에서는 CTE를 사용하여 일반적인 개발 시나리오를 해결하는 데 초점을 맞추겠습니다. 먼저 CTE의 작동 방식과 CTE를 사용하여 해결할 수 있는 시나리오를 설명하겠습니다. 그런 다음 파생 테이블, 뷰 및 사용자 지정 프로시저와 같은 기존 T-SQL 구문에 비해 CTE를 사용할 때의 장점을 알아볼 것입니다. 칼럼 전반에서 예를 제공하고 이러한 예를 어디에 어떻게 사용해야 하는지 설명할 것입니다. 또한 CTE가 재귀 논리를 처리하는 방법을 설명하고 재귀적 CTE가 작동하는 방법을 정의할 것입니다. 이 칼럼에서 설명하는 모든 코드는 MSDN® Magazine 웹 사이트에서 다운로드할 수 있으며, SQL Server 2005와 함께 제공되는 Northwind 및 AdventureWorks 데이터베이스를 사용합니다.

뷰, 파생 테이블 및 CTE
CTE는 쿼리가 데이터베이스 내에 테이블로 존재하지 않는 데이터 집합에서 선택해야 하는 경우에 유용합니다. 예를 들어 고객 및 고객 주문을 바탕으로 값을 계산하는 집계된 데이터의 집합에 대해 쿼리를 작성하기를 원할 수 있습니다. 집계된 데이터를 Customers, Orders 및 Order Details 테이블을 함께 조인하고 합계를 계산한 다음 주문의 평균값을 계산할 수 있습니다. 그리고 집계된 행 집합에 대해 쿼리를 실행할 수 있습니다. 한 가지 솔루션은 먼저 집계된 데이터를 수집하는 뷰를 만든 다음 이 뷰를 대상으로 작업할 쿼리를 작성하는 것입니다. 다른 옵션은 파생 테이블을 사용하여 집계된 데이터에 대해 쿼리를 수행하는 것입니다. 이를 위해서 SQL 문을 FROM 절로 옮기고 이에 대해 쿼리를 수행할 수 있습니다.
일반적으로 뷰는 큰 쿼리를 나누어 알아보기 방법으로 쿼리를 수행하기 위해 사용됩니다. 예를 들어 테이블 10개를 조인하고, 수십 개의 열을 선택하며, 연관된 논리 집합을 바탕으로 열을 필터링하는 SELECT 문을 뷰로 나타낼 수 있습니다. 이러한 뷰는 데이터베이스 전체에 걸쳐 다른 SELECT 문에서 쿼리할 수 있습니다. 추상화를 통해 뷰가 나타내는 행 집합에 대한 손쉬운 액세스가 제공되며 데이터를 복제하거나 임시 테이블에 저장할 필요도 없습니다.
뷰는 사용 권한이 허락한다는 가정하에 데이터베이스 전체에 걸쳐 다시 사용할 수 있습니다. 예를 들어 그림 1에서는 뷰를 만든 다음 다른 T-SQL 문에서 다시 사용하고 있습니다. 그러나 데이터를 수집하고 한 번만 사용하려는 경우에는 뷰가 최적의 솔루션이 아닐 수 있습니다. 뷰는 데이터베이스에 존재하며 모든 일괄 처리에서 사용할 수 있는 데이터베이스 개체이므로 단일 T-SQL 일괄 처리에서만 사용할 뷰를 만드는 것은 과잉 처리입니다.
 Figure 1 쿼리되는 뷰
CREATE VIEW vwMyView AS
SELECT
    EmployeeID, COUNT(*) AS NumOrders,  MAX(OrderDate) AS MaxDate
FROM Orders
GROUP BY EmployeeID
GO

SELECT 
    e.EmployeeID, oe.NumOrders, oe.MaxDate, e.ReportsTo AS ManagerID, 
    om.NumOrders, om.MaxDate
FROM 
    Employees AS e
    INNER JOIN vwMyView AS oe ON e.EmployeeID = oe.EmployeeID
    INNER JOIN vwMyView AS om ON e.ReportsTo = om.EmployeeID

인라인 뷰라고 하는 파생 테이블을 만드는 다른 옵션이 있습니다. 파생 테이블을 만들려면 간단히 SELECT 문을 괄호로 감싸고 FROM 절 내부로 옮기면 됩니다. 이렇게 생성된 파생 테이블은 쿼리의 대상으로 사용하거나 테이블 또는 뷰와 마찬가지로 조인할 수 있습니다. 그림 2에서는 뷰가 아닌 파생 테이블을 사용하여 그림 1에서 해결한 것과 동일한 쿼리를 해결하고 있습니다. 파생 테이블은 이를 만드는 문 내에서만 액세스할 수 있으므로 일반적으로 쿼리를 읽고 유지 관리하기 어렵게 만듭니다. 이러한 문제는 동일한 일괄 처리 내에서 파생 테이블을 여러 번 사용하려는 경우에 가중됩니다. 다시 사용할 파생 테이블을 여러 번 복사하고 붙여 넣어야 하기 때문입니다.
 Figure 2 파생 테이블을 사용하는 쿼리
CTE는 뷰와 비슷하게 T-SQL을 훨씬 읽기 쉽게 만들어 주므로 이러한 시나리오에 잘 맞습니다. 또한 동일한 일괄 처리 내에서 곧 이은 후속 쿼리에서 다시 사용할 수도 있습니다. 물론 이 범위를 벗어나면 사용할 수 없게 됩니다. 또한 CTE는 언어 수준 구문이므로 SQL Server가 내부적으로 임시 또는 가상 테이블을 만들 필요가 없습니다. CTE의 기본 쿼리는 곧 이은 후속 쿼리에서 참조될 때마다 호출됩니다.
앞서 살펴본 것과 동일한 시나리오를 그림 3과 같이 CTE를 사용하여 작성할 수 있습니다. 집계된 데이터를 수집하는 EmpOrdersCTE는 CTE 바로 다음에 있는 쿼리에서 사용됩니다. CTE를 사용하는 그림 3의 코드는 쿼리를 매우 읽기 쉽게 만들어 주면서도 메타데이터를 저장하기 위한 시스템 개체를 만들지 않습니다.
 Figure 3 CTE를 사용한 쿼리
;WITH EmpOrdersCTE (EmployeeID, NumOrders, MaxDate) AS
(
  SELECT EmployeeID, COUNT(*), MAX(OrderDate)
  FROM Orders
  GROUP BY EmployeeID
)

SELECT 
    e.EmployeeID,  oe.NumOrders, oe.MaxDate,
    e.ReportsTo AS ManagerID,  om.NumOrders, om.MaxDate
FROM 
    Employees AS e
    INNER JOIN EmpOrdersCTE oe ON e.EmployeeID = oe.EmployeeID
    LEFT JOIN EmpOrdersCTE om ON e.ReportsTo = om.EmployeeID


CTE의 구조
다음은 간단한 CTE 예를 통해서 CTE를 작성하는 방법을 살펴보겠습니다. CTE는 WITH 키워드로 시작됩니다. 그러나 CTE가 일괄 처리의 첫 번째 문이 아닌 경우에는 WITH 키워드 앞에 세미콜론을 붙여야 합니다. 필자는 최선의 방법으로 모든 CTE 앞에 세미콜론을 붙이는 것을 선호합니다. 세미콜론이 필요한지 기억하는 것보다는 이렇게 하는 것이 훨씬 일관성이 있습니다.
WITH 키워드 다음에는 CTE의 이름을 지정하고 그 다음에는 선택적인 열 별칭 목록을 지정합니다. 열 별칭은 CTE 내의 SELECT 문에서 반환하는 열에 해당합니다. 선택적인 열 별칭 다음에는 필수 항목인 AS 키워드를 지정합니다. 그리고 AS 키워드 다음에는 CTE를 정의하는 쿼리 식을 괄호로 감싸고 지정합니다.
다음 예를 살펴보겠습니다.
;WITH myCTE (CustID, Co) AS
(
  SELECT CustomerID, CompanyName FROM Customers
)
SELECT CustID, Co FROM myCTE
CustomerID와 CompanyName열에는 CustID와 Co라는 별칭을 지정했으며, 곧이어 오는 CTE는 해당 열 별칭을 사용하여 CTE를 참조하는 SELECT 문입니다.

CTE의 이해
CTE 설계를 시작하기 전에 CTE가 작동하는 방식과 어떤 규칙을 따라야 하는지 이해해야 합니다. 이 섹션에서는 CTE를 사용할 수 있는 용도와 함께 CTE 내부에서 수행할 수 있는 작업과 그렇지 못한 작업에 대해 살펴보겠습니다. 우선 CTE는 T-SQL 일괄 처리, 사용자 정의 함수, 저장 프로시저, 트리고 및 뷰 내에서 만들고 사용할 수 있습니다.
CTE 바로 다음에 오는 문에서만 CTE를 참조할 수 있습니다. 이것은 CTE를 사용하려면 T-SQL 일괄 처리에서 CTE 다음에 즉시 CTE를 참조하는 쿼리를 작성해야 한다는 것을 의미합니다. 다름 들어 다음 일괄 처리는 오류가 발생합니다.
;WITH myCTE (CustID, Co) AS
(
  SELECT CustomerID, CompanyName FROM Customers
)
SELECT CompanyName FROM Customers WHERE CustomerID = 'ALFKI'
SELECT CustID, Co FROM myCTE
이 코드에서 myCTE는 바로 다음에 있는 첫 번째 쿼리에서만 사용할 수 있습니다. 두 번째 쿼리에서 myCTE를 참조하면 CTE는 범위를 벗어나며 "개체 이름 'myCTE'이(가) 잘못되었습니다."라는 예외가 발생합니다.
CTE는 어떤 방식으로든 데이터를 다시 처리하는 다른 쿼리에서 참조하기 위한 목적으로 사용되므로 CTE의 쿼리는 ORDER나 COMPUTE와 같은 문을 포함할 수 없습니다. 그러나 FOR XML과 같은 복잡한 문은 CTE를 정의하고 CTE에서 작동하도록 사용할 수 있습니다. 예를 들어 다음과 같이 FOR XML 절을 사용하여 CTE를 쿼리하고 해당 결과를 반환할 수 있습니다.
;WITH myCTE AS
(
  SELECT c.CustomerID, c.CompanyName, o.OrderID, o.OrderDate
  FROM Customers c 
  INNER JOIN Orders o ON c.CustomerID = o.CustomerID
)

SELECT CustomerID, CompanyName, OrderID, OrderDate 
FROM myCTE FOR XML AUTO
일단 CTE를 정의한 뒤에는 다음에 오는 첫 번째 쿼리에서 이를 여러 번 참조할 수 있습니다. 이러한 특성은 쿼리가 CTE를 두 번 이상 참조해야 하는 경우 특히 유용합니다. 그림 3의 코드 샘플에서는 쿼리에서 EmpOrdersCTE를 두 번 참조하여 직원과 해당 직원의 관리자를 얻는 방법을 보여 줍니다. 쿼리를 복제하는 것보다는 CTE를 두 번 참조하는 것이 더 간단하므로 이러한 방법은 동일한 행 집합을 두 번 이상 참조해야 하는 경우 매우 유용합니다.
CTE를 SELECT 문에서만 사용해야 하는 것은 아니며 CTE가 생성하는 행 집합을 참조하는 어떤 문에서나 CTE를 사용할 수 있습니다. 이것은 CTE 다음에 CTE를 사용하는 SELECT, INSERT, UPDATE 또는 DELETE 문이 올 수 있음을 의미합니다. CTE를 사용하는 쿼리에 전진 전용 및 스냅샷 커서를 사용할 수도 있습니다.
또는 CTE 다음에 다른 CTE가 오는 것도 가능합니다. 이 기술은 중간 결과를 행 집합에 수집하려는 경우 CTE로 다른 CTE를 만드는 데 사용할 수 있습니다. 다른 CTE로 구성된 CTE를 만들 때는 CTE 정의를 쉼표로 분리합니다.
그림 4에서는 직원의 목록과 각 직원별 전체 주문 횟수를 수집하는 EmpOrdersCTE를 정의합니다. MinMaxOrdersCTE라는 두 번째 CTE는 첫 번째 EmpOrdersCTE를 쿼리하고 행 집합에 집계 함수를 수행하여 직원의 평균, 최소 및 최대 주문 횟수를 확인합니다.
 Figure 4 다른 CTE를 참조하는 CTE
WITH 키워드 다음 쉼표로 분리하여 여러 개의 CTE를 정의할 수 있습니다. 이 경우 각 CTE는 다음 CTE에서 참조되어 새로운 CTE를 구성합니다. CTE 정의 다음에 오는 DML(데이터 조작 언어) 문에서는 WITH 절 내에 정의된 모든 CTE를 참조할 수 있습니다.

재귀의 규칙
재귀 알고리즘을 구현하는 데도 CTE를 사용할 수 있습니다. 재귀 논리는 스스로를 호출하는 알고리즘을 작성해야 할 때 유용하며, 데이터의 중첩된 집합을 탐색하는 데 자주 사용됩니다. T-SQL과 같은 언어에서 재귀 논리를 작성하기는 특히 까다롭지만 CTE를 설계한 목적 중 하나는 바로 이러한 재귀 논리를 해결하기 위한 것이었습니다. 재귀 CTE를 작성하는 기본 공식은 다음과 같습니다.
최상위 수준(앵커 멤버)을 반환하는 쿼리를 작성합니다.
재귀 쿼리(재귀 멤버)를 작성합니다.
첫 번째 쿼리에 재귀 번째 쿼리로 UNION을 수행합니다.
행이 반환되지 않는 경우에 대비합니다. 이것이 종료 검사입니다.
다음은 재귀 CTE의 예입니다.
;WITH myRecursiveCTE(col1, col2, ... coln) AS
  -- Anchor Member Query
  UNION ALL
  -- Recursive Member Query that references myRecursiveCTE
)
CTE를 포함하지 않는 사용자 지정 재귀 프로시저를 작성할 때는 반드시 명시적 종료 절을 추가해야 합니다. 종료 절은 최종적으로는 재귀 알고리즘을 종료하고 재귀 호출 스택을 원상 복귀하는 임무를 담당합니다. 이러한 절이 없으면 코드에 무한 루프가 발생합니다.
CTE에는 종료 절의 처리를 도울 수 있는 두 가지 측면이 있습니다. 첫 번째는 재귀 멤버가 레코드 0개를 반환하는 경우에 해당하는 암시적인 종료 절입니다. 이러한 경우 재귀 멤버 쿼리는 CTE를 재귀적으로 호출하지 않고 호출 스택을 원상 복귀합니다. 두 번째는 명시적으로 MAXRECURSION 수준을 설정할 수 있다는 것입니다.
MAXRECURSION 수준은 CTE를 포함하는 일괄 처리 내에서 명시적으로 설정하거나 서버 쪽 설정을 통해 설정할 수 있습니다. 서버 차원 설정의 기본값은 변경하지 않은 경우 100입니다. 이 설정은 CTE가 자신을 재귀적으로 호출할 수 있는 횟수를 제한합니다. 한계에 다다르면 예외가 발생합니다. MAXRECURSION 수준을 설정하는 구문은 다음과 같이 CTE 다음의 SELECT 문에서 OPTION 절을 사용하는 것입니다.
-- DEFINE YOUR CTE HERE
SELECT * FROM EmpCTE
OPTION (MAXRECURSION 7)
이 밖에도 재귀 CTE를 설계할 때 염두에 두어야 할 몇 가지 다른 규칙이 있습니다. 재귀 CTE는 앵커 멤버와 재귀 멤버를 모두 포함해야 합니다. 두 멤버에는 같은 수의 열이 있어야 하며 두 멤버에 속한 열은 데이터 형식이 일치해야 합니다. 재귀 멤버는 CTE를 한 번만 참조할 수 있으며 멤버에 다음 절이나 키워드를 사용할 수 없습니다.
SELECT DISTINCT
GROUP BY
HAVING
TOP
LEFT/RIGHT OUTER JOIN

간단한 재귀 연습
데이터와 행 집합의 관점에서 재귀는 동일한 데이터 집합에 대해 다른 조건을 적용하여 동일한 논리를 반복적으로 수행해야 하는 문제를 해결하는 데 사용됩니다. 예를 들어 모든 판매 사원을 검색하고 해당 사원의 관리자를 찾아 계층 순서대로 데이터를 반환해야 한다고 가정해 보겠습니다. 그림 5에서는 재귀를 사용하여 영업 부사장 밑에서 일하는 직원의 목록을 수집하는 CTE를 사용하는 솔루션을 보여 주고 있습니다.
 Figure 5 재귀적으로 판매 사원 정보 수집
몇 가지 추가적인 측면을 제외하고 그림 5에 있는 재귀 CTE는 표준 CTE와 매우 비슷합니다. 표준 CTE는 행 집합을 정의하는 쿼리 한 개를 포함하는데 반해, 재귀 CTE는 두 개의 쿼리 정의를 정의합니다. 첫 번째 쿼리 정의인 앵커 멤버는 CTE가 호출될 때 실행될 쿼리를 정의합니다. 두 번째 쿼리 정의인 재귀 멤버는 앵커 멤버와 동일한 열과 데이터 형식을 반환하는 쿼리를 정의합니다. 재귀 멤버는 또한 재귀적으로 CTE에 콜백을 수행하는 데 사용할 값을 검색합니다. 쿼리의 결과는 UNION 문을 사용하여 함께 가져오게 됩니다.
그림 5의 EmpCTE는 영업 부사장(EmployeeID = 2)에 대한 직원 레코드를 가져오는 앵커 멤버를 보여 줍니다. 앵커 멤버 쿼리의 마지막 열은 계층의 0번째 수준, 즉 최상위를 나타내는 0 값을 반환합니다. 재귀 멤버의 쿼리는 이전 직원 밑에서 근무하는 직원의 목록을 얻습니다. 이를 위해 Employees 테이블을 EmpCTE에 조인합니다.
재귀 멤버에서도 동일한 열이 검색되지만 SalesLevel 열은 현재 직원의 관리자를 얻고, 관리자의 SalesLevel을 얻은 다음, 이를 1만큼 증가시켜서 계산됩니다. m.SalesLevel+1 식은 우리의 앵커 멤버로부터 검색한 영업 부사장 바로 밑에서 근무하는 모든 직원에게 SalesLevel 1 값을 할당합니다. 이러한 직원 밑에서 일하는 모든 직원은 SalesLevel 2 값을 가집니다. SalesLevel 값은 영업 조직 계층의 이어지는 각 수준에서 이와 같이 점차적으로 증가합니다.

요약
쿼리 내에서 복잡한 파생 테이블을 사용하거나 T-SQL 일괄 처리 외부에 정의가 있는 뷰를 참조하는 것이 비하면 CTE는 T-SQL을 훨씬 읽기 쉽게 작성하는 방법을 제공합니다. CTE는 또한 재귀 알고리즘을 사용하는 데 따르는 어려움을 완화하는 데 도움을 주는 훨씬 개선된 도구를 제공합니다. 표준 CTE 또는 재귀 CTE 중 어떤 것을 사용하는지에 관계없이 CTE를 통해 여러 일반적인 개발 시나리오의 문제를 해결하고 성능을 저하시키지 않고도 가독성을 높일 수 있게 될 것입니다.

John에게 질문이나 의견이 있으면 다음 전자 메일 주소로 보내시기 바랍니다: mmdata@microsoft.com.


John Papa는 ASPSOFT(aspsoft.com)의 선임 .NET 컨설턴트로, 여름이면 밤마다 애견 Kadi를 데리고 가족과 함께 양키스 팀을 열광적으로 응원하는 야구광입니다. John은 C# MVP이며 ADO, XML 및 SQL Server에 대한 여러 권의 책을 저술했습니다. 그는 VSLive와 같은 업계 회의에서 자주 강연하며 codebetter.com/blogs/john.papa라는 블로그를 운영하고 있습니다.
:
Posted by Elick
DMV의 활용에 대해 MSDN에 설명된 것. 혹시 링크가 사라질까바 옮기는 것일 뿐 ...


----------------------------------------------------------------------------------------

SQL Server
응용 프로그램 성능 최적화를 위한 숨겨진 데이터 찾기
Ian Stirk
코드 다운로드 위치: DMVsinSQLServer2008_01.exe (155 KB) 
Browse the Code Online 

이 기사에서 다루는 내용: 
동적 관리 뷰 및 함수 
서버 대기의 원인 
일반적인 인덱스 문제 
논리 I/O와 관련된 문제 
 이 기사에서 사용하는 기술: 
SQL Server 
 

 목차 
서버 대기의 원인 
읽기 및 쓰기 
데이터베이스별 누락된 인덱스 
비용이 높은 누락된 인덱스 
사용되지 않는 인덱스 
사용 비용이 높은 인덱스 
자주 사용되는 인덱스 
논리적으로 조각난 인덱스 
I/O 비용이 높은 쿼리 
CPU 비용이 높은 쿼리 
비용이 높은 CLR 쿼리 
가장 많이 실행된 쿼리 
차단당하는 쿼리 
가장 적게 재사용되는 계획 
추가 작업 

응용 프로그램 성능 문제의 많은 부분은 성능이 낮은 데이터베이스 쿼리가 원인이지만 데이터베이스 성능을 개선할 수 있는 방법이 많이 있습니다. SQL ServerTM 2005에서는 성능 문제의 원인을 찾는 데 활용할 수 있는 많은 정보를 수집합니다.
SQL Server 2005는 쿼리 실행과 관련된 데이터를 수집합니다. 서버가 시작되면 누적되기 시작하는 이 데이터는 메모리에 보관되며 테이블 인덱스, 쿼리 성능 및 서버 I/O와 관련된 항목을 포함하므로 다양한 문제 및 메트릭을 확인하는 데 사용할 수 있습니다. 이 데이터는 SQL DMV(동적 관리 뷰) 및 관련된 DMF(동적 관리 함수)를 통해 쿼리할 수 있습니다. 이 두 가지는 서버 상태 정보를 제공하는 시스템 기반 뷰 및 함수이며 문제 진단 및 데이터베이스 성능 튜닝에 사용할 수 있습니다.
이 기사에서는 SQL Server 2005가 이미 수집하고 있는 정보를 활용하여 성능을 개선할 수 있는 부분을 집중적으로 살펴보겠습니다. 이 방법은 기존의 데이터를 수집하고 검사하며 일반적으로 기본 시스템 데이터를 쿼리하므로 방해가 되지 않습니다.
또한, 이러한 정보를 확보하는 방법과 기본 DMV에 대해 설명하고, 데이터를 해석할 때 주의할 점을 지적하며, 성능 향상을 실현할 수 있는 다른 부가적인 영역을 소개할 것입니다. 이를 위해 SQL Server 2005가 수집하는 데이터의 다양한 측면을 세부적으로 살펴보는 일련의 SQL 스크립트를 제공할 것입니다. 주석이 포함되어 있는 완전한 버전의 스크립트는 MSDN® Magazine 웹 사이트에서 다운로드할 수 있습니다.
여기에서 설명하는 일부 단계에서는 선택한 서버에서 호스트되는 모든 데이터베이스를 포함하는 서버 전체가 대상이지만, 필요한 경우에는 쿼리에 데이터베이스의 이름을 추가하는 등 적절한 필터링을 통해 특정 데이터베이스에 한정하는 것이 가능합니다.
반면에 일부 쿼리는 현재 데이터베이스에 대한 결과만 보고하는 데이터베이스별 뷰인 sys.indexes DMV에 조인합니다. 이러한 경우 시스템 저장 프로시저 sp_MSForEachDB를 사용하여 서버에 있는 모든 데이터베이스에 대해 반복하도록 쿼리를 작성했습니다. 즉, 이 경우에도 서버 수준의 결과를 얻을 수 있습니다.
선택한 성능 메트릭에서 가장 연관성 높은 레코드를 대상으로 하기 위해 SQL TOP 함수를 사용하여 반환되는 레코드 수를 제한할 것입니다.


서버 대기의 원인
사용자는 일반적으로 연속된 대기를 통해 성능 저하를 느끼게 됩니다. SQL 쿼리를 실행할 수 있지만 다른 리소스 때문에 대기해야 할 때마다 이러한 대기에 대한 세부 사항이 기록됩니다. 이러한 세부 사항은 sys.dm_os_wait_stats DMV를 통해 액세스할 수 있습니다. 그림 1에 있는 SQL 스크립트를 사용하여 누적된 대기의 원인을 조사할 수 있습니다.
 Figure 1 SQL 쿼리 레코드로 인한 대기 시간 증가 
 코드 복사
SELECT TOP 10
 [Wait type] = wait_type,
 [Wait time (s)] = wait_time_ms / 1000,
 [% waiting] = CONVERT(DECIMAL(12,2), wait_time_ms * 100.0 
               / SUM(wait_time_ms) OVER())
FROM sys.dm_os_wait_stats
WHERE wait_type NOT LIKE '%SLEEP%' 
ORDER BY wait_time_ms DESC;


 
그림 1 SQL 쿼리 레코드로 인한 대기 시간 증가 
이 스크립트를 실행하면 대기 유형이 전체 대기 소요 시간을 기준으로 정렬되어 나열됩니다. 필자의 샘플 결과에서는 대기의 원인으로 I/O가 비교적 높게 기록된 것을 볼 수 있습니다. 처음 로드한 다음 데이터는 일반적으로 메모리에 있으므로 실제 I/O가 아닌 논리 I/O(메모리 내의 데이터 읽기/쓰기)에 집중한다는 데 주의하십시오.


읽기 및 쓰기
높은 I/O 사용률은 효율이 낮은 데이터 액세스 메커니즘을 나타내는 것일 수 있습니다. SQL Server 2005는 각 쿼리가 요구를 충족하기 위해 사용하는 전체 읽기 및 쓰기 횟수를 추적합니다. 이러한 수를 더해 어떤 데이터베이스가 전반적으로 가장 많은 읽기 및 쓰기를 수행하는지 알아낼 수 있습니다.
sys.dm_exec_query_stats DMV에는 캐시된 쿼리 계획에 대한 집계 성능 통계가 포함됩니다. 여기에는 논리 읽기 및 쓰기와 쿼리 실행 횟수에 대한 정보가 포함됩니다. 이 DMV를 sys.dm_exec_sql_text DMF에 조인하면 데이터베이스별로 읽기 및 쓰기 횟수 합계를 구할 수 있습니다. 이 조인을 처리하는 데는 새로운 SQL Server 2005 CROSS APPLY 연산자를 사용했습니다. 어떤 데이터베이스에서 가장 많은 읽기 및 쓰기를 사용하는지 확인하는 스크립트는 그림 2에서 볼 수 있습니다.
 Figure 2 가장 많은 읽기 및 쓰기를 사용하는 데이터베이스 확인 
 코드 복사
SELECT TOP 10 
        [Total Reads] = SUM(total_logical_reads)
        ,[Execution count] = SUM(qs.execution_count)
        ,DatabaseName = DB_NAME(qt.dbid)
FROM sys.dm_exec_query_stats qs
CROSS APPLY sys.dm_exec_sql_text(qs.sql_handle) as qt
GROUP BY DB_NAME(qt.dbid)
ORDER BY [Total Reads] DESC;

SELECT TOP 10 
        [Total Writes] = SUM(total_logical_writes)
        ,[Execution count] = SUM(qs.execution_count)
        ,DatabaseName = DB_NAME(qt.dbid)
FROM sys.dm_exec_query_stats qs
CROSS APPLY sys.dm_exec_sql_text(qs.sql_handle) as qt
GROUP BY DB_NAME(qt.dbid)
ORDER BY [Total Writes] DESC;


 
그림 2 가장 많은 읽기 및 쓰기를 사용하는 데이터베이스 확인 
결과에서 어떤 데이터베이스가 논리 페이지에 대한 읽기 및 쓰기를 가장 많이 수행하는지 볼 수 있습니다. 위쪽 데이터 집합은 Total Reads를 기준으로 정렬되며 아래쪽 집합은 Total Writes를 기준으로 정렬됩니다.
몇 가지 인스턴스에서 DatabaseName이 NULL로 설정된 것을 확인할 수 있습니다. 이 설정은 임시 및 준비된 SQL 문을 나타냅니다. 이 세부 정보는 네이티브 SQL의 사용 정도를 확인하는 데 유용합니다. 네이티브 SQL은 그 자체로 쿼리 계획이 재사용되지 않거나, 코드가 재사용되지 않거나, 보안 영역에 잠재적인 우려가 있는 등의 여러 다른 문제의 원인일 수 있습니다.
tempdb 값이 높다는 것은 임시 테이블의 과도한 사용, 과도한 다시 컴파일 또는 비효율적인 장치를 의미할 수 있습니다. 이 결과를 통해 트랜잭션 데이터베이스(업데이트가 많음)와는 다른 보고(데이터 선택이 많음)에 주로 사용되는 데이터베이스를 확인할 수 있습니다. 보고 또는 트랜잭션 같은 각 데이터베이스 유형에 따라 다른 인덱싱이 요구됩니다. 이에 대한 자세한 내용은 조금 뒤에 살펴보겠습니다.


데이터베이스별 누락된 인덱스
SQL Server가 쿼리를 처리할 때 최적화 프로그램은 쿼리를 수행하기 위해 사용하려고 시도한 인덱스에 대한 기록을 보관합니다. 이러한 인덱스가 없는 경우 SQL Server는 누락된 인덱스에 대한 기록을 만듭니다. 이 정보는 sys.dm_db_missing_index_details DMV를 통해 볼 수 있습니다.
그림 3에 있는 스크립트를 사용하면 선택한 서버의 어떤 데이터베이스에 인덱스가 누락되었는지 확인할 수 있습니다. 인덱스는 쿼리 데이터를 검색하는 최적의 경로를 제공하는 경우가 많으므로 누락된 인덱스를 발견하는 것은 중요합니다. 결과적으로 I/O를 줄이고 전반적인 성능을 개선할 수 있습니다. 필자의 스크립트는 sys.dm_db_missing_index_details를 검사하고 데이터베이스별로 누락된 인덱스의 수를 합산하여 추가 조사가 필요한 데이터베이스를 쉽게 확인할 수 있도록 해 줍니다.
 Figure 3 누락된 데이터베이스 확인 
 코드 복사
SELECT 
    DatabaseName = DB_NAME(database_id)
    ,[Number Indexes Missing] = count(*) 
FROM sys.dm_db_missing_index_details
GROUP BY DB_NAME(database_id)
ORDER BY 2 DESC;


 
그림 3 누락된 데이터베이스 확인 
데이터베이스는 트랜잭션 시스템과 보고 기반 시스템으로 나뉘는 경우가 많습니다. 보고 데이터베이스의 경우에는 제안된 누락 인덱스를 적용하는 것이 비교적 쉽습니다. 반면에 트랜잭션 데이터베이스의 경우에는 기본 테이블 데이터에 대한 인덱스 추가가 미치는 영향에 대한 추가 조사가 필요합니다.


비용이 높은 누락된 인덱스
인덱스가 쿼리 성능에 미치는 영향의 정도는 다양합니다. 서버에 있는 모든 데이터베이스에 대해 가장 비용이 높은 누락된 인덱스에 대한 조사를 수행하여 어떤 누락된 인덱스를 추가했을 때 성능에 긍정적인 영향이 있을지 알아낼 수 있습니다.
sys.dm_db_missing_index_group_stats DMV는 SQL이 특정 누락된 인덱스를 사용하려고 시도한 횟수를 나타냅니다. sys.dm_db_missing_index_details DMV는 쿼리에서 요구되는 열과 같은 누락된 인덱스의 구조에 대한 세부 사항을 제공합니다. 이러한 정보는 sys.dm_db_missing_index_groups DMV를 통해 서로 연결된 두 개의 DMV에 있습니다. 누락된 인덱스의 비용(Total Cost 열)은 평균 전체 사용자 비용 및 사용자 검색과 사용자 스캔의 합으로 곱한 평균 사용자 영향의 곱으로 계산됩니다.
그림 4에 있는 스크립트를 사용하여 가장 비용이 높은 누락된 인덱스를 확인할 수 있습니다. Total Cost를 기준으로 정렬되는 이 쿼리의 결과를 보면 가장 중요한 누락된 인덱스를 비롯하여 데이터베이스/스키마/테이블에 대한 세부 사항과 누락된 인덱스에 필요한 열을 알 수 있습니다. 특히 이 스크립트를 통해 같음 및 같지 않음 SQL 문에서 사용되는 열을 알 수 있습니다. 또한 누락된 인덱스에 포함된 열과 함께 사용될 다른 열에 대해서도 보고합니다. 포함된 열을 통해 기본 페이지에서 데이터를 가져오지 않고도 더 많은 쿼리의 요구를 충족시킬 수 있어 필요한 I/O 작업이 줄어들고 성능이 향상됩니다.
 Figure 4 누락된 인덱스의 비용 
 코드 복사
SELECT  TOP 10 
        [Total Cost]  = ROUND(avg_total_user_cost * avg_user_impact * (user_seeks + user_scans),0) 
        , avg_user_impact
        , TableName = statement
        , [EqualityUsage] = equality_columns 
        , [InequalityUsage] = inequality_columns
        , [Include Cloumns] = included_columns
FROM        sys.dm_db_missing_index_groups g 
INNER JOIN    sys.dm_db_missing_index_group_stats s 
       ON s.group_handle = g.index_group_handle 
INNER JOIN    sys.dm_db_missing_index_details d 
       ON d.index_handle = g.index_handle
ORDER BY [Total Cost] DESC;


 
그림 4 누락된 인덱스의 비용 (더 크게 보려면 이미지를 클릭하십시오.)
결과에 표시되는 순서대로 요구되는 인덱스에서 열을 만들어야 하는 것은 아닙니다. 필요한 순서를 확인하려면 전체적인 SQL 코드 기반을 조사해야 합니다. 일반적인 규칙은 가장 자주 선택되는 열을 인덱스 처음에 배치하는 것입니다.
누락된 인덱스의 비용을 계산할 때는 사용자 열(예: user_seeks 및 user_scans)만 고려된다는 것을 기억하십시오. 시스템 열은 통계, DBCC(데이터베이스 일관성 검사) 및 DDL(데이터 정의 언어) 명령 사용을 나타내는 경우가 많으며 이러한 작업은 데이터베이스 관리 기능에는 중요하지만 비즈니스 기능을 수행하는 데는 중요성이 떨어집니다.
기본 테이블에 대한 데이터 수정이 추가 인덱스의 잠재적인 비용에 주는 영향에 대한 특별한 고려가 필요하다는 것을 기억하십시오. 따라서 기본 SQL 코드 기반에 대한 추가 조사가 반드시 필요합니다.
포함하도록 권장되는 열의 수가 지나치게 많은 경우에는 포괄적인 "SELECT *" 문이 사용되고 있음을 나타내는 것일 수 있으므로 기본 SQL을 살펴보아야 합니다. 만약 그렇다면 선택 쿼리를 다시 작성해야 할 수 있습니다.


사용되지 않는 인덱스
사용되지 않는 인덱스는 성능에 부정적인 영향을 줍니다. 기본 테이블 데이터가 수정되면 인덱스 역시 업데이트해야 할 수 있기 때문입니다. 이 작업에는 추가 시간이 소요되는 것은 물론이며 차단이 증가할 우려도 있습니다.
쿼리를 충족하기 위해 인덱스가 사용되고, 이 인덱스가 기본 테이블 데이터에 적용되는 업데이트에 의해 업데이트되면 SQL Server는 해당 인덱스 사용의 세부 사항을 업데이트합니다. 이런 사용 세부 사항을 보면 사용되지 않는 인덱스를 확인할 수 있습니다.
sys.dm_db_index_usage_stats DMV를 보면 인덱스가 얼마나 자주 그리고 어느 정도까지 사용되는지 알 수 있습니다. 이 DMV는 인덱스를 만드는 데 사용된 정보가 포함된 sys.indexes DMV와 조인됩니다. 다양한 사용자 열에서 사용되지 않는 인덱스를 의미하는 0 값이 있는지 조사할 수 있습니다. 시스템 열의 영향은 앞에서 설명한 이유 때문에 여기에서도 무시됩니다. 그림 5에 있는 스크립트를 사용하여 가장 비용이 높은 사용되지 않는 인덱스를 확인할 수 있습니다.
 Figure 5 가장 비용이 높은 사용되지 않은 인덱스 확인 
 코드 복사
-- 필요한 테이블 구조만 작성합니다.
-- 참고: 이 SQL은 다음 단계에서 지정된 데이터베이스 루프 내에 있어야 합니다.
SELECT TOP 1
        DatabaseName = DB_NAME()
        ,TableName = OBJECT_NAME(s.[object_id])
        ,IndexName = i.name
        ,user_updates    
        ,system_updates    
        -- 기타 유용한 필드를 아래에 나열
        --, *
INTO #TempUnusedIndexes
FROM   sys.dm_db_index_usage_stats s 
INNER JOIN sys.indexes i ON  s.[object_id] = i.[object_id] 
    AND s.index_id = i.index_id 
WHERE  s.database_id = DB_ID()
    AND OBJECTPROPERTY(s.[object_id], 'IsMsShipped') = 0
    AND    user_seeks = 0
    AND user_scans = 0 
    AND user_lookups = 0
    AND s.[object_id] = -999  -- 테이블 구조를 얻기 위한 임시 값
;

-- 서버의 모든 데이터베이스를 대상으로 반복합니다.
EXEC sp_MSForEachDB    'USE [?]; 
-- 테이블이 이미 있는 경우
INSERT INTO #TempUnusedIndexes 
SELECT TOP 10    
        DatabaseName = DB_NAME()
        ,TableName = OBJECT_NAME(s.[object_id])
        ,IndexName = i.name
        ,user_updates    
        ,system_updates    
FROM   sys.dm_db_index_usage_stats s 
INNER JOIN sys.indexes i ON  s.[object_id] = i.[object_id] 
    AND s.index_id = i.index_id 
WHERE  s.database_id = DB_ID()
    AND OBJECTPROPERTY(s.[object_id], ''IsMsShipped'') = 0
    AND    user_seeks = 0
    AND user_scans = 0 
    AND user_lookups = 0
    AND i.name IS NOT NULL    -- HEAP 인덱스 무시
ORDER BY user_updates DESC
;
'

-- 레코드 선택
SELECT TOP 10 * FROM #TempUnusedIndexes ORDER BY [user_updates] DESC
-- 임시 테이블 정리
DROP TABLE #TempUnusedIndexes
(참고: 프로그래머 주석은 예제 프로그램 파일에는 영문으로 제공되며 기사에는 이해를 돕기 위해 번역문으로 제공됩니다.)

 
그림 5 가장 비용이 높은 사용되지 않은 인덱스 확인 (더 크게 보려면 이미지를 클릭하십시오.)
이 쿼리 결과는 데이터를 검색하는 데는 사용되지 않으면서도 기본 테이블이 변경되면 함께 업데이트되는 인덱스를 보여 줍니다. 이러한 업데이트는 user_updates 및 system_updates 열에 표시됩니다. 결과는 인덱스에 적용된 사용자 업데이트 횟수를 기준으로 정렬됩니다.
인덱스가 실제로 사용되지 않는지 확인하려면 충분한 정보를 수집해야 합니다. 분기별 또는 매년 실행되는 쿼리에 중요한 인덱스가 실수로 제거되는 상황을 원하지는 않을 것입니다. 또한 일부 인덱스는 중복 레코드 삽입을 제한하거나 데이터를 정렬하는 데 사용됩니다. 사용되지 않는 인덱스를 제거하기 전에 반드시 이러한 요소를 고려해야 합니다.
기본적인 형식의 쿼리는 현재 데이터베이스에만 연관되는 sys.indexes DMV에 조인되므로 현재 데이터베이스에만 적용됩니다. 시스템 저장 프로시저 sp_MSForEachDB를 사용하면 서버의 모든 데이터베이스에 대한 결과를 추출할 수 있습니다. 이를 위해 필자가 사용한 패턴은 "모든 데이터베이스를 대상으로 반복" 보충 기사를 참조하십시오. 스크립트의 다른 섹션에서도 서버의 모든 데이터베이스를 대상으로 반복하려는 부분에 이 패턴을 사용했습니다. 또한 정식 인덱스가 없는 테이블의 네이티브 구조를 나타내는 힙 유형의 인덱스는 필터링하였습니다.


사용 비용이 높은 인덱스
사용되고 있는 인덱스 중에서도 기본 테이블을 변경할 때 비용이 가능 높은 인덱스를 확인할 수 있다면 유용할 것입니다. 비용은 성능에 부정적인 영향을 주지만 인덱스 자체는 데이터 검색에 중요할 수 있습니다.
sys.dm_db_index_usage_stats DMV를 보면 인덱스가 얼마나 자주 그리고 어느 정도까지 사용되는지 알 수 있습니다. 이 DMV는 인덱스를 만드는 데 사용된 세부 사항이 포함된 sys.indexes DMV와 조인됩니다. user_updates 및 system_updates 열을 조사하면 유지 관리 비용이 높은 인덱스를 볼 수 있습니다. 그림 6에 있는 스크립트는 가장 비용이 높은 인덱스를 확인하고 결과를 보여 줍니다.
 Figure 6 가장 비용이 높은 인덱스 확인 
 코드 복사
-- 필요한 테이블 구조만 작성합니다.
-- 참고: 이 SQL은 다음 단계에서 지정된 데이터베이스 루프 내에 있어야 합니다.
SELECT TOP 1
        [Maintenance cost]  = (user_updates + system_updates)
        ,[Retrieval usage] = (user_seeks + user_scans + user_lookups)
        ,DatabaseName = DB_NAME()
        ,TableName = OBJECT_NAME(s.[object_id])
        ,IndexName = i.name
INTO #TempMaintenanceCost
FROM   sys.dm_db_index_usage_stats s 
INNER JOIN sys.indexes i ON  s.[object_id] = i.[object_id] 
    AND s.index_id = i.index_id
WHERE s.database_id = DB_ID() 
    AND OBJECTPROPERTY(s.[object_id], 'IsMsShipped') = 0
    AND (user_updates + system_updates) > 0 – 활성 행에 대해서만 보고
    AND s.[object_id] = -999  -- 테이블 구조를 얻기 위한 임시 값
;

-- 서버의 모든 데이터베이스를 대상으로 반복합니다.
EXEC sp_MSForEachDB    'USE [?]; 
-- 테이블이 이미 있는 경우
INSERT INTO #TempMaintenanceCost 
SELECT TOP 10
        [Maintenance cost]  = (user_updates + system_updates)
        ,[Retrieval usage] = (user_seeks + user_scans + user_lookups)
        ,DatabaseName = DB_NAME()
        ,TableName = OBJECT_NAME(s.[object_id])
        ,IndexName = i.name
FROM   sys.dm_db_index_usage_stats s 
INNER JOIN sys.indexes i ON  s.[object_id] = i.[object_id] 
    AND s.index_id = i.index_id
WHERE s.database_id = DB_ID() 
    AND i.name IS NOT NULL    -- HEAP 인덱스 무시
    AND OBJECTPROPERTY(s.[object_id], ''IsMsShipped'') = 0
    AND (user_updates + system_updates) > 0 -- 활성 행에 대해서만 보고
ORDER BY [Maintenance cost]  DESC
;
'

-- 레코드 선택
SELECT TOP 10 * FROM #TempMaintenanceCost 
ORDER BY [Maintenance cost]  DESC
-- 임시 테이블 정리
DROP TABLE #TempMaintenanceCost


 
그림 6 가장 비용이 높은 인덱스 확인 (더 크게 보려면 이미지를 클릭하십시오.)
결과를 통해 유지 관리 비용이 높은 인덱스와 이에 연관된 데이터베이스/테이블에 대한 세부 사항을 볼 수 있습니다. Maintenance cost 열은 user_updates 및 system_updates 열의 합으로 계산됩니다. 인덱스의 유용성(Retrieval usage 열에 표시됨)은 다양한 user_* 열의 합으로 계산됩니다. 인덱스 제거 여부를 결정할 때는 인덱스의 유용성을 고려해야 합니다.
이러한 결과를 토대로 데이터를 대량으로 수정하는 경우 업데이트를 적용하기 전에 제거해야 하는 인덱스를 확인할 수 있습니다. 그리고 업데이트를 완료한 다음 인덱스를 다시 적용하면 됩니다.


자주 사용되는 인덱스
모든 데이터베이스를 대상으로 반복
sys.indexes DMV는 데이터베이스별 뷰입니다. 따라서 sys.indexes로 조인하는 쿼리는 현재 데이터베이스에 대한 결과만 보고합니다. 시스템 저장 프로시저 sp_MSForEachDB를 사용하면 서버에 있는 모든 데이터베이스에 대해 반복하여 서버 수준의 결과를 얻을 수 있습니다. 이를 위해 필자가 사용한 패턴은 다음과 같습니다. 
코드 본문과 비슷한 필요한 구조로 임시 테이블을 만듭니다. 존재하지 않는 레코드(object_id를 -999로 지정)를 지정하여 임시 테이블 구조가 생성되도록 합니다.
코드 본문이 실행되어 서버에 있는 모든 데이터베이스를 대상으로 반복합니다. 각 데이터베이스에서 검색되는 레코드의 수(TOP 문 사용)는 표시하려는 레코드의 수와 같아야 합니다. 그렇지 않으면 결과는 서버의 모든 데이터베이스에 걸친 TOP n 레코드를 올바르게 나타내지 못할 수 있습니다.
레코드는 임시 테이블에서 추출되고 관심이 있는 열(이 경우에는 user_updates 열)을 기준으로 정렬됩니다.

DMV를 사용하여 가장 자주 사용되는 인덱스를 확인할 수 있습니다. 이러한 인덱스는 기본 데이터에 대한 가장 일반적인 경로이므로 이를 개선하거나 최적화한다면 전체적으로 높은 성능 향상을 거둘 수 있습니다.
sys.dm_db_index_usage_stats DMV에는 검색, 스캔 및 조회를 통해 데이터를 검색하는 데 인덱스가 얼마나 자주 사용되었는지에 대한 세부 사항이 포함되어 있습니다. 이 DMV는 인덱스를 만드는 데 사용된 세부 사항이 포함된 sys.indexes DMV와 조인됩니다. Usage 열은 모든 user_* 열의 합으로 계산됩니다. 그림 7에 있는 스크립트를 사용하여 이를 수행할 수 있습니다. 이 쿼리의 결과는 인덱스가 사용된 횟수를 Usage를 기준으로 정렬하여 보여 줍니다.
 Figure 7 가장 많이 사용되는 인덱스 확인 
 코드 복사
-- 필요한 테이블 구조만 작성합니다.
-- 참고: 이 SQL은 다음 단계에서 지정된 데이터베이스 루프 내에 있어야 합니다.
SELECT TOP 1
        [Usage] = (user_seeks + user_scans + user_lookups)
        ,DatabaseName = DB_NAME()
        ,TableName = OBJECT_NAME(s.[object_id])
        ,IndexName = i.name
INTO #TempUsage
FROM   sys.dm_db_index_usage_stats s 
INNER JOIN sys.indexes i ON s.[object_id] = i.[object_id] 
    AND s.index_id = i.index_id 
WHERE   s.database_id = DB_ID() 
    AND OBJECTPROPERTY(s.[object_id], 'IsMsShipped') = 0
    AND (user_seeks + user_scans + user_lookups) > 0 
-- 활성 행에 대해서만 보고
    AND s.[object_id] = -999  -- 테이블 구조를 얻기 위한 임시 값
;

-- 서버의 모든 데이터베이스를 대상으로 반복합니다.
EXEC sp_MSForEachDB    'USE [?]; 
-- 테이블이 이미 있는 경우
INSERT INTO #TempUsage 
SELECT TOP 10
        [Usage] = (user_seeks + user_scans + user_lookups)
        ,DatabaseName = DB_NAME()
        ,TableName = OBJECT_NAME(s.[object_id])
        ,IndexName = i.name
FROM   sys.dm_db_index_usage_stats s 
INNER JOIN sys.indexes i ON s.[object_id] = i.[object_id] 
    AND s.index_id = i.index_id 
WHERE   s.database_id = DB_ID() 
    AND i.name IS NOT NULL    -- HEAP 인덱스 무시
    AND OBJECTPROPERTY(s.[object_id], ''IsMsShipped'') = 0
    AND (user_seeks + user_scans + user_lookups) > 0 -- 활성 행에 대해서만 보고
ORDER BY [Usage]  DESC
;
'

-- 레코드 선택
SELECT TOP 10 * FROM #TempUsage ORDER BY [Usage] DESC
-- 임시 테이블 정리
DROP TABLE #TempUsage


 
그림 7 가장 많이 사용되는 인덱스 확인 (더 크게 보려면 이미지를 클릭하십시오.)
가장 많이 사용되는 인덱스는 기본 데이터에 대한 가장 중요한 액세스 경로를 나타냅니다. 이러한 인덱스를 제거할 이유는 없을 것이지만 인덱스가 최적의 상태인지 확인할 필요는 있습니다. 예를 들어 특히 데이터가 순차적으로 검색되는 경우 인덱스 조각화 수준이 낮은지 그리고 기본 통계가 최신인지 확인해야 합니다. 그리고 테이블에 사용되지 않는 인덱스가 있으면 제거해야 합니다.


논리적으로 조각난 인덱스
논리적 인덱스 조각화는 인덱스에서 정렬되지 않은 항목의 비율을 나타냅니다. 이것은 페이지 사용률 조각화 유형과는 다릅니다. 논리적 조각화는 인덱스를 사용하는 모든 순서 검색에 영향을 줍니다. 이러한 조각화는 가급적 제거해야 하며 이를 위해 인덱스를 다시 작성하거나 다시 구성할 수 있습니다.
다음 DMV를 사용하여 논리적으로 가장 많이 조각난 인덱스를 확인할 수 있습니다. sys.dm_db_index_physical_stats DMV를 사용하면 인덱스의 크기 및 조각화에 대한 세부 사항을 볼 수 있습니다. 이 DMV는 인덱스를 만드는 데 사용된 세부 사항이 포함된 sys.indexes DMV와 조인됩니다.
그림 8에 있는 스크립트는 논리적으로 조각화가 가장 심한 인덱스를 확인합니다. 결과는 조각화 비율을 기준으로 정렬되며 모든 데이터베이스에 걸쳐 논리적으로 조각화가 가장 심한 인덱스와 해당하는 데이터베이스/테이블을 보여 줍니다. 이 스크립트는 처음 실행하면 꽤 오래(몇 분) 걸릴 수 있으므로 스크립트 다운로드에는 주석으로 표시했습니다.
 Figure 8 논리적으로 조각화가 가장 심한 인덱스 확인 
 코드 복사
-- 필요한 테이블 구조만 작성합니다.
-- 참고: 이 SQL은 다음 단계에서 지정된 데이터베이스 루프 내에 있어야 합니다.
SELECT TOP 1 
        DatbaseName = DB_NAME()
        ,TableName = OBJECT_NAME(s.[object_id])
        ,IndexName = i.name
        ,[Fragmentation %] = ROUND(avg_fragmentation_in_percent,2)
INTO #TempFragmentation
FROM sys.dm_db_index_physical_stats(db_id(),null, null, null, null) s
INNER JOIN sys.indexes i ON s.[object_id] = i.[object_id] 
    AND s.index_id = i.index_id 
WHERE s.[object_id] = -999  -- 테이블 구조를 얻기 위한 임시 값
;

-- 서버의 모든 데이터베이스를 대상으로 반복합니다.
EXEC sp_MSForEachDB    'USE [?]; 
-- 테이블이 이미 있는 경우
INSERT INTO #TempFragmentation 
SELECT TOP 10
        DatbaseName = DB_NAME()
        ,TableName = OBJECT_NAME(s.[object_id])
        ,IndexName = i.name
        ,[Fragmentation %] = ROUND(avg_fragmentation_in_percent,2)
FROM sys.dm_db_index_physical_stats(db_id(),null, null, null, null) s
INNER JOIN sys.indexes i ON s.[object_id] = i.[object_id] 
    AND s.index_id = i.index_id 
WHERE s.database_id = DB_ID() 
      AND i.name IS NOT NULL    -- HEAP 인덱스 무시
    AND OBJECTPROPERTY(s.[object_id], ''IsMsShipped'') = 0
ORDER BY [Fragmentation %] DESC
;
'

-- 레코드 선택
SELECT TOP 10 * FROM #TempFragmentation ORDER BY [Fragmentation %] DESC
-- 임시 테이블 정리
DROP TABLE #TempFragmentation


 
그림 8 논리적으로 조각화가 가장 심한 인덱스 확인 (더 크게 보려면 이미지를 클릭하십시오.)


I/O 비용이 높은 쿼리
I/O는 쿼리가 수행하는 읽기/쓰기 횟수를 측정한 값입니다. 이 값은 쿼리의 효율을 알아보는 지표로 사용할 수 있습니다. I/O를 많이 사용하는 쿼리는 성능 개선 대상이 되는 경우가 많습니다.
sys.dm_exec_query_stats DMV는 실제 및 논리 읽기/쓰기와 쿼리 실행 횟수에 대한 세부 사항을 포함하여 캐시된 쿼리 계획에 대한 집계 성능 통계를 제공합니다. 여기에는 포함하는 부모 SQL에서 실제 SQL을 추출하는 데 사용되는 오프셋이 포함되어 있습니다. 이 DMV는 I/O가 연관된 SQL 일괄 처리에 대한 정보를 포함하는 sys.dm_exec_sql_text DMF에 조인됩니다. 이 일괄 처리에는 개별 기본 SQL 쿼리를 가져오기 위한 다양한 오프셋이 적용됩니다. 스크립트는 그림 9에서 볼 수 있습니다. 평균 I/O를 기준으로 정렬되는 이 결과에서는 평균 I/O, 전체 I/O, 개별 쿼리, 부모 쿼리(개별 쿼리가 일괄 처리의 일부인 경우) 및 데이터베이스 이름을 볼 수 있습니다.
 Figure 9 I/O 비용이 가장 높은 쿼리 확인 
 코드 복사
SELECT TOP 10 
 [Average IO] = (total_logical_reads + total_logical_writes) / qs.execution_count
,[Total IO] = (total_logical_reads + total_logical_writes)
,[Execution count] = qs.execution_count
,[Individual Query] = SUBSTRING (qt.text,qs.statement_start_offset/2, 
         (CASE WHEN qs.statement_end_offset = -1 
            THEN LEN(CONVERT(NVARCHAR(MAX), qt.text)) * 2 
          ELSE qs.statement_end_offset END - qs.statement_start_offset)/2) 
        ,[Parent Query] = qt.text
,DatabaseName = DB_NAME(qt.dbid)
FROM sys.dm_exec_query_stats qs
CROSS APPLY sys.dm_exec_sql_text(qs.sql_handle) as qt
ORDER BY [Average IO] DESC;


 
그림 9 I/O 비용이 가장 높은 쿼리 확인 (더 크게 보려면 이미지를 클릭하십시오.)
I/O는 데이터의 양을 반영하므로 Individual Query 열에 표시된 쿼리를 참조하여 I/O를 줄이거나 성능을 개선할 수 있는 위치를 찾을 수 있습니다. 데이터베이스 튜닝 관리자에 쿼리를 제출하여 쿼리 성능을 개선하기 위해 인덱스/통계를 추가해야 하는지 알아볼 수 있습니다. 통계에는 기본 데이터의 분포 및 밀도에 대한 세부 사항이 포함되며 이러한 정보는 쿼리 최적화 프로그램이 최적의 쿼리 액세스 계획을 결정하는 데 사용됩니다.
또한 이러한 쿼리의 테이블 및 누락된 인덱스 섹션에 나열된 인덱스 간에 연결이 있는지 확인하면 도움이 될 수 있습니다. 그러나 인덱스를 추가하면 기반 테이블 데이터를 업데이트하는 시간이 늘어나므로 업데이트를 자주 수행하는 테이블의 경우에는 인덱스를 만들기 전에 그에 따른 영향을 조사하는 것이 중요합니다.
읽기만 또는 쓰기만 보고하도록 스크립트를 수정할 수 있으며 이렇게 하면 각각 보고 데이터베이스 또는 트랜잭션 데이터베이스에 유용합니다. 합계 및 평균 값을 보고하고 적절하게 정렬하기를 원할 수도 있습니다. 읽기 값이 높다는 것은 인덱스가 누락되거나 완전하지 않거나 쿼리나 테이블이 잘못 설계되었음을 의미할 수 있습니다.
sys.dm_exec_query_stats DMV를 사용하는 결과를 해석할 때는 약간의 주의가 필요합니다. 예를 들어 쿼리 계획은 언제든지 프로시저 캐시에서 제거될 수 있으며 모든 쿼리가 캐시되는 것은 아닙니다. 이러한 사항이 결과에 영향을 주지만 그래도 결과는 가장 비용이 높은 쿼리를 나타냅니다.


CPU 비용이 높은 쿼리
CPU 사용 측면에서 가장 비용이 높은 쿼리를 분석하는 방법도 유용합니다. 이 방법으로 실행 효율이 떨어지는 쿼리를 발견할 수 있습니다. 여기에서 사용할 DMV는 I/O 비용이 높은 쿼리를 살펴볼 때 사용한 것과 동일합니다. 그림 10에 있는 쿼리를 사용하면 CPU 사용 비용이 가장 높은 쿼리를 확인할 수 있습니다.
 Figure 10 CPU 비용이 가장 높은 쿼리 확인 
 코드 복사
SELECT TOP 10 
 [Average CPU used] = total_worker_time / qs.execution_count
,[Total CPU used] = total_worker_time
,[Execution count] = qs.execution_count
,[Individual Query] = SUBSTRING (qt.text,qs.statement_start_offset/2, 
         (CASE WHEN qs.statement_end_offset = -1 
            THEN LEN(CONVERT(NVARCHAR(MAX), qt.text)) * 2 
          ELSE qs.statement_end_offset END - 
qs.statement_start_offset)/2)
,[Parent Query] = qt.text
,DatabaseName = DB_NAME(qt.dbid)
FROM sys.dm_exec_query_stats qs
CROSS APPLY sys.dm_exec_sql_text(qs.sql_handle) as qt
ORDER BY [Average CPU used] DESC;


 
그림 10 CPU 비용이 가장 높은 쿼리 확인 (더 크게 보려면 이미지를 클릭하십시오.)
이 쿼리는 평균 CPU 사용, 전체 CPU 사용, 개별 쿼리 및 부모 쿼리(개별 쿼리가 일괄 처리의 일부인 경우), 그리고 해당 데이터베이스 이름을 보여 줍니다. 또한, 앞에서 언급한 것처럼 데이터베이스 튜닝 관리자로 쿼리에 대해 더 조사하여 추가 개선이 가능한지 확인하는 것이 좋을 수 있습니다.


비용이 높은 CLR 쿼리
SQL Server에서 CLR 활용이 점차 증가하고 있습니다. 따라서 저장 프로시저, 함수 및 트리거를 포함하여 CLR을 가장 많이 사용하는 쿼리를 확인하는 것이 유용할 수 있습니다.
sys.dm_exec_query_stats DMV에는 total_clr_time에 대한 세부 사항과 쿼리가 실행된 횟수가 포함되어 있습니다. 여기에는 포함하는 부모 쿼리에서 실제 쿼리를 추출하는 데 사용되는 오프셋도 포함되어 있습니다. DMV는 SQL 일괄 처리에 대한 정보를 포함하는 sys.dm_exec_sql_text DMF에 조인됩니다. 기본 SQL 쿼리를 가져오기 위한 다양한 오프셋이 적용됩니다. 그림 11에서는 가장 비용이 높은 CLR 쿼리를 확인하는 쿼리를 볼 수 있습니다.
 Figure 11 가장 비용이 높은 CLR 쿼리 확인 
 코드 복사
SELECT TOP 10 
 [Average CLR Time] = total_clr_time / execution_count 
,[Total CLR Time] = total_clr_time 
,[Execution count] = qs.execution_count
,[Individual Query] = SUBSTRING (qt.text,qs.statement_start_offset/2, 
         (CASE WHEN qs.statement_end_offset = -1 
            THEN LEN(CONVERT(NVARCHAR(MAX), qt.text)) * 2 
          ELSE qs.statement_end_offset END - qs.statement_start_offset)/2)
,[Parent Query] = qt.text
,DatabaseName = DB_NAME(qt.dbid)
FROM sys.dm_exec_query_stats as qs
CROSS APPLY sys.dm_exec_sql_text(qs.sql_handle) as qt
WHERE total_clr_time <> 0
ORDER BY [Average CLR Time] DESC;


 
그림 11 가장 비용이 높은 CLR 쿼리 확인 (더 크게 보려면 이미지를 클릭하십시오.)
이 쿼리를 실행하면 평균 CLR 시간, 전체 CLR 시간, 실행 횟수, 개별 쿼리, 부모 쿼리 및 데이터베이스 이름이 반환됩니다. 이번에도 역시 데이터베이스 튜닝 관리자로 쿼리에 대해 더 조사하여 추가 개선이 가능한지 확인하는 것이 좋을 수 있습니다.


가장 많이 실행된 쿼리
앞에서 살펴본 비용이 높은 CLR 쿼리를 찾는 샘플을 수정하여 가장 많이 실행된 쿼리를 알아낼 수 있습니다. 여기에는 동일한 DMV가 적용됩니다. 자주 실행되는 쿼리의 성능을 개선하면 가끔 실행되는 큰 쿼리를 최적화하는 것보다 더 많은 성능 개선 효과를 거둘 수 있습니다. 누적 CPU 또는 I/O를 가장 많이 사용하는 쿼리와 대조하여 정상 작동 여부를 확인할 수 있습니다. 자주 실행되는 쿼리를 개선함으로써 얻을 수 있는 다른 장점은 잠금 횟수와 트랜잭션 길이를 줄일 수 있는 기회가 있다는 것입니다. 결과적으로는 전체 시스템의 응답성이 향상됩니다.
그림 12에 있는 쿼리를 사용하여 가장 자주 실행되는 쿼리를 확인할 수 있습니다. 이 쿼리를 실행하면 실행 횟수, 개별 쿼리, 부모 쿼리(개별 쿼리가 일괄 처리의 일부인 경우) 및 관련된 데이터베이스를 볼 수 있습니다. 이번에도 역시 데이터베이스 튜닝 관리자로 쿼리에 대해 더 조사하여 추가 개선이 가능한지 확인하는 것이 좋을 수 있습니다.
 Figure 12 가장 자주 실행되는 쿼리 확인 
 코드 복사
SELECT TOP 10 
 [Execution count] = execution_count
,[Individual Query] = SUBSTRING (qt.text,qs.statement_start_offset/2, 
         (CASE WHEN qs.statement_end_offset = -1 
            THEN LEN(CONVERT(NVARCHAR(MAX), qt.text)) * 2 
          ELSE qs.statement_end_offset END - qs.statement_start_offset)/2)
,[Parent Query] = qt.text
,DatabaseName = DB_NAME(qt.dbid)
FROM sys.dm_exec_query_stats qs
CROSS APPLY sys.dm_exec_sql_text(qs.sql_handle) as qt
ORDER BY [Execution count] DESC;


 
그림 12 가장 자주 실행되는 쿼리 확인 (더 크게 보려면 이미지를 클릭하십시오.)


차단당하는 쿼리
가장 많이 차단당하는 쿼리는 일반적으로 오래 실행되는 쿼리가 됩니다. 이러한 쿼리를 확인한 다음에는 차단을 줄이도록 쿼리를 다시 작성해야 하는지, 그리고 이것이 가능한지 확인해야 합니다. 차단의 원인으로는 일관적이지 않은 순서의 개체 사용, 트랜잭션 범위 충돌, 사용되지 않는 인덱스 업데이트 등이 있습니다.
앞에서 설명한 sys.dm_exec_query_stats DMV에는 가장 많이 차단당하는 쿼리를 확인하는 데 사용할 수 있는 열이 포함되어 있습니다. 평균 차단 시간은 total_elaspsed_time 및 total_worker_time 간의 차이를 execution_count로 나누어 계산할 수 있습니다.
sys.dm_exec_sql_text DMF에는 차단과 연관된 SQL 일괄 처리에 대한 세부 사항이 포함되어 있습니다. 여기에는 기본 SQL 쿼리를 가져오기 위한 다양한 오프셋이 적용됩니다.
그림 13에 있는 쿼리를 사용하면 가장 많이 차단당하는 쿼리를 확인할 수 있습니다. 결과에서 평균 차단 시간, 전체 차단 시간, 실행 횟수, 개별 쿼리, 부모 쿼리 및 관련 데이터베이스 이름을 볼 수 있습니다. 이러한 결과는 Average Time Blocked를 기준으로 정렬되지만 Total Time Blocked로 정렬하는 것도 유용합니다.
 Figure 13 가장 자주 차단당하는 쿼리 확인 
 코드 복사
SELECT TOP 10 
 [Average Time Blocked] = (total_elapsed_time - total_worker_time) / qs.execution_count
,[Total Time Blocked] = total_elapsed_time - total_worker_time 
,[Execution count] = qs.execution_count
,[Individual Query] = SUBSTRING (qt.text,qs.statement_start_offset/2, 
         (CASE WHEN qs.statement_end_offset = -1 
            THEN LEN(CONVERT(NVARCHAR(MAX), qt.text)) * 2 
          ELSE qs.statement_end_offset END - qs.statement_start_offset)/2) 
,[Parent Query] = qt.text
,DatabaseName = DB_NAME(qt.dbid)
FROM sys.dm_exec_query_stats qs
CROSS APPLY sys.dm_exec_sql_text(qs.sql_handle) as qt
ORDER BY [Average Time Blocked] DESC;


 
그림 13 가장 자주 차단당하는 쿼리 확인 (더 크게 보려면 이미지를 클릭하십시오.)
쿼리를 살펴보면 디자인 문제(누락된 인덱스), 트랜잭션 문제(정렬되지 않은 리소스 사용) 등과 같은 문제를 발견할 수 있습니다. 데이터베이스 튜닝 관리자를 사용하여 가능한 개선 방법을 찾을 수 있습니다.


가장 적게 재사용되는 계획
저장 프로시저를 사용할 때의 장점 중 하나는 쿼리 계획을 캐시하여 쿼리를 컴파일하지 않고 재사용할 수 있다는 것입니다. 이렇게 하면 시간과 리소스가 절약되고 성능이 향상됩니다. 가장 적게 재사용되는 쿼리 계획을 확인한 다음에는 계획이 재사용되지 않는 이유를 더 조사할 수 있습니다. 재사용을 최적화하도록 일부 쿼리를 다시 작성할 수도 있습니다.
그림 14에서는 계획이 가장 적게 재사용되는 쿼리를 확인하기 위해 필자가 작성한 스크립트를 보여 줍니다. 여기에서는 이미 설명한 DMV와 함께 아직 설명하지 않은 dm_exec_cached_plans를 사용합니다. 이 DMV에는 SQL Server가 캐시한 쿼리 계획에 대한 세부 사항도 포함되어 있습니다. 여기에서 볼 수 있듯이 결과에서는 계획이 사용된 횟수(Plan usage 열), 개별 쿼리, 부모 쿼리 및 데이터베이스 이름을 볼 수 있습니다.
 Figure 14 계획이 가장 적게 재사용되는 쿼리 확인 
 코드 복사
SELECT TOP 10
 [Plan usage] = cp.usecounts
,[Individual Query] = SUBSTRING (qt.text,qs.statement_start_offset/2, 
         (CASE WHEN qs.statement_end_offset = -1 
            THEN LEN(CONVERT(NVARCHAR(MAX), 
qt.text)) * 2 ELSE qs.statement_end_offset END - 
qs.statement_start_offset)/2)
,[Parent Query] = qt.text
,DatabaseName = DB_NAME(qt.dbid)
,cp.cacheobjtype
FROM sys.dm_exec_query_stats qs
CROSS APPLY sys.dm_exec_sql_text(qs.sql_handle) AS qt
INNER JOIN sys.dm_exec_cached_plans as cp on qs.plan_handle=cp.plan_handle
WHERE cp.plan_handle=qs.plan_handle
ORDER BY [Plan usage] ASC;


 
그림 14 계획이 가장 적게 재사용되는 쿼리 확인 (더 크게 보려면 이미지를 클릭하십시오.)
이제 개별 쿼리를 조사하여 쿼리 재사용 빈도가 떨어지는 이유를 확인할 수 있습니다. 한 가지 가능한 이유는 쿼리가 실행될 때마다 다시 컴파일되는 것입니다. 쿼리에 다양한 SET 문이나 임시 테이블이 포함되면 이런 현상이 나타날 수 있습니다. 다시 컴파일 및 계획 캐싱에 대한 자세한 설명은 "SQL Server 2005의 일괄 컴파일, 다시 컴파일 및 계획 캐싱 문제"(microsoft.com/technet/prodtechnol/sql/2005/recomp.mspx)를 참조하십시오.
쿼리가 여러 번 실행되는 충분한 기회가 있었는지도 확인해야 합니다. 연관된 SQL 추적 파일에서 이를 확인할 수 있습니다.


추가 작업
다양한 DMV를 통해 공개되는 메트릭은 영구적으로 저장되지 않으며 메모리에만 유지됩니다. SQL Server 2005가 다시 시작하면 이러한 메트릭은 삭제됩니다.
DMV의 출력을 바탕으로 정기적으로 테이블을 만들고 타임스탬프와 함께 결과를 저장할 수 있습니다. 그런 다음 타임스탬프 순서대로 이러한 결과를 조사하여 응용 프로그램의 변경이나 선택한 작업 또는 시간 기반 처리의 영향을 알아볼 수 있습니다. 예를 들어 월말에 실행하는 프로시저가 어떤 영향을 주는지 확인할 수 있습니다.
비슷하게 선택한 추적 파일 작업량을 이러한 테이블의 변경과 연관지어 선택한 작업량에 대한 누락된 인덱스, 가장 많이 사용된 쿼리 등의 영향을 확인할 수 있습니다. 이러한 테이블을 만들기 위해 필자가 추가한 스크립트를 편집하여 지속적인 유지 관리 작업의 일부로 주기적으로 실행할 수 있습니다.
이 기사에서 앞서 설명한 스크립트를 Visual Studio 2005에서 사용하여 사용자 지정 보고서를 만드는 것도 가능합니다. 이러한 보고서를 SQL Server Management Studio에 통합하면 데이터를 더 보기 편하게 표시할 수 있습니다.
필자가 설명한 방법을 추적이나 비율 분석과 같은 다른 방법과 통합을 시도해 보십시오. 이를 통해 데이터베이스 성능을 향상시키기 위해 필요한 변경에 대한 더 완전한 시야를 얻을 수 있습니다.
이 기사에서는 SQL Server 2005가 일반적인 작업을 수행하면서 축적하는 풍부한 정보의 유용함에 대해 살펴보았습니다. 쿼리 성능을 개선하려는 지속적인 노력에 이러한 정보를 유용하게 활용할 수 있습니다. 예를 들어 서버 대기의 원인을 발견하고, 성능에 부정적인 영향을 주는 사용되지 않는 인덱스를 찾으며, 가장 자주 사용되는 쿼리와 가장 비용이 높은 쿼리를 확인할 수 있습니다. 숨겨진 데이터를 살펴보기 시작한다면 그 가능성은 무궁무진합니다. DMV에 대해서는 아직도 배울 것이 많으므로 이 기사를 더 세부적인 부분을 살펴보는 계기로 삼기를 바랍니다. 
:
Posted by Elick
2009. 1. 15. 10:21

MSSQL Replication (복제) 개요만... Work/SQL Server2009. 1. 15. 10:21

[개념]
한 Server에 있는 Data를 다른 Server로 배포하는데 사용.
Backup / Recovery 나 Data Export / Import의 한번의 복사로 불충분한 경우, 즉 아래 처럼 좀 더 다른 이유가 있는 경우 사용.
중앙 DB의 변경 내용을 원격 DB와 일치시키기 위해.
업무 부하를 분산할 여러 개의 DB Instance를 만들기 위해.
중앙 Server 의 특정 Data set을 여러 개의 다른 Server로 배포하고자 할 때.
Data를 customize하고 다수의 가입자에게 배포하기 위해.

[대상]
복제는 게시자, 배포자, 구독자라는 개념이 필요함. (pass)
복제의 내용은 table, table의 특정 열 / 특정 행, 특정 열과 특정 행으로 이루어진 table의 부분 집합, view, index, user define function, stored procedure 을 포함할 수 있다.
복제할 수 없는 내용으로는 model, tempdb, msdb, master 와 같은 DB의 객체는 복제할 수 없다.

[유형]
Snapshot : 장점은 정확한 복사. 단점은 업무 부하 및 Network tranffic 증가, 정해진 주기로만 동기화.
Transaction : 장점은 비교적 정확한 복사와 융통성있는 동기화. 분산 Transaction 사용.
Merge : 분산 Transaction을 사용하지 않기 때문에 Transaction의 일관성을 보장하지 않는다.

[Model]
Peer to peer : Peer to peer topology에 참여하는 DB간의 복제.
Central publisher : 동일한 Server에 게시자와 배포자 DB를 유지하고 다른 Server에 구성되는 하나 이상의 구독자를 가진다.
이 Model이 일반적으로 사용된다. 게시자 Server에 추가적인 부하가 발생하여 Server 성능에 영향을 미친다. 
Server의 부하를 줄이기 위해 배포자를 별도의 Server에 설치할 수 있지만 게시자 Server의 부하를 완전히 해소하지 못한다.
배포자와 게시자는 통신이 필요하고 Data를 서로 주고 받아야 하기 때문이다.

p.s. 이번에 새로 추가되는 Service에 복제를 사용하는 것을 고민했는데... 여기에서 실제 적용은 좀 더 지켜본 후 결정해야 겠다는 생각이 ... 게시자가와 배포자로 사용될 Server가 아무래도 Service용이기 때문에 성능에 악영향을 주는 요소를 추가하는 것이 마음에 걸린다.
:
Posted by Elick
2009. 1. 14. 15:03

MSSQL Hint 사용 Work/SQL Server2009. 1. 14. 15:03

온라인 설명서나 학습서 등 여러 문서와 웹사이트에서 MSSQL의 Hint의 사용을 권장하지 않습니다.
그래서 옵티마이저의 선택을 믿을 수 있게 환경설정을 해 주는 것을 최우선으로 설명하고 있습니다.
저도 기본적으로 Hint를 사용하는 것을 권장하고 싶지는 않지만, 사용법을 알고 하지 않는 것과 몰라서 못하는 것은 차이가 많기 때문에 사용법에 대한 조사를 해본 적이 있습니다.
바라지는 않지만, 언젠가 사용해야만 하는 상황이 온다면 빠른 시간내에 일을 처리하고 퇴근을 해야되기 때문입니다.
--------------------------------------------------------------------------------------
MSSQL
Join Hint
두 Table 간의 Join 전략을 강제 적용하도록 지정.
표기예 
SELECT Col1, Col2
FROM TAB1 INNER LOOP JOIN TAB2 
ON TAB1.Col3 = TAB2.Col3

LOOP
Join을 Nasted Loop으로 실행
HASH
Join을 Hash로 실행
MERGE 
Join을 Merge로 실행
REMOTE 
오른쪽 Table에서 Join 작업을 수행하도록 지정.
왼쪽 Table이 Local Table이고 오른쪽 Table이 원격 Table인 경우에 유용.
INNER JOIN 작업에 대해서만 사용가능.
Query Hint
OPTION 절의 일부로 지정.
표기예 
SELECT Col1, Col2
FROM TAB1 INNER JOIN TAB2 
ON TAB1.Col3 = TAB2.Col3
OPTION (MERGE JOIN)

{ HASH | ORDER } GROUP 
Query의 GROUP BY, DISTINCT 또는 COMPUTE 절에 지정된 집계에서 Hash나 정렬을 사용하도록 지정.
{ MERGE | HASH | CONCAT } UNION 
UNION 집합을 병합, Hash 또는 연결하여 모든 UNION 연산을 수행하도록 지정. 
둘 이상의 UNION Hint를 지정한 경우 Query 최적화 프로그램에서는 지정한 Hint 중 가장 부담이 적은 전략을 선택.
참고:  
FROM 절에서 Join된 특정 Table 쌍에 대해 <joint_hint>를 지정한 경우 OPTION 절에 지정한 <join_hint>보다 우선적으로 적용.
{ LOOP | MERGE | HASH } JOIN
전체 Query에서 모든 Join 연산이 LOOP JOIN, MERGE JOIN 또는 HASH JOIN에 의해 수행되도록 지정.
둘 이상의 Join Hint를 지정한 경우 최적화 프로그램에서는 허용되는 Hint 중 가장 부담이 적은 Join 방법을 선택.
표기예 
USE AdventureWorks;
GO
SELECT * 
FROM Sales.Customer AS c INNER JOIN Sales.CustomerAddress AS ca
ON c.CustomerID = ca.CustomerID
WHERE TerritoryID = 5
OPTION (MERGE JOIN);
FAST number_rows
첫 번째 음수가 아닌 정수 number_rows.를 빨리 검색하기 위해 Query를 최적화하도록 지정. 
FORCE ORDER 
Query 구문에 지정된 Join 순서가 Query 최적화 시 유지되도록 지정.
MAXDOP number
SQL의 병렬처리를 유도.
이 Option을 지정한 Query에 대해서만 sp_configure의 max degree of parallelism 구성 Option을 무시.
OPTIMIZE FOR
Query가 Compile되고 최적화될 때 Query 최적화 프로그램이 Local 변수에 대해 특정 값을 사용하도록 지시.
표기예 
DECLARE @city_name nvarchar(30)
SET @city_name = 'Ascheim'
SELECT * FROM Person.Address
WHERE City = @city_name
OPTION ( OPTIMIZE FOR (@city_name = 'Seattle') )
PARAMETERIZATION { SIMPLE | FORCED }
SQL Server Query 최적화 프로그램에서 Query Compile 시 적용하는 매개 변수화 규칙을 지정합. 
PARAMETERIZATION Query Hint는 계획 지침 내에서만 지정. Query 내에서 직접 지정할 수는 없다.
SIMPLE은 Query 최적화 프로그램이 단순 매개 변수화를 시도하도록 지시. 
FORCED는 최적화 프로그램이 강제 매개 변수화를 시도하도록 지시. 
PARAMETERIZATION Query Hint는 계획 지침 내에서 PARAMETERIZATION 데이터베이스 SET Option의 현재 설정을 무시하는 데 사용.
RECOMPILE
Query를 실행한 후 SQL Server 2005 데이터베이스 엔진 에서 해당 Query에 대해 생성된 계획을 삭제.
다음에 같은 Query가 실행될 때 Query 최적화 프로그램이 Query 계획을 다시 Compile하도록 지시. 
ROBUST PLAN 
Query 최적화 프로그램에서 성능이 저하되더라도 최대 잠재적 행 크기를 정의할 수 있는 계획을 유도. 
KEEP PLAN 
Query 최적화 프로그램에서 Query에 대한 예상 다시 Compile 임계값을 완화. 
Table이 자주 Update되는 경우 KEEP PLAN을 지정하면 Query가 지나치게 자주 다시 Compile되지 않아 유용.
KEEPFIXED PLAN 
통계 변경 시에는 최적화 프로그램이 Query를 다시 Compile하지 않도록 한다. 
원본으로 사용하는 Table의 스키마가 바뀌거나 Table에 대해 sp_recompile이 실행되는 경우에만 Query를 다시 Compile.
EXPAND VIEWS 
Indexed View를 확장하고 Query 최적화 프로그램에서 Indexed View를 Query 일부를 대체하는 것으로 간주하지 않도록 지정.
Query 텍스트에 있는 View 정의에 의해 View 이름이 바뀌면 View가 확장. 
MAXRECURSION number
해당 Query에 대해 허용되는 최대 재귀 횟수를 지정. 
number는 0에서 32767 사이의 음수가 아닌 정수. 0을 지정하면 한도가 적용되지 않는다.
이 Option을 지정하지 않은 경우 서버에 대한 기본 한도는 100. 
Query 실행 중에 MAXRECURSION 한도로 지정된 횟수 또는 기본 횟수에 도달하면 Query가 종료되고 오류가 반환.
이 오류로 인해 문의 모든 결과가 Rollback. 문이 SELECT 문인 경우 결과의 일부가 반환되거나 아무런 결과도 반환되지 않을 수 있음. 
반환된 일부 결과에는 지정한 최대 재귀 수준을 초과한 재귀 수준의 모든 행이 포함되지 않을 수 있음. 
USE PLAN N'xml_plan'
Query 최적화 프로그램이 'xml_plan'에 의해 지정된 Query에 대해 기존의 Query 계획을 사용.

Table Hint
주로 FROM 절에 with 와 함께 사용되는 Hint를 말합니다.
한번에 두개 이상의 Hint를 허용하지 않는 경우도 존재합니다.
표기예
SELECT Col1, Col2
FROM TAB1 with (nolock) INNER JOIN TAB2 with (nolock)
ON TAB1.Col3 = TAB2.Col3
NOEXPAND
Indexed View에 대한 Index를 강제로 사용하게 하려면 NOEXPAND Option을 지정.
INDEX ( index_val [ ,...n ] )
Query 최적화 프로그램이 문을 처리할 때 사용할 Index의 이름이나 ID를 지정. 
Table당 하나의 Index Hint만 지정.
FASTFIRSTROW
OPTION(FAST 1)과 동일. (Query Hint > FAST number_rows 참고.)
HOLDLOCK
SERIALIZABLE과 동일.
NOLOCK
READUNCOMMITTED와 동일.
NOWAIT
Table에 잠금이 있으면 SQL Server 2005 데이터베이스 엔진 에서 바로 메시지를 반환하도록 지정.
NOWAIT는 특정 Table에 SET LOCK_TIMEOUT 0을 지정하는 것과 동일.
PAGLOCK
일반적으로 행 또는 Key에 개별 잠금이 사용되거나 일반적으로 단일 Table 잠금이 사용되는 곳에서 Page 잠금을 사용.
기본적으로 작업에 적합한 잠금 모드를 사용.
READCOMMITTED
읽기 작업이 잠금 또는 행 버전 관리를 사용하여 READ COMMITTED 격리 수준에 대한 규칙을 따르도록 지정.
READCOMMITTEDLOCK
읽기 작업이 잠금을 사용하여 READ COMMITTED 격리 수준에 대한 규칙을 따르도록 지정.
READPAST
Data베이스 엔진 이 다른 Transaction에 의해 잠긴 행 및 Page를 읽지 않도록 지정.
READUNCOMMITTED
Commit되지 않은 Data 읽기를 허용하도록 지정.
REPEATABLEREAD
REPEATABLE READ 격리 수준에서 실행되는 Transaction과 동일한 잠금 기능으로 검색이 수행되도록 지정합.
ROWLOCK
Page 또는 Table 잠금이 일반적으로 사용될 때 행 잠금을 사용하도록 지정.
SERIALIZABLE
HOLDLOCK과 동일. 
필요한 Table 또는 Data Page가 더 이상 필요 없을 때 Transaction의 완료 여부과 관계없이 즉시 공유 잠금을 해제하지 않고 Transaction 완료 시까지 유지.
TABLOCK
Table에 사용되는 공유 잠금이 문이 끝날 때까지 유지되도록 지정.
TABLOCKX
Table에 배타적 잠금을 사용하도록 지정.
UPDLOCK
Update 잠금을 사용하고 Transaction이 완료될 때까지 유지하도록 지정.
XLOCK
배타적 잠금을 사용하고 Transaction이 완료될 때까지 유지하도록 지정.
참고 
HOLDLOCK 부터 XLOCK까지는 Table 격리 수준에 대한 것으로 아래의 SET 절로도 지정할 수 있다.
SET TRANSACTION ISOLATION LEVEL
   { READ UNCOMMITTED
   | READ COMMITTED
   | REPEATABLE READ
   | SNAPSHOT
   | SERIALIZABLE
   }
[ ; ]
KEEPIDENTITY
OPENROWSET에 BULK Option을 사용하는 경우 INSERT 문에만 적용.
가져온 Data 파일의 ID 값이 ID 열에 사용되도록 지정.
표기예 1
USE AdventureWorks;
GO
BULK INSERT MyTestDefaultCol2
  FROM 'C:\MyTestEmptyField2-c.Dat'
  with (
     DATAFILETYPE = 'char',
     FIELDTERMINATOR = ',',
     KEEPNULLS -- KEEPIDENTITY, KEEPDEFAULTS
  );
GO
표기예 2
INSERT INTO MyTestDefaultCol2
 --with (KEEPDEFAULTS)
   SELECT *
     FROM OPENROWSET(BULK  'C:\MyTestEmptyField2-c.Dat',
     FORMATFILE='C:\MyTestDefaultCol2-f-c.Fmt'     
     ) as t1 ;
GO
KEEPDEFAULTS 
OPENROWSET에 BULK Option을 사용하는 경우 INSERT 문에만 적용.
Data 레코드에 열의 값이 없는 경우 NULL 대신 Table 열의 기본값(있는 경우)을 삽입하도록 지정.
표기예
KEEPIDENTITY 의 표기예와 동일
IGNORE_CONSTRAINTS 
OPENROWSET에 BULK Option을 사용하는 경우 INSERT 문에만 적용.
대량 가져오기 작업에서 Table의 모든 제약 조건을 무시하도록 지정.
IGNORE_TRIGGERS 
OPENROWSET에 BULK Option을 사용하는 경우 INSERT 문에만 적용.
대량 가져오기 작업에서 Table에 정의된 모든 Trigger를 무시하도록 지정.
:
Posted by Elick
2009. 1. 14. 14:51

MSSQL Release 정보 Work/SQL Server2009. 1. 14. 14:51

언젠가 이거 찾느라고 한동안 해맸던 기억이... ㅠㅠ
------------------------------------------------------

. SQL Server Release 확인
SQL Server 2005 Release
RTM 90.1399 
SP1 90.2047 
SP2 90.3042 
SQL Server 2000 Release 
RTM 80.194.0 
SP1 80.384.0 
SP2 80.534.0 
SP3 80.760.0 
SP3a 80.760.0 
SP4 8.00.2039 (Release 종료)

SQL Server 7.0
SP4 7.00.1063 (Release 종료)

SQL Server 6.5
SP5a 6.50.416 (Release 종료)

SQL Server 6.0
SP3 6.00.151 (Release 종료)



. Release information
:
Posted by Elick
2009. 1. 14. 14:47

MSSQL Oracle 분산트랜잭션 Work/Database General2009. 1. 14. 14:47

한동안 고민했었던 것인데... 아래처럼 하면 된다.
그런데... MSSQL에서 이렇게 연결한 Oracle의 package를 호출하는 방법을 누가 좀 알려줬으면 좋겠다..


Distribute transaction for MSSQL-ORACLE.

- 연결된 두 서버의 네트워크 주소가 C 클래스까지 일치해야 한다고 한다. 
   이 문제에 대한 공식 문서를 찾지 못했지만, 실제로 테스트해본 결과로는 믿을만한 정보다. 
   몇몇 증언들을 보면 C클래스까지 일치하지 않으면 분산트랜잭션이 실패한다고 한다.
--> 이것은 정확하지 않다. 컴퓨터 이름으로 ping이 갈 수 있으면 이 조건은 무시해도 될 것 같다.

 - Windows 구성요소 중 네트워크 COM+ 엑세스 사용, 네트워크 DTC 엑세스 사용이 모두 설치되어 있어야 한다.

 - 고급 TCP/IP - WINS의 NetBIOS 설정은 기본값으로 설정되어 있어야 한다.

 - hosts, Imhosts 파일에 서버IP 서버 HOST명를 맵핑. (동일 네트웍에서 Ping 서버 HOST명 이 이상없어야 한다)

 - 인터넷 연결 방화벽 설정도 해지되어 있어야 한다. (135 port 및 MSDTC관련 App를 예외로 등록한다)

 - DTCPing Tool을 이용한 MSDTC 정상 동작 확인해야 한다..(135 port 확인)

 - 레지스트리 에서 HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\MSDTC에 TurnOffRpcSecurity 데이터 값은 1로 설정되어있어야 한다.

 - 관리도구>구성요소서비스 >내 컴퓨터> 속성> 보안구성도 정상적으로 설정되어 있습니다.
: 네트워크 DTC 액세스, 인바운드 허용, 아웃바운드 허용, 인증필요 없음 이 체크되어 있는지 확인할 것.
 - netstat -na 결과 (TCP    0.0.0.0:135    0.0.0.0:0    LISTENING - 순서대로)이면 135번 포트는 열려있어야한다.

 - MSSQL Server 보다 MSDTC가 먼저 시작되어야 한다. (설정이 변경되면 당연히 재시작한다.)

 - 아래처럼 SET XACT_ABORT ON 를 사용한다.

 SET XACT_ABORT ON
 BEGIN DISTRIBUTED TRANSACTION
   INSERT INTO T_TEST VALUES (2);
   INSERT INTO ORADEV9..DEVDBA.T_TEST VALUES (2);
 COMMIT TRANSACTION

Reference. 
after reference. 

:
Posted by Elick