web-dev-qa-db-ja.com

SQL ServerDMVによって提案された欠落インデックスの数の制限の回避策

本番環境では、SQL Serverの各インスタンスに250を超えるデータベース(「OrgDb」と呼ばれるもの)があります。私が現在取り組んでいるプロジェクトは、SQL ServerのDMVによって報告されたすべての欠落インデックスをテレメトリに送信して、これらのorgDbsに送信されたクエリがどの程度うまく機能しているかを事後分析し、場合によっては最適化を行うことを目的としています。

簡単に聞こえますよね?ただし、問題は 欠落しているインデックスの数の最大制限5 DMVが単一のSQLServerでレポートできるのに対し、OrgDbごとに約20の欠落しているインデックス(合計で約5000)があることです。 。

誰かがこの制限の回避策を考えることができますか?私が最初に考えた解決策の1つは、DMVの欠落しているインデックステーブルを削除することでした。

sys.dm_db_missing_index_details
sys.dm_db_missing_index_groups
sys.dm_db_missing_index_group

更新するたびに、これらのテーブルは変更できないことが判明しました。

Error:Ad hoc updates to system catalogs are not allowed.
3
green-i

DMVをリセットすることはできませんが、 この制限を回避し、DMVから行を削除する DMVに記載されているテーブルに小さなフィルター処理されたインデックスを作成し、すぐにそのインデックスを削除することでできます。

例えば:

CREATE INDEX IX_temp
ON dbo.SomeTable(SomeKey)
WHERE SomeKey IS NULL;

DROP INDEX dbo.SomeTable.IX_temp;

このプロセスを自動化するスクリプトを作成しました。

IF OBJECT_ID('dbo.RemoveMissingIndexSuggestions') IS NOT NULL
DROP PROCEDURE RemoveMissingIndexSuggestions;
GO
CREATE PROCEDURE dbo.RemoveMissingIndexSuggestions
(
    @Database SYSNAME = NULL --optional, if NULL, clear all suggestions
                             --if specified, only clear suggestions for that database
    , @Table SYSNAME = NULL --if not NULL, only clear suggestions for the specified table 
)
AS
BEGIN
    /*
        Max Vernon, 2016-04-08
        Inspired by work by Joe Sack and Glenn Berry at
        http://www.sqlskills.com/blogs/joe/clearing-missing-index-suggestions-for-a-single-table/

        Creates one index for each table that is mentioned in sys.dm_db_missing_index_details
        then promply drops that index.  The index is created with a WHERE clause that is likely 
        to eliminate all or almost all rows, and therefore will be created quite quickly.
    */
    SET NOCOUNT ON;
    DECLARE @ObjectName SYSNAME;
    DECLARE @DatabaseName SYSNAME;
    DECLARE @CreateStmt NVARCHAR(MAX);
    DECLARE @DropStmt NVARCHAR(MAX);
    DECLARE @stmt NVARCHAR(MAX);
    DECLARE @msg NVARCHAR(2000);
    DECLARE @vars NVARCHAR(1000);
    DECLARE @Uniquifier NVARCHAR(48);
    SET @vars = '@stmt NVARCHAR(MAX)';
    DECLARE cur CURSOR LOCAL FORWARD_ONLY STATIC
    FOR
    WITH cte AS
    (
        SELECT ObjectName = d.name + '.' + s.name + '.' + o.name
            , DatabaseName = d.name
            , CreateStmt = N'CREATE INDEX [IX_temp] 
ON ' + QUOTENAME(s.name) + N'.' + QUOTENAME(o.name) + N'(' + mid.equality_columns + N') 
WHERE ' 
              + (
                SELECT TOP(1) cols.ColName FROM (
                    SELECT TOP(1) ColName = QUOTENAME(c.name) + N' IS NULL'
                    FROM sys.columns c 
                        INNER JOIN sys.key_constraints kc ON c.object_id = kc.parent_object_id 
                    WHERE c.object_id = o.object_id 
                        AND kc.type_desc = N'PRIMARY_KEY_CONSTRAINT'
                    UNION ALL
                    SELECT TOP(1) QUOTENAME(c.name) + N' = -2147483648'
                    FROM sys.columns c
                        INNER JOIN sys.types ty ON c.system_type_id = ty.system_type_id
                    WHERE ty.name IN 
                        (
                              N'bigint'
                            , N'binary'
                            , N'hierarchyid'
                            , N'int'
                            , N'uniqueidentifier'
                            , N'varbinary'
                        )
                    ) cols
                ) 
                + ';'
                , DropStmt = N'DROP INDEX ' + QUOTENAME(s.name) + N'.' + QUOTENAME(o.name) + '.[IX_temp];'
                , rn = ROW_NUMBER() OVER (PARTITION BY mid.object_id ORDER BY mid.index_handle)
        FROM sys.dm_db_missing_index_details mid
            INNER JOIN sys.objects o ON mid.object_id = o.object_id
            INNER JOIN sys.schemas s ON o.schema_id = s.schema_id
            INNER JOIN sys.databases d ON mid.database_id = d.database_id
        WHERE o.name NOT LIKE '#%' -- ignore temp tables
            AND (d.name = @Database OR @Database IS NULL)
            AND (o.name = @Table OR @Table IS NULL)
    )
    SELECT cte.ObjectName
        , cte.DatabaseName
        , cte.CreateStmt
        , cte.DropStmt
    FROM cte
    WHERE rn = 1;
    OPEN cur;
    FETCH NEXT FROM cur INTO @ObjectName, @DatabaseName, @CreateStmt, @DropStmt;
    WHILE @@FETCH_STATUS = 0
    BEGIN
        SET @msg = 'Flushing ' + @ObjectName + ' indexes.

';
        RAISERROR (@msg, 0, 1) WITH NOWAIT;
        SET @stmt = 'EXEC ' + QUOTENAME(@DatabaseName) + '.sys.sp_executesql @stmt;'
        SET @Uniquifier = CONVERT(NVARCHAR(48), NEWID(), 0);
        SET @CreateStmt = REPLACE(@CreateStmt, '[IX_Temp]', '[IX_Temp_' + @Uniquifier + ']')
        SET @DropStmt = REPLACE(@DropStmt, '[IX_Temp]', '[IX_Temp_' + @Uniquifier + ']')
        SET @CreateStmt = 'SET ANSI_NULLS ON;
SET ANSI_PADDING ON;
SET ANSI_WARNINGS ON;
SET ARITHABORT ON;
SET CONCAT_NULL_YIELDS_NULL ON;
SET NUMERIC_ROUNDABORT OFF;
SET QUOTED_IDENTIFIER ON;
' + @CreateStmt + '

';
        SET @DropStmt = 'SET ANSI_NULLS ON;
SET ANSI_PADDING ON;
SET ANSI_WARNINGS ON;
SET ARITHABORT ON;
SET CONCAT_NULL_YIELDS_NULL ON;
SET NUMERIC_ROUNDABORT OFF;
SET QUOTED_IDENTIFIER ON;
' + @DropStmt + '

';
        RAISERROR (@CreateStmt, 0, 1) WITH NOWAIT;
        RAISERROR (@DropStmt, 0, 1) WITH NOWAIT;
        EXEC sp_executesql @stmt, @vars, @stmt = @CreateStmt;
        EXEC sp_executesql @stmt, @vars, @stmt = @DropStmt;
        FETCH NEXT FROM cur INTO @ObjectName, @DatabaseName, @CreateStmt, @DropStmt;
    END
    CLOSE cur;
    DEALLOCATE cur;
END
GO

これは、@Databaseパラメーターをデータベースの名前に設定して、そのデータベースにのみ関連する推奨インデックスを削除するか、パラメーターなしで実行して、すべての推奨インデックスを削除できます。オプションで、テーブルの名前を@Tableパラメーターに渡すことにより、これを単一のテーブルの提案に制限できます。

EXEC dbo.RemoveMissingIndexSuggestions @Database = 'tempdb', @Table = 'SomeTable';

テーブルごとに最大で1つのインデックスを作成します。インデックスには一意の名前があり、単一の列を使用して構築され、テーブルのPRIMARY KEY(ある場合)の使用が優先されます。テーブルに主キーがない場合、または次のタイプの列の少なくとも1つがない場合、このプロシージャはテーブルを見逃します。

bigint
binary
hierarchyid
int
uniqueidentifier
varbinary

この問題に関する短いブログ投稿を SQLServerScience に書きました。

5
Max Vernon