Skip to content

Latest commit

 

History

History

idx

인덱스(index) 정보

Database context : user database

다음 DMV들과 DMF가 사용되었습니다.

기본적인 인덱스 정보(스키마 이름, 테이블 이름, 인덱스 아이디, 인덱스 이름, 인덱스 유형)와 간략한 사용 패턴(seek, scan, lookup, update)에 대한 정보는 다음의 쿼리를 이용하여 확인이 가능합니다.

SELECT OBJECT_SCHEMA_NAME(u.object_id) AS [schema_name], OBJECT_NAME(u.object_id) AS Table_Name,  
				u.index_id, i.name AS Index_name, i.type_desc AS index_type,
				u.user_seeks, u.user_scans, u.user_lookups, u.user_updates
FROM sys.dm_db_index_usage_stats AS u INNER JOIN sys.indexes AS i ON u.object_id = i.object_id AND u.index_id = i.index_id
ORDER BY OBJECT_NAME(u.object_id), u.index_id;
GO

다음의 쿼리는 인덱스 정보는 물론이고, 인덱스를 구성하고 있는 컬럼, 해당 컬럼의 데이터 형식 등의 정보들을 반환합니다.

SELECT SCHEMA_NAME(o.schema_id) AS Schema_Name, OBJECT_NAME(i.object_id) AS Table_Name, i.name AS Index_Name, i.index_id, 
			i.type_desc AS Index_type_desc, ic.is_included_column,
			ic.index_column_id, c.name AS Column_Name, t.name AS Column_data_type, c.max_length, c.precision, c.scale, c.collation_name, c.is_nullable, CASE t.is_user_defined WHEN 1 THEN 'TRUE' ELSE 'FALSE' END AS user_defined_data_type
FROM sys.indexes AS i INNER JOIN sys.objects AS o ON i.object_id = o.object_id
			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 ic.column_id = c.column_id
			INNER JOIN sys.types AS t on c.user_type_id = t.user_type_id
WHERE o.is_ms_shipped = 0
ORDER BY i.object_id, i.index_id, ic.index_column_id;
GO

인덱스가 얼마나, 어떻게 사용되었는지는 다음과 같이 확인이 가능합니다. 인덱스가 유용성 여부는 물론이고, 인덱스가 어떤 유형의 연산자 형태로 사용되었는지 확인이 가능합니다.

SELECT OBJECT_SCHEMA_NAME(object_id) AS [schema_name], OBJECT_NAME(object_id) AS [table_name], index_id, 
	SUM(user_seeks + user_scans + user_lookups) AS ReadsOps,
	SUM(user_updates) AS WritesOps,
	CAST(SUM(user_seeks + user_scans + user_lookups) AS decimal) /NULLIF(SUM(user_updates + user_seeks + user_scans + user_lookups), 0) * 100 AS ReadsRatio,
	CAST(SUM(user_updates) AS decimal) /NULLIF(SUM(user_updates + user_seeks + user_scans + user_lookups), 0) * 100 AS WritesRatio
FROM sys.dm_db_index_usage_stats
GROUP BY object_id, index_id
ORDER BY object_id, index_id;
GO

다음의 쿼리는 인덱스의 기본 정보와 함께, 사용 패턴 및 데이터의 행수를 반환하게 됩니다.

SELECT o.name AS table_name, o.object_id, OBJECT_SCHEMA_NAME(o.object_id) AS [schema_name],
			i.name AS index_name, i.index_id,  i.type_desc AS index_type_desc, i.is_primary_key, i.is_unique_constraint, 
			u.user_seeks, u.user_scans, u.user_lookups, u.user_updates,
			SUM(p.rows) AS [# of rows]
FROM sys.objects AS o INNER JOIN sys.indexes AS i on o.object_id = i.object_id
			INNER  JOIN sys.dm_db_index_usage_stats AS u ON i.object_id = u.object_id AND i.index_id = u.index_id
			INNER JOIN sys.partitions AS p ON u.object_id = p.object_id AND u.index_id = p.index_id
WHERE o.is_ms_shipped = 0
GROUP BY o.name, o.object_id, o.schema_id, i.name, i.index_id, i.type_desc, i.is_primary_key, i.is_unique_constraint, u.user_seeks, u.user_scans, u.user_lookups, u.user_updates
ORDER BY o.object_id,i.index_id;
GO

다음은 인덱스별 페이지에 대한 작업 유형 횟수들을 확인하는 쿼리입니다. 크게 leaf level과 non-leaf level에 따른 insert, update, delete 횟수와 할당량을 보여주고 있습니다. SQL Server의 데이터 구조에 대해서는 다음의 문서를 참고하시기 바랍니다.

 SELECT o.name AS table_name, o.object_id, OBJECT_SCHEMA_NAME(o.object_id) AS [schema_name],
	i.name AS index_name, i.index_id,  i.type_desc AS index_type_desc, 

	--leaf level
	s.leaf_insert_count,
	s.leaf_delete_count,
	s.leaf_update_count,
	s.leaf_ghost_count,

	--non leaf
	s.nonleaf_insert_count,
	s.nonleaf_delete_count,
	s.nonleaf_update_count,

	--allocation
	s.leaf_allocation_count,
	s.nonleaf_allocation_count

FROM sys.objects AS o INNER JOIN sys.indexes AS i ON o.object_id = i.object_id
		INNER JOIN sys.partitions AS p ON i.object_id = p.object_id AND i.index_id = p.index_id
		INNER JOIN sys.dm_db_index_operational_stats(db_id(), NULL, NULL, NULL) AS s ON p.object_id = s.object_id AND p.index_id = s.index_id AND p.partition_number = s.partition_number
WHERE o.is_ms_shipped = 0
ORDER BY o.object_id, i.index_id;
GO

다음의 DMV 쿼리는 row, page 단위의 잠금과 latch 정보들을 반환합니다.

SELECT SCHEMA_NAME(o.schema_id) AS [schema_name],  o.name AS [object_name], 
 	i.name AS index_name, o.object_id, i.index_id, i.type_desc AS index_type_desc,

	--row & page lock
	s.partition_number, s.row_lock_count, s.row_lock_wait_count, s.row_lock_wait_in_ms, 
	s.page_lock_count, s.page_lock_wait_count, s.page_lock_wait_in_ms,

	--latch
	s.page_latch_wait_count, s.page_latch_wait_in_ms, s.page_io_latch_wait_count, s.page_io_latch_wait_in_ms,
	s.tree_page_latch_wait_count, s.tree_page_latch_wait_in_ms, s.tree_page_io_latch_wait_count, s.tree_page_io_latch_wait_in_ms

FROM sys.objects AS o INNER JOIN sys.indexes AS i ON o.object_id = i.object_id
            INNER JOIN sys.dm_db_index_operational_stats(DB_ID(), NULL, NULL, NULL) AS s ON i.object_id = s.object_id AND i.index_id = s.index_id
WHERE o.is_ms_shipped = 0
ORDER BY OBJECT_NAME(o.object_id), i.index_id;
GO

다음은 인덱스 관리 시에 가장 많이 이슈가 되는 인덱스 조각화 정보를 확인하는 DMV 쿼리입니다.

SELECT  OBJECT_SCHEMA_NAME(o.object_id) AS [schema_name],  o.name AS [object_name], 
 				i.name AS index_name, o.object_id, i.index_id, i.type_desc AS index_type_desc, 
				s.partition_number, s.alloc_unit_type_desc, s.index_depth, s.index_level,
				s.avg_fragmentation_in_percent, s.fragment_count,
				s.avg_fragment_size_in_pages, s.page_count				
FROM sys.objects AS o INNER JOIN sys.indexes AS i ON o.object_id = i.object_id
			INNER JOIN sys.dm_db_index_physical_stats(DB_ID(), NULL, NULL, NULL, 'LIMITED') AS s ON i.object_id = s.object_id AND i.index_id = s.index_id
WHERE o.is_ms_shipped = 0
ORDER BY s.avg_fragmentation_in_percent DESC , s.page_count DESC;
GO

Azure SQL Database에서는 SQL Server 2000에서 지원되던 시스템 개체들도 사용이 가능합니다.

단, 아래의 두 쿼리 모두 동일한 결과를 반환하며, 사용자 데이터베이스는 물론이고 master 데이터베이스에서도 실행이 가능합니다.

SELECT * FROM sysindexes;
SELECT * FROM sys.sysindexes;

다음의 쿼리는 테이블의 인덱스별로 데이터의 행수를 반환합니다.

SELECT OBJECT_SCHEMA_NAME(o.object_id)  AS [schema_name], OBJECT_NAME(o.object_id) AS table_name, i.index_id, i.type_desc, SUM(s.row_count) AS row_count
FROM sys.objects AS o INNER JOIN sys.indexes AS i on o.object_id = i.object_id
			INNER JOIN sys.dm_db_partition_stats AS s on i.object_id = s.object_id AND i.index_id = s.index_id
WHERE o.is_ms_shipped = 0
GROUP BY o.object_id, i.index_id, i.type_desc
ORDER BY o.object_id, i.index_id;
GO

데이터베이스에 요청되는 쿼리를 기준으로 생성이 필요한 인덱스와 인덱스 생성 구문을 반환하는 쿼리는 다음과 같습니다.

SELECT 
  migs.avg_total_user_cost * (migs.avg_user_impact / 100.0) * (migs.user_seeks + migs.user_scans) AS improvement_measure, 
  'CREATE INDEX [missing_index_' + CONVERT (varchar, mig.index_group_handle) + '_' + CONVERT (varchar, mid.index_handle) 
  + '_' + LEFT (PARSENAME(mid.statement, 1), 32) + ']'
  + ' ON ' + mid.statement 
  + ' (' + ISNULL (mid.equality_columns,'') 
    + CASE WHEN mid.equality_columns IS NOT NULL AND mid.inequality_columns IS NOT NULL THEN ',' ELSE '' END 
    + ISNULL (mid.inequality_columns, '')
  + ')' 
  + ISNULL (' INCLUDE (' + mid.included_columns + ')', '') AS create_index_statement, 
  migs.*, mid.database_id, mid.[object_id]
FROM sys.dm_db_missing_index_groups mig INNER JOIN sys.dm_db_missing_index_group_stats migs ON migs.group_handle = mig.index_group_handle
					INNER JOIN sys.dm_db_missing_index_details mid ON mig.index_handle = mid.index_handle
WHERE migs.avg_total_user_cost * (migs.avg_user_impact / 100.0) * (migs.user_seeks + migs.user_scans) > 10
ORDER BY migs.avg_total_user_cost * migs.avg_user_impact * (migs.user_seeks + migs.user_scans) DESC;