티스토리 툴바


몇가지 SET 명령어

MSSQL 2012/03/07 09:56


* SET NOCOUNT 
  - Transact-SQL 문의 영향을 받은 행 수를 나타내는 메시지가 결과의 일부로 반환되지 않도록 한다.

* SET STATISTICS IO 
  - Microsoft® SQL Server™가 Transact-SQL 문이 생성한 디스크 작동 양에 대한 정보를 표시한다.

* SET STATISTICS PROFILE 
  - 명령문에 대한 프로필 정보를 표시한다. STATISTICS PROFILE은 임의 쿼리, 뷰, 트리거 및 저장 프로시저에서 작동한다.
   Rows:각 연산자에서 만든 실제 행 수
   Exucutes : 연산자가 실행된 횟수

* SET STATISTICS TIME 
  - 각 명령문을 구문 분석, 컴파일 및 실행하는 데 필요한 시간을 밀리초 단위로 표시한다.

크리에이티브 커먼즈 라이선스
Creative Commons License


 

Posted by 진실세상


[출처] http://msdn.microsoft.com/ko-kr/library/ms345522.aspx

지정된 데이터베이스에 클러스터형 인덱스가 없는 테이블을 모두 찾으려면 어떻게 해야 합니까?

다음 쿼리를 실행하기 전에 <database_name>을 올바른 데이터베이스 이름으로 대체합니다.

USE AdventureWorks2008;
GO
SELECT SCHEMA_NAME(t.schema_id) AS schema_name, t.name AS table_name
FROM sys.tables AS t
WHERE NOT EXISTS 
   (
     SELECT * FROM sys.indexes AS i
     WHERE i.object_id = t.object_id
     AND i.type = 1  -- or type_desc = 'CLUSTERED'
   )
ORDER BY schema_name, table_name;
GO


또는 다음 예와 같이 OBJECTPROPERTY 함수를 사용할 수 있습니다.

USE <database_name>;
GO
SELECT SCHEMA_NAME(schema_id) AS schema_name, name AS table_name
FROM sys.tables 
WHERE OBJECTPROPERTY(object_id,'TableHasClustIndex') = 0
ORDER BY schema_id, name;
GO


지정된 스키마에 속하는 엔터티 소유자를 모두 찾으려면 어떻게 해야 합니까?

다음 쿼리를 실행하기 전에 <database_name>  <schema_name>을 올바른 이름으로 대체합니다.

USE <database_name>;
GO
SELECT 'OBJECT' AS entity_type
    ,USER_NAME(OBJECTPROPERTY(object_id, 'OwnerId')) AS owner_name
    ,name 
FROM sys.objects WHERE SCHEMA_NAME(schema_id) = '<schema_name>'
UNION 
SELECT 'TYPE' AS entity_type
    ,USER_NAME(TYPEPROPERTY(SCHEMA_NAME(schema_id) + '.' + name, 'OwnerId')) AS owner_name
    ,name 
FROM sys.types WHERE SCHEMA_NAME(schema_id) = '<schema_name>' 
UNION
SELECT 'XML SCHEMA COLLECTION' AS entity_type 
    ,COALESCE(USER_NAME(xsc.principal_id),USER_NAME(s.principal_id)) AS owner_name
    ,xsc.name 
FROM sys.xml_schema_collections AS xsc JOIN sys.schemas AS s
    ON s.schema_id = xsc.schema_id
WHERE s.name = '<schema_name>';
GO


기본 키가 없는 테이블을 모두 찾으려면 어떻게 해야 합니까?

다음 쿼리를 실행하기 전에 <database_name>을 올바른 데이터베이스 이름으로 대체합니다.

USE <database_name>;
GO
SELECT SCHEMA_NAME(t.schema_id) AS schema_name
    ,t.name AS table_name
FROM sys.tables t 
WHERE object_id NOT IN 
   (
    SELECT parent_object_id 
    FROM sys.key_constraints 
    WHERE type_desc = 'PRIMARY_KEY_CONSTRAINT' -- or type = 'PK'
    );
GO


또는 다음 쿼리를 실행할 수 있습니다.

USE <database_name>;
GO
SELECT SCHEMA_NAME(schema_id) AS schema_name
    ,name AS table_name 
FROM sys.tables 
WHERE OBJECTPROPERTY(object_id,'TableHasPrimaryKey') = 0
ORDER BY schema_name, table_name;
GO


인덱스가 없는 테이블을 모두 찾으려면 어떻게 해야 합니까?

다음 쿼리를 실행하기 전에 <database_name>을 올바른 데이터베이스 이름으로 대체합니다.

USE <database_name>;
GO
SELECT SCHEMA_NAME(schema_id) AS schema_name
    ,name AS table_name
FROM sys.tables 
WHERE OBJECTPROPERTY(object_id,'IsIndexed') = 0
ORDER BY schema_name, table_name;
GO


ID 열이 있는 테이블을 모두 찾으려면 어떻게 해야 합니까?

다음 쿼리를 실행하기 전에 <database_name>을 올바른 데이터베이스 이름으로 대체합니다.

USE <database_name>;
GO
SELECT SCHEMA_NAME(schema_id) AS schema_name
    , t.name AS table_name
    , c.name AS column_name
FROM sys.tables AS t
JOIN sys.identity_columns c ON t.object_id = c.object_id
ORDER BY schema_name, table_name;
GO


또는 다음 쿼리를 실행할 수 있습니다.

참고 참고

이 쿼리는 열 이름을 반환하지 않습니다.

USE <database_name>;
GO
SELECT SCHEMA_NAME(schema_id) AS schema_name
    ,name AS table_name 
FROM sys.tables 
WHERE OBJECTPROPERTY(object_id,'TableHasIdentity') = 1
ORDER BY schema_name, table_name;
GO


지정된 테이블에 있는 열의 데이터 형식을 찾으려면 어떻게 해야 합니까?

다음 쿼리를 실행하기 전에 <database_name>  <schema_name.table_name>을 올바른 이름으로 대체합니다.

USE <database_name>;
GO
SELECT c.name AS column_name
    ,c.column_id
    ,SCHEMA_NAME(t.schema_id) AS type_schema
    ,t.name AS type_name
    ,t.is_user_defined
    ,t.is_assembly_type
    ,c.max_length
    ,c.precision
    ,c.scale
FROM sys.columns AS c 
JOIN sys.types AS t ON c.user_type_id=t.user_type_id
WHERE c.object_id = OBJECT_ID('<schema_name.table_name>')
ORDER BY c.column_id;
GO


지정된 함수에서 종속성을 찾으려면 어떻게 해야 합니까?

다음 쿼리를 실행하기 전에 <database_name>  <schema_name.function_name>을 올바른 이름으로 대체합니다.

USE <database_name>;
GO
SELECT OBJECT_NAME(object_id) AS referencing_object_name
    ,COALESCE(COL_NAME(object_id, column_id), '(n/a)') AS referencing_column_name
    ,*
FROM sys.sql_dependencies
WHERE referenced_major_id = OBJECT_ID('<schema_name.function_name>')
ORDER BY OBJECT_NAME(object_id), COL_NAME(object_id, column_id);
GO 


데이터베이스에서 저장 프로시저를 모두 찾으려면 어떻게 해야 합니까?

다음 쿼리를 실행하기 전에 <database_name>을 올바른 이름으로 대체합니다.

USE <database_name>;
GO
SELECT name AS procedure_name 
    ,SCHEMA_NAME(schema_id) AS schema_name
    ,type_desc
    ,create_date
    ,modify_date
FROM sys.procedures;
GO


지정한 저장 프로시저나 함수에 대한 매개 변수를 찾으려면 어떻게 해야 합니까?

다음 쿼리를 실행하기 전에 <database_name>  <schema_name.object_name>을 올바른 이름으로 대체합니다.

USE <database_name>;
GO
SELECT SCHEMA_NAME(schema_id) AS schema_name
    ,o.name AS object_name
    ,o.type_desc
    ,p.parameter_id
    ,p.name AS parameter_name
    ,TYPE_NAME(p.user_type_id) AS parameter_type
    ,p.max_length
    ,p.precision
    ,p.scale
    ,p.is_output
FROM sys.objects AS o
INNER JOIN sys.parameters AS p ON o.object_id = p.object_id
WHERE o.object_id = OBJECT_ID('<schema_name.object_name>')
ORDER BY schema_name, object_name, p.parameter_id;
GO


데이터베이스에서 사용자 정의 함수를 모두 찾으려면 어떻게 해야 합니까?

다음 쿼리를 실행하기 전에 <database_name>을 올바른 데이터베이스 이름으로 대체합니다.

USE <database_name>;
GO
SELECT name AS function_name 
  ,SCHEMA_NAME(schema_id) AS schema_name
  ,type_desc
  ,create_date
  ,modify_date
FROM sys.objects
WHERE type_desc LIKE '%FUNCTION%';
GO


데이터베이스에서 뷰를 모두 찾으려면 어떻게 해야 합니까?

다음 쿼리를 실행하기 전에 <database_name>을 올바른 데이터베이스 이름으로 대체합니다.

USE <database_name>;
GO
SELECT name AS view_name 
  ,SCHEMA_NAME(schema_id) AS schema_name
  ,OBJECTPROPERTYEX(object_id,'IsIndexed') AS IsIndexed
  ,OBJECTPROPERTYEX(object_id,'IsIndexable') AS IsIndexable
  ,create_date
  ,modify_date
FROM sys.views;
GO


최근 n일 동안 수정된 엔터티를 모두 찾으려면 어떻게 해야 합니까?

다음 쿼리를 실행하기 전에 <database_name>  <n_days>를 올바른 값으로 대체합니다.

USE <database_name>;
GO
SELECT name AS object_name 
  ,SCHEMA_NAME(schema_id) AS schema_name
  ,type_desc
  ,create_date
  ,modify_date
FROM sys.objects
WHERE modify_date > GETDATE() - <n_days>
ORDER BY modify_date;
GO


지정한 테이블의 LOB 데이터 형식을 찾으려면 어떻게 해야 합니까?

다음 쿼리를 실행하기 전에 <database_name>  <schema_name.table_name>을 올바른 이름으로 대체합니다.

USE <database_name>;
GO
SELECT name AS column_name 
    ,column_id 
    ,TYPE_NAME(user_type_id) AS type_name
    ,max_length
    ,CASE 
       WHEN max_length = -1 AND TYPE_NAME(user_type_id) <> 'xml'
            THEN 1
            ELSE 0
     END AS [(max)]
FROM sys.columns
WHERE object_id=OBJECT_ID('<schema_name.table_name>') 
    AND ( TYPE_NAME(user_type_id) IN ('xml','text', 'ntext','image')
         OR (TYPE_NAME(user_type_id) IN ('varchar','nvarchar','varbinary')
         AND max_length = -1)
        );
GO


모듈의 정의를 보려면 어떻게 해야 합니까?

다음 쿼리를 실행하기 전에 <database_name>  <schema_name.object_name>을 올바른 이름으로 대체합니다.

USE <database_name>;
GO
SELECT definition
FROM sys.sql_modules
WHERE object_id = OBJECT_ID('<schema_name.object_name>');
GO


또는 다음 예와 같이 OBJECT_DEFINITION 함수를 사용할 수 있습니다.

USE <database_name>;
GO
SELECT OBJECT_DEFINITION (OBJECT_ID('<schema_name.object_name>')) AS ObjectDefinition;
GO


서버 수준 트리거의 정의를 보려면 어떻게 해야 합니까?

SELECT definition
FROM sys.server_sql_modules;
GO


지정한 테이블의 기본 키 열을 찾으려면 어떻게 해야 합니까?

다음 쿼리를 실행하기 전에 <database_name>  <schema_name.table_name>을 올바른 이름으로 대체합니다.

USE <database_name>;
GO
SELECT i.name AS index_name
    ,ic.index_column_id
    ,key_ordinal
    ,c.name AS column_name
    ,TYPE_NAME(c.user_type_id)AS column_type 
    ,is_identity
FROM sys.indexes AS i
INNER JOIN sys.index_columns AS ic 
    ON i.object_id = ic.object_id AND i.index_id = ic.index_id
INNER JOIN sys.columns AS c 
    ON ic.object_id = c.object_id AND c.column_id = ic.column_id
WHERE i.is_primary_key = 1 
    AND i.object_id = OBJECT_ID('<schema_name.table_name>');
GO


또는 다음 예와 같이 COL_NAME 함수를 사용할 수 있습니다.

USE <database_name>;
GO
SELECT i.name AS index_name
    ,COL_NAME(ic.object_id,ic.column_id) AS column_name
    ,ic.index_column_id
    ,key_ordinal
FROM sys.indexes AS i
INNER JOIN sys.index_columns AS ic 
    ON i.object_id = ic.object_id AND i.index_id = ic.index_id
WHERE i.is_primary_key = 1 
    AND i.object_id = OBJECT_ID('<schema_name.table_name>');
GO


지정한 테이블의 외래 키 열을 찾으려면 어떻게 해야 합니까?

다음 쿼리를 실행하기 전에 <database_name>  <schema_name.table_name>을 올바른 이름으로 대체합니다.

USE <database_name>;
GO
SELECT 
    f.name AS foreign_key_name
   ,OBJECT_NAME(f.parent_object_id) AS table_name
   ,COL_NAME(fc.parent_object_id, fc.parent_column_id) AS constraint_column_name
   ,OBJECT_NAME (f.referenced_object_id) AS referenced_object
   ,COL_NAME(fc.referenced_object_id, fc.referenced_column_id) AS referenced_column_name
   ,is_disabled
   ,delete_referential_action_desc
   ,update_referential_action_desc
FROM sys.foreign_keys AS f
INNER JOIN sys.foreign_key_columns AS fc 
   ON f.object_id = fc.constraint_object_id 
WHERE f.parent_object_id = OBJECT_ID('<schema_name.table_name>');


지정한 보안 주체에 허용 또는 거부된 사용 권한을 찾으려면 어떻게 해야 합니까?

다음 예에서는 사용 권한이 확인되는 엔터티의 이름을 반환하는 함수를 만듭니다. 이 함수는 뒷부분에 나오는 쿼리에서 호출됩니다. 사용 권한을 확인할 모든 데이터베이스에서 함수를 만들어야 합니다.

-- Create a function to return the name of the entity on which the permissions are checked.
IF OBJECT_ID (N'dbo.entity_instance_name', N'FN') IS NOT NULL
    DROP FUNCTION dbo.entity_instance_name;
GO
CREATE FUNCTION dbo.entity_instance_name(@class_desc nvarchar(60), @major_id int) 
RETURNS sysname AS
BEGIN
    DECLARE @the_entity_name sysname
    SELECT @the_entity_name = CASE
        WHEN @class_desc = 'DATABASE' THEN DB_NAME()
        WHEN @class_desc = 'SCHEMA' THEN SCHEMA_NAME(@major_id)
        WHEN @class_desc = 'OBJECT_OR_COLUMN' THEN OBJECT_NAME(@major_id)
        WHEN @class_desc = 'DATABASE_PRINCIPAL' THEN USER_NAME(@major_id)
        WHEN @class_desc = 'ASSEMBLY' THEN 
            (SELECT name FROM sys.assemblies WHERE assembly_id=@major_id)
        WHEN @class_desc = 'TYPE' THEN TYPE_NAME(@major_id)
        WHEN @class_desc = 'XML_SCHEMA_COLLECTION' THEN 
            (SELECT name FROM sys.xml_schema_collections
              WHERE xml_collection_id=@major_id)
        WHEN @class_desc = 'MESSAGE_TYPE' THEN 
            (SELECT name FROM sys.service_message_types WHERE message_type_id=@major_id)
        WHEN @class_desc = 'SERVICE_CONTRACT' THEN 
           (SELECT name FROM sys.service_contracts
              WHERE service_contract_id=@major_id)
        WHEN @class_desc = 'SERVICE' THEN
          (SELECT name FROM sys.services WHERE service_id=@major_id)
        WHEN @class_desc = 'REMOTE_SERVICE_BINDING' THEN
          (SELECT name FROM sys.remote_service_bindings
             WHERE remote_service_binding_id=@major_id)
        WHEN @class_desc = 'ROUTE' THEN
          (SELECT name FROM sys.routes WHERE route_id=@major_id)
        WHEN @class_desc = 'FULLTEXT_CATALOG' THEN
          (SELECT name FROM sys.fulltext_catalogs WHERE fulltext_catalog_id=@major_id)
        WHEN @class_desc = 'SYMMETRIC_KEY' THEN
          (SELECT name FROM sys.symmetric_keys WHERE symmetric_key_id=@major_id)
        WHEN @class_desc = 'CERTIFICATE' THEN
          (SELECT name FROM sys.certificates WHERE certificate_id=@major_id)
        WHEN @class_desc = 'ASYMMETRIC_KEY' THEN
          (SELECT name FROM sys.asymmetric_keys WHERE asymmetric_key_id=@major_id)
        WHEN @class_desc = 'SERVER' THEN 
             (SELECT name FROM sys.servers WHERE server_id=@major_id)
        WHEN @class_desc = 'SERVER_PRINCIPAL' THEN SUSER_NAME(@major_id)
        WHEN @class_desc = 'ENDPOINT' THEN 
             (SELECT name FROM sys.endpoints WHERE endpoint_id=@major_id)      
        ELSE '?'
    END
    RETURN @the_entity_name
END;
GO
-- Return server-level permissions for the user.
SELECT class
    ,class_desc
    ,dbo.entity_instance_name(class_desc, major_id) AS entity_name 
    ,minor_id
    ,SUSER_NAME(grantee_principal_id) AS grantee
    ,SUSER_NAME(grantor_principal_id) AS grantor
    ,type
    ,permission_name
    ,state_desc 
FROM sys.server_permissions 
WHERE grantee_principal_id = SUSER_ID('public');
GO
-- Return database-level permissions for the user.
SELECT class
    ,class_desc
    ,dbo.entity_instance_name(class_desc , major_id) AS entity_name 
    ,minor_id
    ,USER_NAME(grantee_principal_id) AS grantee
    ,USER_NAME(grantor_principal_id) AS grantor
    ,type
    ,permission_name
    ,state_desc   
FROM  sys.database_permissions 
WHERE grantee_principal_id = DATABASE_PRINCIPAL_ID('public');
GO


열이 계산 열 식에 사용되었는지 어떻게 확인합니까?

다음 쿼리를 실행하기 전에 <database_name>, <schema_name.table_name>  <column_name>을 올바른 이름으로 대체합니다.

USE <database_name>;
GO
SELECT OBJECT_NAME(object_id) AS object_name
    ,COL_NAME(object_id, column_id) AS computed_column 
    ,class_desc
    ,is_selected
    ,is_updated
    ,is_select_all
FROM sys.sql_dependencies
WHERE referenced_major_id = OBJECT_ID('<schema_name.table_name>')
    AND referenced_minor_id = COLUMNPROPERTY(referenced_major_id, '<column_name>', 'ColumnId')
    AND class = 1;
GO


계산 열 식에 사용된 열을 모두 찾으려면 어떻게 해야 합니까?

다음 쿼리를 실행하기 전에 <database_name>을 올바른 이름으로 대체합니다.

USE <database_name>;
GO
SELECT OBJECT_NAME(d.referenced_major_id) AS object_name
    ,COL_NAME(d.referenced_major_id, d.referenced_minor_id) AS column_name
    ,OBJECT_NAME(referenced_major_id) AS dependent_object_name 
    ,COL_NAME(d.object_id, d.column_id) AS dependent_computed_column
    ,cc.definition AS computed_column_definition
FROM sys.sql_dependencies AS d
JOIN sys.computed_columns AS cc 
    ON cc.object_id = d.object_id AND cc.column_id = d.column_id AND d.object_id=d.referenced_major_id     
WHERE d.class = 1
ORDER BY object_name, column_name;
GO


지정한 CLR 사용자 정의 형식 또는 별칭 형식에 종속되는 열을 찾으려면 어떻게 해야 합니까?

다음 쿼리를 실행하기 전에 <database_name>을 올바른 이름으로 대체하고 <schema_name.data_type_name>을 올바른 스키마 한정 CLR 사용자 정의 형식 또는 스키마 한정 별칭 형식 이름으로 대체합니다. 다음 쿼리에는 데이터베이스의 모든 종속 열 및 계산 열 메타데이터를 보기 위한 db_owner 역할의 멤버 자격 또는 사용 권한이 필요합니다.

USE <database_name>;
GO
SELECT OBJECT_NAME(object_id) AS object_name 
    ,c.name AS column_name 
    ,SCHEMA_NAME(t.schema_id) AS schema_name
    ,TYPE_NAME(c.user_type_id) AS user_type_name
    ,c.max_length
    ,c.precision
    ,c.scale
    ,c.is_nullable
    ,c.is_computed
FROM sys.columns AS c
INNER JOIN sys.types AS t ON c.user_type_id = t.user_type_id
WHERE c.user_type_id = TYPE_ID('<schema_name.data_type_name>'); 
GO


다음 쿼리는 CLR 사용자 정의 형식 또는 별칭에 종속되는 제한적이며 좁은 뷰의 열을 반환하지만 결과 집합은 public 역할의 사용자에게 표시됩니다. 사용자 정의 형식에 대한 REFERENCE 권한을 다른 사용자에게 부여했고, 다른 사용자가 만들었으며 해당 형식을 사용하는 개체에 대한 메타데이터를 볼 수 있는 권한이 없는 경우 이 쿼리를 사용할 수 있습니다.

USE <database_name>;
GO
SELECT OBJECT_NAME(object_id) AS object_name 
    ,COL_NAME(object_id, column_id) AS column_name
    ,TYPE_NAME(user_type_id) AS user_type
FROM sys.column_type_usages
WHERE user_type_id = TYPE_ID('<schema_name.data_type_name>');
GO


지정한 CLR 사용자 정의 형식 또는 별칭 형식에 종속되는 계산 열을 찾으려면 어떻게 해야 합니까?

다음 쿼리를 실행하기 전에 <database_name>을 올바른 이름으로 대체하고 <schema_name.data_type_name>을 올바른 스키마 한정 CLR 사용자 정의 형식, 별칭 형식 이름으로 대체합니다. 계산 열 종속성에 대한 자세한 내용은 SQL 종속성 이해를 참조하십시오.

USE <database_name>;
GO
SELECT OBJECT_NAME(object_id) AS object_name
    ,COL_NAME(object_id, column_id) AS column_name
FROM sys.sql_dependencies
WHERE referenced_major_id = TYPE_ID('<schema_name.data_type_name>')
    AND class = 2 -- schema-bound references to type
    AND OBJECTPROPERTY(object_id, 'IsTable') = 1;   -- exclude non-table dependencies 


지정한 CLR 사용자 정의 형식 또는 별칭 형식에 종속되는 매개 변수를 찾으려면 어떻게 해야 합니까?

다음 쿼리를 실행하기 전에 <database_name>을 올바른 이름으로 대체하고 <schema_name.data_type_name>을 올바른 스키마 한정 CLR 사용자 정의 형식, 별칭 형식 이름으로 대체합니다. 다음 쿼리에는 데이터베이스의 모든 종속 열 및 계산 열 메타데이터를 보기 위한 db_owner 역할의 멤버 자격 또는 사용 권한이 필요합니다.

USE <database_name>;
GO
SELECT OBJECT_NAME(object_id) AS object_name
    ,NULL AS procedure_number
    ,name AS param_name
    ,parameter_id AS param_num
    ,TYPE_NAME(p.user_TYPE_ID) AS type_name
FROM sys.parameters AS p
WHERE p.user_TYPE_ID = TYPE_ID('<schema_name.data_type_name>')
UNION 
SELECT OBJECT_NAME(object_id) AS object_name
    ,procedure_number
    ,name AS param_name
    ,parameter_id AS param_num
    ,TYPE_NAME(p.user_TYPE_ID) AS type_name
FROM sys.numbered_procedure_parameters AS p
WHERE p.user_TYPE_ID = TYPE_ID('<schema_name.data_type_name>')
ORDER BY object_name, procedure_number, param_num;
GO



다음 쿼리는 CLR 사용자 정의 형식 또는 별칭에 종속되는 제한적이며 좁은 뷰의 매개 변수를 반환하지만 결과 집합은 public 역할의 사용자에 표시됩니다. 사용자 정의 형식에 대한 REFERENCE 권한을 다른 사용자에게 부여했고, 다른 사용자가 만들었으며 해당 형식을 사용하는 개체에 대한 메타데이터를 볼 수 있는 권한이 없는 경우 이 쿼리를 사용할 수 있습니다.

USE <database_name>;
GO
SELECT OBJECT_NAME(object_id) AS object_name
    ,parameter_id
    ,TYPE_NAME(user_type_id) AS type_name
FROM sys.parameter_type_usages 
WHERE user_type_id = TYPE_ID('<schema_name.data_type_name>');
GO


지정한 CLR 사용자 정의 형식에 종속되는 CHECK 제약 조건을 찾으려면 어떻게 해야 합니까?

다음 쿼리를 실행하기 전에 <database_name>을 올바른 이름으로 대체하고 <schema_name.data_type_name>을 올바른 스키마 한정 CLR 사용자 정의 형식 이름으로 대체합니다.

USE <database_name>;
GO
SELECT SCHEMA_NAME(o.schema_id) AS schema_name
    ,OBJECT_NAME(o.parent_object_id) AS table_name
    ,OBJECT_NAME(o.object_id) AS constraint_name
FROM sys.sql_dependencies AS d
JOIN sys.objects AS o ON o.object_id = d.object_id
WHERE referenced_major_id = TYPE_ID('<schema_name.data_type_name>')
    AND class = 2 -- schema-bound references to type
    AND OBJECTPROPERTY(o.object_id, 'IsCheckCnst') = 1; -- exclude non-CHECK dependencies
GO


지정한 CLR 사용자 정의 형식 또는 별칭 형식에 종속되는 뷰, Transact-SQL 함수 및 Transact-SQL 저장 프로시저를 찾으려면 어떻게 해야 합니까?

다음 쿼리를 실행하기 전에 <database_name>을 올바른 이름으로 대체하고 <schema_name.data_type_name>을 올바른 스키마 한정 CLR 사용자 정의 형식, 별칭 형식 이름으로 대체합니다.

함수 또는 프로시저에 정의된 매개 변수는 암시적으로 스키마에 바인딩됩니다. 따라서 CLR 사용자 정의 형식 또는 별칭 형식에 종속되는 매개 변수는 sys.sql_dependencies 카탈로그 뷰를 사용하여 볼 수 있습니다. 프로시저 및 트리거는 스키마에 바인딩되지 않습니다. 즉, 프로시저 또는 트리거 본문에 정의되어 있는 모든 식과 CLR 사용자 정의 형식 또는 별칭 형식 간의 종속성이 유지되지 않습니다. CLR 사용자 정의 형식 또는 별칭 형식에 종속되는 식이 있는 스키마 바운드 뷰 및 스키마 바운드 사용자 정의 함수는 sys.sql_dependencies 카탈로그 뷰에 유지됩니다. 형식, CLR 함수 및 CLR 프로시저 간의 종속성은 유지되지 않습니다.

다음 쿼리는 지정한 CLR 사용자 정의 형식 또는 별칭 형식에 대한 뷰, Transact-SQL 함수 및 Transact-SQL 저장 프로시저에 있는 모든 스키마 바운드 종속성을 반환합니다.

USE <database_name>;
GO
SELECT SCHEMA_NAME(o.schema_id) AS dependent_object_schema
  ,OBJECT_NAME(o.object_id) AS dependent_object_name
  ,o.type_desc AS dependent_object_type
  ,d.class_desc AS kind_of_dependency
  ,TYPE_NAME (d.referenced_major_id) AS type_name
FROM sys.sql_dependencies AS d 
JOIN sys.objects AS o
  ON d.object_id = o.object_id
  AND o.type IN ('FN','IF','TF', 'V', 'P')
WHERE d.class = 2 -- dependencies on types
  AND d.referenced_major_id = TYPE_ID('<schema_name.data_type_name>')
ORDER BY dependent_object_schema, dependent_object_name;
GO


지정한 테이블에 대한 제약 조건을 모두 찾으려면 어떻게 해야 합니까?

다음 쿼리를 실행하기 전에 <database_name>  <schema_name.table_name>을 올바른 이름으로 대체합니다.

USE <database_name>;
GO
SELECT OBJECT_NAME(object_id) as constraint_name
    ,SCHEMA_NAME(schema_id) AS schema_name
    ,OBJECT_NAME(parent_object_id) AS table_name
    ,type_desc
    ,create_date
    ,modify_date
    ,is_ms_shipped
    ,is_published
    ,is_schema_published
FROM sys.objects
WHERE type_desc LIKE '%CONSTRAINT' 
    AND parent_object_id = OBJECT_ID('<schema_name.table_name>');
GO


지정한 테이블에 대한 인덱스를 모두 찾으려면 어떻게 해야 합니까?

다음 쿼리를 실행하기 전에 <database_name>  <schema_name.table_name>을 올바른 이름으로 대체합니다.

USE <database_name>;
GO
SELECT i.name AS index_name
    ,i.type_desc
    ,is_unique
    ,ds.type_desc AS filegroup_or_partition_scheme
    ,ds.name AS filegroup_or_partition_scheme_name
    ,ignore_dup_key
    ,is_primary_key
    ,is_unique_constraint
    ,fill_factor
    ,is_padded
    ,is_disabled
    ,allow_row_locks
    ,allow_page_locks
FROM sys.indexes AS i
INNER JOIN sys.data_spaces AS ds ON i.data_space_id = ds.data_space_id
WHERE is_hypothetical = 0 AND i.index_id <> 0 
AND i.object_id = OBJECT_ID('<schema_name.table_name>');
GO


지정한 열 이름이 있는 개체를 모두 찾으려면 어떻게 해야 합니까?

다음 쿼리를 실행하기 전에 <database_name>  <column_name>을 올바른 이름으로 대체합니다.

USE <database_name>;
GO
SELECT OBJECT_NAME(object_id)
FROM sys.columns
WHERE name = '<column_name>';
GO


또는

USE <database_name>;
GO
SELECT SCHEMA_NAME(o.schema_id) AS schema_name 
    ,o.name AS object_name
    ,type_desc
FROM sys.objects AS o
INNER JOIN sys.columns AS c ON o.object_id = c.object_id
WHERE c.name = '<column_name>';
GO


지정된 데이터베이스에서 사용자 정의 테이블을 모두 찾으려면 어떻게 해야 합니까?

다음 쿼리를 실행하기 전에 <database_name>을 올바른 이름으로 대체합니다.

USE <database_name>;
GO
SELECT * 
FROM sys.tables;
GO


분할된 테이블 및 인덱스를 모두 찾으려면 어떻게 해야 합니까?

다음 쿼리를 실행하기 전에 <database_name>을 올바른 이름으로 대체합니다.

USE <database_name>;
GO
SELECT SCHEMA_NAME(o.schema_id) AS schema_name
    ,OBJECT_NAME(p.object_id) AS table_name
    ,i.name AS index_name
    ,p.partition_number
    ,rows 
FROM sys.partitions AS p
INNER JOIN sys.indexes AS i ON p.object_id = i.object_id AND p.index_id = i.index_id
INNER JOIN sys.partition_schemes ps ON i.data_space_id=ps.data_space_id
INNER JOIN sys.objects AS o ON o.object_id = i.object_id
ORDER BY index_name, partition_number;
GO


지정한 개체에 대한 통계를 모두 찾으려면 어떻게 해야 합니까?

다음 쿼리를 실행하기 전에 <database_name>을 올바른 이름으로 대체하고 <schema_name.object_name>을 올바른 테이블, 인덱싱된 뷰 또는 테이블 반환 함수 이름으로 대체합니다.

USE <database_name>;
GO
SELECT name AS statistics_name
    ,stats_id
    ,auto_created
    ,user_created
    ,no_recompute
FROM sys.stats
WHERE object_id = OBJECT_ID('<schema_name.object_name>');
GO


지정한 개체에 대한 통계 및 통계 열을 모두 찾으려면 어떻게 해야 합니까?

다음 쿼리를 실행하기 전에 <database_name>을 올바른 이름으로 대체하고 <schema_name.object_name>을 올바른 테이블, 인덱싱된 뷰 또는 테이블 반환 함수 이름으로 대체합니다.

USE <database_name>;
GO
SELECT s.name AS statistics_name
    ,c.name AS column_name
    ,sc.stats_column_id
FROM sys.stats AS s
INNER JOIN sys.stats_columns AS sc 
    ON s.object_id = sc.object_id AND s.stats_id = sc.stats_id
INNER JOIN sys.columns AS c 
    ON sc.object_id = c.object_id AND c.column_id = sc.column_id
WHERE s.object_id = OBJECT_ID('<schema_name.object_name>');
GO


뷰의 정의를 찾으려면 어떻게 해야 합니까?

다음 쿼리를 실행하기 전에 <database_name>  <schema_name.object_name>을 올바른 이름으로 대체합니다.

USE <database_name>;
GO
SELECT definition
FROM sys.sql_modules
WHERE object_id = OBJECT_ID('<schema_name.object_name>');
GO


또는 다음 예와 같이 OBJECT_DEFINITION 함수를 사용할 수 있습니다.

USE <database_name>;
GO
SELECT OBJECT_DEFINITION (OBJECT_ID('<schema_name.object_name>')) AS ObjectDefinition;
GO


Posted by 진실세상

[출처] http://msdn.microsoft.com/ko-kr/magazine/cc135978.aspx


응용 프로그램 성능 문제의 많은 부분은 성능이 낮은 데이터베이스 쿼리가 원인이지만 데이터베이스 성능을 개선할 수 있는 방법이 많이 있습니다. 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 스크립트를 사용하여 누적된 대기의 원인을 조사할 수 있습니다.
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에서 볼 수 있습니다.
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를 검사하고 데이터베이스별로 누락된 인덱스의 수를 합산하여 추가 조사가 필요한 데이터베이스를 쉽게 확인할 수 있도록 해 줍니다.
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 작업이 줄어들고 성능이 향상됩니다.
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에 있는 스크립트를 사용하여 가장 비용이 높은 사용되지 않는 인덱스를 확인할 수 있습니다.
-- 필요한 테이블 구조만 작성합니다.
-- 참고: 이 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에 있는 스크립트는 가장 비용이 높은 인덱스를 확인하고 결과를 보여 줍니다.
-- 필요한 테이블 구조만 작성합니다.
-- 참고: 이 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를 사용하면 서버에 있는 모든 데이터베이스에 대해 반복하여 서버 수준의 결과를 얻을 수 있습니다. 이를 위해 필자가 사용한 패턴은 다음과 같습니다.
  1. 코드 본문과 비슷한 필요한 구조로 임시 테이블을 만듭니다. 존재하지 않는 레코드(object_id를 -999로 지정)를 지정하여 임시 테이블 구조가 생성되도록 합니다.
  2. 코드 본문이 실행되어 서버에 있는 모든 데이터베이스를 대상으로 반복합니다. 각 데이터베이스에서 검색되는 레코드의 수(TOP 문 사용)는 표시하려는 레코드의 수와 같아야 합니다. 그렇지 않으면 결과는 서버의 모든 데이터베이스에 걸친 TOP n 레코드를 올바르게 나타내지 못할 수 있습니다.
  3. 레코드는 임시 테이블에서 추출되고 관심이 있는 열(이 경우에는 user_updates 열)을 기준으로 정렬됩니다.

DMV를 사용하여 가장 자주 사용되는 인덱스를 확인할 수 있습니다. 이러한 인덱스는 기본 데이터에 대한 가장 일반적인 경로이므로 이를 개선하거나 최적화한다면 전체적으로 높은 성능 향상을 거둘 수 있습니다.
sys.dm_db_index_usage_stats DMV에는 검색, 스캔 및 조회를 통해 데이터를 검색하는 데 인덱스가 얼마나 자주 사용되었는지에 대한 세부 사항이 포함되어 있습니다. 이 DMV는 인덱스를 만드는 데 사용된 세부 사항이 포함된 sys.indexes DMV와 조인됩니다. Usage 열은 모든 user_* 열의 합으로 계산됩니다. 그림 7에 있는 스크립트를 사용하여 이를 수행할 수 있습니다. 이 쿼리의 결과는 인덱스가 사용된 횟수를 Usage를 기준으로 정렬하여 보여 줍니다.
-- 필요한 테이블 구조만 작성합니다.
-- 참고: 이 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에 있는 스크립트는 논리적으로 조각화가 가장 심한 인덱스를 확인합니다. 결과는 조각화 비율을 기준으로 정렬되며 모든 데이터베이스에 걸쳐 논리적으로 조각화가 가장 심한 인덱스와 해당하는 데이터베이스/테이블을 보여 줍니다. 이 스크립트는 처음 실행하면 꽤 오래(몇 분) 걸릴 수 있으므로 스크립트 다운로드에는 주석으로 표시했습니다.
-- 필요한 테이블 구조만 작성합니다.
-- 참고: 이 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, 개별 쿼리, 부모 쿼리(개별 쿼리가 일괄 처리의 일부인 경우) 및 데이터베이스 이름을 볼 수 있습니다.
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 사용 비용이 가장 높은 쿼리를 확인할 수 있습니다.
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 쿼리를 확인하는 쿼리를 볼 수 있습니다.
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에 있는 쿼리를 사용하여 가장 자주 실행되는 쿼리를 확인할 수 있습니다. 이 쿼리를 실행하면 실행 횟수, 개별 쿼리, 부모 쿼리(개별 쿼리가 일괄 처리의 일부인 경우) 및 관련된 데이터베이스를 볼 수 있습니다. 이번에도 역시 데이터베이스 튜닝 관리자로 쿼리에 대해 더 조사하여 추가 개선이 가능한지 확인하는 것이 좋을 수 있습니다.
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로 정렬하는 것도 유용합니다.
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 열), 개별 쿼리, 부모 쿼리 및 데이터베이스 이름을 볼 수 있습니다.
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에 대해서는 아직도 배울 것이 많으므로 이 기사를 더 세부적인 부분을 살펴보는 계기로 삼기를 바랍니다.

Ian Stirk는 1987년부터 개발자, 디자이너, 설계자로 IT 업계에서 일해왔습니다. Ian은 M.Sc., MCSD, MCDBA 및 SCJP 자격을 보유하고 있으며 영국 런던에서 Microsoft 기술을 전파하는 프리랜서 컨설턴트입니다. 문의 사항이 있으면 Ian_Stirk@yahoo.com으로 연락하십시오.

Posted by 진실세상
TAG MSSQL, 성능