sys.dm_db_index_physical_stats의 성능 향상


14

유지 관리 작업 중에 조각난 인덱스 목록을 얻으려고합니다. 그러나 쿼리 속도가 매우 느리고 실행하는30 분 이상 이 걸립니다 . 이것이 sys.dm_db_index_physical_stats의 원격 스캔 때문이라고 생각합니다.

다음 쿼리 속도를 높일 수있는 방법이 있습니까?

SELECT
    OBJECT_NAME(i.OBJECT_ID) AS TableName,
    i.name AS TableIndexName
FROM
    sys.dm_db_index_physical_stats(DB_ID(), NULL, NULL, NULL, 'DETAILED') phystat 
    INNER JOIN sys.indexes i 
        ON i.OBJECT_ID = phystat.OBJECT_ID AND i.index_id = phystat.index_id 
WHERE 
    phystat.avg_fragmentation_in_percent > 20 
    AND OBJECT_NAME(i.OBJECT_ID) IS NOT NULL
ORDER BY phystat.avg_fragmentation_in_percent DESC

나는 DBA가 아니며 위의 쿼리에서 명백한 실수를 할 수 있거나 도움이 될만한 색인이나 통계가 있습니까? 아마도 데이터베이스의 크기 일뿐입니다 (약 140 개의 테이블이있는 약 20Gb).

내가 묻는 이유는 우리가 밤 동안 유지 보수를위한 매우 작은 창만을 가지고 있기 때문에 대부분의 시간을 차지하고 있기 때문입니다.

답변:


20

'DETAILED'전체 스캔을 의미인덱스 (또는 힙)에있는 모든 단일 페이지 을 . 모든 테이블과 모든 2 차 인덱스에 대해이 작업을 수행하면 결과적으로 전체 데이터베이스 스캔, 엔드 투 엔드 (end-to-end) 및 매우 효율적인 스캔이 수행되지 않습니다 (예 : 백업이 읽는 속도만큼 빠르지 않음). 시간은 다음에 의해 구동됩니다.

  • 데이터베이스의 크기
  • IO 서브 시스템이 전체 데이터베이스를 읽는 속도
  • IO 처리량을 위해 경쟁하는 추가 동시로드

기본적으로 모든 것이 짚 (IO 처리량)이면 버킷 (데이터베이스 크기)을 마시는 데 30 분이 걸립니다. 빠른 IO 구매, 데이터 크기 축소 또는 사용SAMPLED 스캔 .

20Gb는 매우 작습니다. 삼십분은 20GB의가있다 읽는 많은 시간. IO 서브 시스템 느린가요? 7200 RPM 소비자 1TB 드라이브에 배포 했습니까?


12

@Remus에서 SAMPLED스캔 을 사용하도록 권장하는 것 외에도 유지 관리 기간이 시작될 때까지이 쿼리를 시작할 수 없습니다. 결과로 테이블을 미리 채우지 않는 이유는 무엇입니까? 유지 관리 기간 약 15-20 분 전에이 쿼리를 시작하고 (샘플링 된 검색에 10 분이 걸리는 경우) 유지 관리 기간이 시작 되 자마자 데이터를 사용할 수있게됩니다. 기본 데이터는 그 동안 실제로 그렇게 많이 변경되지 않았습니다. 원래 쿼리에서 정렬 및 필터링을 피하면 다음과 같이 더 빨리 완료됩니다.

CREATE TABLE dbo.IndexStats
(
  TableName SYSNAME,
  IndexName SYSNAME,
  Frag DECIMAL(5,2)
);
CREATE INDEX x ON dbo.IndexStats(Frag);

그런 다음 첫 야간 작업 (유지 관리 기간 전에 시작)에서 :

TRUNCATE TABLE dbo.IndexStats;

INSERT dbo.IndexStats
SELECT 
  OBJECT_NAME(i.[object_id]),
  i.name,
  s.avg_fragmentation_in_percent
FROM
  sys.dm_db_index_physical_stats(DB_ID(), NULL, NULL, NULL, 'SAMPLED') AS s 
INNER JOIN sys.indexes AS i 
ON i.[object_id] = s.[object_id]
AND i.index_id = s.index_id;

DELETE dbo.IndexStats WHERE Frag < 20
  OR TableName IS NULL;

이제 실제 조각 모음 스크립트에는 이미 즉시 진행하는 데 필요한 모든 정보가 있습니다. (작업을 함께 연결하거나 위의 명령을 사용하여 유지 관리 기간이 시작될 때까지 기다릴 수도 있습니다 WAITFOR TIME.)

당신은 또한 노는 것을 고려 LIMITED하고 그 방법을 볼 수 있습니다.


5

고지 사항 :이 스크립트는 SQL Server 2005/2008에서 테스트되었습니다. 그러나이 코드 및 정보는 묵시적 보증, 상업성 및 / 또는 특정 목적에의 적합성을 포함하되 이에 국한되지 않는 명시 적 또는 묵시적 보증없이 "있는 그대로"제공됩니다. 항상 그렇듯이 프로덕션 환경에 배포하기 전에 테스트 환경에서이를 테스트하십시오. 이제 그 길을 벗어났습니다 ...

인덱스 DMV를 처리 할 때 발생하는 문제 중 하나는 상관 관계가 없다는 것입니다. 즉, 스캔을 수행하는 인덱스를 제한하기 위해 CROSS / OUTER APPLY를 사용할 수 없습니다. 이 문제를 해결하기 위해 물리적 및 운영 인덱스 DMV에 대한 래퍼 함수를 ​​마스터 데이터베이스에 배포합니다.

물리적 인:

ALTER FUNCTION [dbo].[tfn_IndexPhysicalStats_select]
(
    @DatabaseID SMALLINT = 0,
    @ObjectID INT = 0,
    @IndexID INT = -1,
    @PartitionNumber INT = 0,
    @Mode NVARCHAR(20) = NULL
)
RETURNS @IndexPhysicalStats TABLE
(
    database_id SMALLINT NOT NULL,
    object_id INT NOT NULL,
    index_id INT NOT NULL,
    partition_number INT NOT NULL,
    index_type_desc NVARCHAR(60) NULL,
    alloc_unit_type_desc NVARCHAR(60) NULL,
    index_depth TINYINT NOT NULL,
    index_level TINYINT NOT NULL,
    avg_fragmentation_in_percent FLOAT NULL,
    fragment_count BIGINT NULL,
    avg_fragment_size_in_pages FLOAT NULL,
    page_count BIGINT NOT NULL,
    avg_page_space_used_in_percent FLOAT NULL,
    record_count BIGINT NULL,
    ghost_record_count BIGINT NULL,
    version_ghost_record_count BIGINT NULL,
    min_record_size_in_bytes INT NULL,
    max_record_size_in_bytes INT NULL,
    avg_record_size_in_bytes FLOAT NULL,
    forwarded_record_count BIGINT NULL
)
AS
BEGIN

    INSERT INTO @IndexPhysicalStats
    (
        database_id,
        object_id,
        index_id,
        partition_number,
        index_type_desc,
        alloc_unit_type_desc,
        index_depth,
        index_level,
        avg_fragmentation_in_percent,
        fragment_count,
        avg_fragment_size_in_pages,
        page_count,
        avg_page_space_used_in_percent,
        record_count,
        ghost_record_count,
        version_ghost_record_count,
        min_record_size_in_bytes,
        max_record_size_in_bytes,
        avg_record_size_in_bytes,
        forwarded_record_count
    )
    SELECT
        ddips.database_id,
        ddips.object_id,
        ddips.index_id,
        ddips.partition_number,
        ddips.index_type_desc,
        ddips.alloc_unit_type_desc,
        ddips.index_depth,
        ddips.index_level,
        ddips.avg_fragmentation_in_percent,
        ddips.fragment_count,
        ddips.avg_fragment_size_in_pages,
        ddips.page_count,
        ddips.avg_page_space_used_in_percent,
        ddips.record_count,
        ddips.ghost_record_count,
        ddips.version_ghost_record_count,
        ddips.min_record_size_in_bytes,
        ddips.max_record_size_in_bytes,
        ddips.avg_record_size_in_bytes,
        ddips.forwarded_record_count
    FROM sys.dm_db_index_physical_stats
    (
        @DatabaseID,
        @ObjectID,
        @IndexID,
        @PartitionNumber,
        @Mode
    ) AS ddips;

    RETURN;
END

운영 :

ALTER FUNCTION [dbo].[tfn_IndexOperationalStats_select]
(
    @DatabaseID SMALLINT = 0,
    @TableID INT = 0,
    @IndexID INT = -1,
    @PartitionNumber INT = 0
)
RETURNS @IndexOperationalStats TABLE
(
    database_id SMALLINT NOT NULL,
    object_id INT NOT NULL,
    index_id INT NOT NULL,
    partition_number INT NOT NULL,
    leaf_insert_count BIGINT NULL,
    leaf_delete_count BIGINT NULL,
    leaf_update_count BIGINT NULL,
    leaf_ghost_count BIGINT NULL,
    nonleaf_insert_count BIGINT NULL,
    nonleaf_delete_count BIGINT NULL,
    nonleaf_update_count BIGINT NULL,
    leaf_allocation_count BIGINT NULL,
    nonleaf_allocation_count BIGINT NULL,
    leaf_page_merge_count BIGINT NULL,
    nonleaf_page_merge_count BIGINT NULL,
    range_scan_count BIGINT NULL,
    singleton_lookup_count BIGINT NULL,
    forwarded_fetch_count BIGINT NULL,
    lob_fetch_in_pages BIGINT NULL,
    lob_fetch_in_bytes BIGINT NULL,
    lob_orphan_create_count BIGINT NULL,
    lob_orphan_insert_count BIGINT NULL,
    row_overflow_fetch_in_pages BIGINT NULL,
    row_overflow_fetch_in_bytes BIGINT NULL,
    column_value_push_off_row_count BIGINT NULL,
    column_value_pull_in_row_count BIGINT NULL,
    row_lock_count BIGINT NULL,
    row_lock_wait_count BIGINT NULL,
    row_lock_wait_in_ms BIGINT NULL,
    page_lock_count BIGINT NULL,
    page_lock_wait_count BIGINT NULL,
    page_lock_wait_in_ms BIGINT NULL,
    index_lock_promotion_attempt_count BIGINT NULL,
    index_lock_promotion_count BIGINT NULL,
    page_latch_wait_count BIGINT NULL,
    page_latch_wait_in_ms BIGINT NULL,
    page_io_latch_wait_count BIGINT NULL,
    page_io_latch_wait_in_ms BIGINT NULL
    PRIMARY KEY CLUSTERED
    (
        database_id ASC,
        object_id ASC,
        index_id ASC,
        partition_number ASC
    )
)
AS
BEGIN
    INSERT INTO @IndexOperationalStats
    (
        database_id,
        object_id,
        index_id,
        partition_number,
        leaf_insert_count,
        leaf_delete_count,
        leaf_update_count,
        leaf_ghost_count,
        nonleaf_insert_count,
        nonleaf_delete_count,
        nonleaf_update_count,
        leaf_allocation_count,
        nonleaf_allocation_count,
        leaf_page_merge_count,
        nonleaf_page_merge_count,
        range_scan_count,
        singleton_lookup_count,
        forwarded_fetch_count,
        lob_fetch_in_pages,
        lob_fetch_in_bytes,
        lob_orphan_create_count,
        lob_orphan_insert_count,
        row_overflow_fetch_in_pages,
        row_overflow_fetch_in_bytes,
        column_value_push_off_row_count,
        column_value_pull_in_row_count,
        row_lock_count,
        row_lock_wait_count,
        row_lock_wait_in_ms,
        page_lock_count,
        page_lock_wait_count,
        page_lock_wait_in_ms,
        index_lock_promotion_attempt_count,
        index_lock_promotion_count,
        page_latch_wait_count,
        page_latch_wait_in_ms,
        page_io_latch_wait_count,
        page_io_latch_wait_in_ms
    )
    SELECT
        ddios.database_id,
        ddios.object_id,
        ddios.index_id,
        ddios.partition_number,
        ddios.leaf_insert_count,
        ddios.leaf_delete_count,
        ddios.leaf_update_count,
        ddios.leaf_ghost_count,
        ddios.nonleaf_insert_count,
        ddios.nonleaf_delete_count,
        ddios.nonleaf_update_count,
        ddios.leaf_allocation_count,
        ddios.nonleaf_allocation_count,
        ddios.leaf_page_merge_count,
        ddios.nonleaf_page_merge_count,
        ddios.range_scan_count,
        ddios.singleton_lookup_count,
        ddios.forwarded_fetch_count,
        ddios.lob_fetch_in_pages,
        ddios.lob_fetch_in_bytes,
        ddios.lob_orphan_create_count,
        ddios.lob_orphan_insert_count,
        ddios.row_overflow_fetch_in_pages,
        ddios.row_overflow_fetch_in_bytes,
        ddios.column_value_push_off_row_count,
        ddios.column_value_pull_in_row_count,
        ddios.row_lock_count,
        ddios.row_lock_wait_count,
        ddios.row_lock_wait_in_ms,
        ddios.page_lock_count,
        ddios.page_lock_wait_count,
        ddios.page_lock_wait_in_ms,
        ddios.index_lock_promotion_attempt_count,
        ddios.index_lock_promotion_count,
        ddios.page_latch_wait_count,
        ddios.page_latch_wait_in_ms,
        ddios.page_io_latch_wait_count,
        ddios.page_io_latch_wait_in_ms
    FROM sys.dm_db_index_operational_stats
    (
        @DatabaseID,
        @TableID,
        @IndexID,
        @PartitionNumber
    ) AS ddios;

    RETURN;
END

그런 다음 인덱스 유지 관리 작업에서이 기능을 다음과 같은 방식으로 참조합니다.

DECLARE 
    @DDL NVARCHAR(MAX);

DECLARE ddl_cursor CURSOR
FOR
    SELECT
        CONVERT(NVARCHAR(MAX), DDL.DDL) AS DDL
    FROM
    (
        SELECT
            MasterIndexes.SchemaName,
            MasterIndexes.TableName,
            MasterIndexes.IndexName,
            MasterIndexes.DatabaseID,
            MasterIndexes.ObjectID,
            MasterIndexes.IndexID,
            MasterIndexes.PartitionNumber,
            MasterIndexes.type_desc,
            MasterIndexes.is_unique,
            MasterIndexes.is_primary_key,
            MasterIndexes.is_unique_constraint,
            MasterIndexes.fill_factor,
            MasterIndexes.allow_row_locks,
            MasterIndexes.allow_page_locks,
            MasterIndexes.UpdateStatisticsIndicator,
            1 AS SortInTempDB,
            CASE
                WHEN CONVERT(VARCHAR(100), SERVERPROPERTY('edition')) LIKE 'Enterprise Edition%' THEN 1
                ELSE 0
            END AS OnlineIndicator,
            CASE
                WHEN 
                    ips.avg_fragmentation_in_percent BETWEEN CONVERT(FLOAT, 10) AND CONVERT(FLOAT, 30)
                    AND ips.page_count >= 100
                THEN
                    1
                ELSE
                    0
            END AS ReorganizationIndicator,
            CASE
                WHEN
                (
                    ips.avg_fragmentation_in_percent >= 30
                    AND ips.page_count >= 100
                )
                OR
                (
                    ips.avg_fragmentation_in_percent BETWEEN CONVERT(FLOAT, 10) AND CONVERT(FLOAT, 30)
                    AND ips.page_count < 100
                )
                THEN 
                    1
                ELSE
                    0
            END AS RebuildIndicator
        FROM
        (
            SELECT
                s.name AS SchemaName,
                t.name AS TableName,
                ix.name AS IndexName,
                DB_ID() AS DatabaseID,
                ddps.object_id AS ObjectID,
                ddps.index_id AS IndexID,
                ddps.partition_number AS PartitionNumber,
                ix.type_desc,
                ix.is_unique,   
                ix.is_primary_key,
                ix.is_unique_constraint,
                ix.fill_factor, 
                ix.allow_row_locks,
                ix.allow_page_locks,
                1 AS UpdateStatisticsIndicator  
            FROM sys.schemas AS s

                INNER JOIN sys.tables AS t
                    ON s.schema_id = t.schema_id

                    INNER JOIN sys.indexes AS ix
                        ON t.object_id = ix.object_id

                        INNER JOIN sys.dm_db_partition_stats AS ddps
                            ON ix.object_id = ddps.object_id
                            AND ix.index_id = ddps.index_id

                CROSS APPLY master.dbo.tfn_IndexOperationalStats_select
                (
                    DB_ID(),
                    t.object_id,
                    ix.index_id,
                    NULL
                ) AS ios

            WHERE
                CASE
                    WHEN ddps.row_count = 0 THEN 0
                    ELSE
                    (
                        (
                            CONVERT
                            (
                                FLOAT,
                                (
                                    ios.nonleaf_insert_count + 
                                    ios.nonleaf_update_count + 
                                    ios.leaf_insert_count + 
                                    ios.leaf_update_count
                                )
                            ) /
                            CONVERT
                            (
                                FLOAT,
                                ddps.row_count
                            )
                        ) * 100.0
                    ) 
                END >= 10.0
            AND t.is_ms_shipped = 0
            AND t.name NOT LIKE 'MSmerge%'
            AND ix.index_id > 0
        ) AS MasterIndexes

            CROSS APPLY master.dbo.tfn_IndexPhysicalStats_select
            (
                MasterIndexes.DatabaseID,
                MasterIndexes.ObjectID,
                MasterIndexes.IndexID,
                MasterIndexes.PartitionNumber,
                'SAMPLED'
            ) AS ips
    ) AS MasterIndexList    

        CROSS APPLY
        (
            SELECT          
                'ALTER INDEX ' + 
                MasterIndexList.IndexName + 
                ' ON ' + 
                MasterIndexList.SchemaName + '.' + MasterIndexList.TableName +
                ' REBUILD WITH(' + 
                'FILLFACTOR = ' + 
                    CASE
                        WHEN MasterIndexList.fill_factor = 0 THEN '100'
                        ELSE CONVERT(VARCHAR(3), MasterIndexList.fill_factor)
                    END + ', ' +
                'SORT_IN_TEMPDB = ' + 
                    CASE
                        WHEN MasterIndexList.SortInTempDB = 1 THEN 'ON'
                        ELSE 'OFF'
                    END + ', ' +
                'ONLINE = ' + 
                    CASE
                        WHEN MasterIndexList.OnlineIndicator = 1 THEN 'ON'
                        ELSE 'OFF'
                    END + ', ' + 
                'ALLOW_ROW_LOCKS = ' + 
                    CASE
                        WHEN MasterIndexList.[allow_row_locks] = 1 THEN 'ON'
                        ELSE 'OFF'
                    END + ', ' + 
                'ALLOW_PAGE_LOCKS = ' + 
                    CASE
                        WHEN MasterIndexList.[allow_page_locks] = 1 THEN 'ON'
                        ELSE 'OFF'
                    END + ');' AS [DDL],

                1 AS DDLOrdinal

            WHERE MasterIndexList.RebuildIndicator = 1

            UNION ALL
            SELECT          
                'ALTER INDEX ' + 
                MasterIndexList.IndexName + 
                ' ON ' + 
                MasterIndexList.SchemaName + '.' + MasterIndexList.TableName +
                ' REORGANIZE;' AS [DDL],

                2 AS DDLOrdinal

            WHERE MasterIndexList.ReorganizationIndicator = 1

            UNION ALL
            SELECT
                'UPDATE STATISTICS ' + 
                MasterIndexList.SchemaName + '.' + MasterIndexList.TableName + ' ' + 
                MasterIndexList.IndexName + ' ' + 
                'WITH FULLSCAN;' AS [DDL],

                3 AS DDLOrdinal

            WHERE MasterIndexList.UpdateStatisticsIndicator = 1
            AND MasterIndexList.RebuildIndicator = 0
            AND STATS_DATE(MasterIndexList.ObjectID, MasterIndexList.IndexID) <= DATEADD(hh, -20, GETDATE())
        ) AS [DDL]

    ORDER BY
        ObjectID ASC,
        IndexID ASC,
        DDLOrdinal ASC;

OPEN ddl_cursor;

FETCH NEXT FROM ddl_cursor
INTO @DDL;

WHILE @@FETCH_STATUS = 0
BEGIN

    EXECUTE sys.sp_executesql 
        @stmt = @DDL;

    FETCH NEXT FROM ddl_cursor
    INTO @DDL;
END

CLOSE ddl_cursor;
DEALLOCATE ddl_cursor;
GO

항상 그렇듯이 마일리지는 다를 수 있지만 필요에 따라이 스크립트를 자유롭게 사용 / 변경하십시오.

좋은이,

매트


0

아래 코덱은 ~ 185GB 데이터베이스에서 잘 작동합니다.

DECLARE @dbid int

SET @dbid = DB_ID()

select o.name as ObjectName, 
       O.id as ObjectID,
       I.name as IndexName,
       I.index_id as IndexID,
       I.type,
       I.type_Desc,
       ps.avg_fragmentation_in_percent 

from sysobjects O with (nolock)
inner join sys.indexes i with (nolock) ON O.id = i.object_id 
inner join sys.dm_db_index_physical_stats (@dbid,null,null,null,'LIMITED') ps on ps.database_id = @dbid
                                                                             and ps.object_id = O.id 
                                                                             and ps.index_id = I.index_id
where xtype = 'U'
and LEFT(o.name,2) <> 'MS'
and ps.avg_fragmentation_in_percent > 10
order by o.name 

0

조각난 인덱스에 대한 정보를 수집하는 데 소요되는 시간이 업데이트 통계를 수행하여 3 시간 30 분에서 5 분으로 단축되었음을 알았습니다. Ola Hallengren 업데이트 통계 작업을 사용하여 테이블에 대한 통계를 업데이트해야합니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.