You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

求助:优化MS SQL Server缺失索引检测脚本,过滤已有索引

Microsoft Dynamics NAV数据库缺失索引检测脚本优化需求

我有一套用于Microsoft Dynamics NAV(基于MS SQL Server)的缺失索引检测SQL脚本,当前存在以下问题:

  • 脚本会识别出部分**已存在索引仅缺少大量包含列(通常占表中列的70%以上)**的情况,我们不希望处理这类场景
  • 同时需要确保不生成包含列数量超过10个的索引

我尝试通过数据库名、表名和等值列过滤已有索引,但以下方案均未成功:

  • ChatGPT生成的脚本无法运行
  • 自行合并现有索引查询脚本时出现错误,无法正常工作
  • 自行修改脚本也报错
  • 未找到有效解决方案

现有缺失索引检测脚本

USE [master]
GO

SET NOCOUNT ON
GO

-- >> Missing Indexes <<
 
-- Filter-Values
declare @threshold_occurrence int, @threshold_impact int, @threshold_age int, @threshold_include int

set @threshold_occurrence = 10 -- Avg. Proposals per Day online; change on demand
set @threshold_impact = 90 -- Percent; change on demand
set @threshold_age = 14 -- last proposal date; change on demand
set @threshold_include = 10 -- maximum number of included columns used in CREATE; change on demand

--

declare @daysonline int
select @daysonline = datediff(dd, create_date, getdate()) + 1 from sys.databases where name = 'tempdb'
declare @dbname sysname, @tsql nvarchar(max)
declare db_cur cursor fast_forward for
  select [name] from sys.databases 
  where [state] = 0 
    --and [name] not in ('master', 'model', 'msdb', 'tempdb', 'SSI')  -- exclude databases
    and [name] in ('XXXX') -- include databases 
  order by [name]
open db_cur
fetch next from db_cur into @dbname
while @@FETCH_STATUS = 0
begin
    set @tsql =
    '
    USE [' + @dbname + ']
    SELECT mig.index_handle, migs.user_seeks, migs.last_user_seek, migs.user_scans, migs.last_user_scan, migs.avg_user_impact,
                                CONVERT(decimal(18,2), migs.user_seeks * migs.avg_total_user_cost * (migs.avg_user_impact * 0.01)) AS [index_advantage],
                                db_name(mid.database_id) as db, object_name(mid.object_id) as object, mid.equality_columns, mid.inequality_columns, mid.included_columns,
                                ''CREATE INDEX [ssi_'' + convert(varchar, mig.index_handle) + ''_'' + replace(replace(replace(convert(varchar(30), getdate(), 120), ''-'',''''), '':'',''''), '''' , ''_'') + ''] ON ['' + OBJECT_SCHEMA_NAME(mid.[object_id]) + ''].['' +  object_name(mid.object_id) + ''] '' + ''('' +
                                  CASE WHEN mid.equality_columns is not null THEN mid.equality_columns ELSE '''' END +
                                  CASE WHEN mid.equality_columns is null AND mid.inequality_columns is not null THEN mid.inequality_columns ELSE '''' END +
                                  CASE WHEN mid.equality_columns is not null AND mid.inequality_columns is not null THEN '', '' + mid.inequality_columns ELSE '''' END + '')'' +
                                  CASE WHEN (mid.included_columns is not null) AND ( (SELECT LEN(mid.included_columns) - LEN(REPLACE(mid.included_columns, '','',''''))) < ' + convert(varchar(5), @threshold_include) + ' ) 
                                  THEN '' INCLUDE ('' + mid.included_columns + '') '' 
                                  ELSE '''' 
                                  END + '' WITH (MAXDOP = 64, ONLINE = OFF, DROP_EXISTING = OFF, ALLOW_PAGE_LOCKS = ON, ALLOW_ROW_LOCKS = ON);''  COLLATE DATABASE_DEFAULT as tsql
    FROM sys.dm_db_missing_index_group_stats AS migs
    INNER JOIN sys.dm_db_missing_index_groups AS mig
                   ON (migs.group_handle = mig.index_group_handle)
    INNER JOIN sys.dm_db_missing_index_details AS mid
                   ON (mig.index_handle = mid.index_handle)
    WHERE (mid.database_id = db_id())
    AND ((migs.user_seeks / ' + convert(varchar(5), @daysonline) + ') >= ' + convert(varchar(5), @threshold_occurrence) + ')  
    AND (migs.avg_user_impact >= ' + convert(varchar(5), @threshold_impact) + ') 
    AND (datediff(dd, migs.last_user_seek, getdate()) <= ' + convert(varchar(5), @threshold_age) + ') -- propsed within period 
    ORDER BY object_name(mid.object_id), replace(replace((mid.equality_columns + mid.inequality_columns), '', '', ''''), '','', ''''), mid.included_columns
    COLLATE DATABASE_DEFAULT;
    '

    --print @tsql
    exec sp_executesql @tsql

    fetch next from db_cur into @dbname
end
close db_cur
deallocate db_cur
GO

已尝试合并的现有索引查询脚本

DECLARE @TableName NVARCHAR(50) = 'Cronus$Sales Price'

;WITH CTE_INDEX_DATA AS (
       SELECT
              SCHEMA_DATA.name AS schema_name,
              TABLE_DATA.name AS table_name,
              INDEX_DATA.name AS index_name,
              STUFF((SELECT  ', ' + COLUMN_DATA_KEY_COLS.name + ' ' + CASE WHEN INDEX_COLUMN_DATA_KEY_COLS.is_descending_key = 1 THEN 'DESC' ELSE 'ASC' END -- Include column order (ASC / DESC)
                                  FROM    sys.tables AS T
                                                INNER JOIN sys.indexes INDEX_DATA_KEY_COLS
                                                ON T.object_id = INDEX_DATA_KEY_COLS.object_id
                                                INNER JOIN sys.index_columns INDEX_COLUMN_DATA_KEY_COLS
                                                ON INDEX_DATA_KEY_COLS.object_id = INDEX_COLUMN_DATA_KEY_COLS.object_id
                                                AND INDEX_DATA_KEY_COLS.index_id = INDEX_COLUMN_DATA_KEY_COLS.index_id
                                                INNER JOIN sys.columns COLUMN_DATA_KEY_COLS
                                                ON T.object_id = COLUMN_DATA_KEY_COLS.object_id
                                                AND INDEX_COLUMN_DATA_KEY_COLS.column_id = COLUMN_DATA_KEY_COLS.column_id
                                  WHERE   INDEX_DATA.object_id = INDEX_DATA_KEY_COLS.object_id
                                                AND INDEX_DATA.index_id = INDEX_DATA_KEY_COLS.index_id
                                                AND INDEX_COLUMN_DATA_KEY_COLS.is_included_column = 0
                                  ORDER BY INDEX_COLUMN_DATA_KEY_COLS.key_ordinal
                                  FOR XML PATH('')), 1, 2, '') AS key_column_list ,
          STUFF(( SELECT  ', ' + COLUMN_DATA_INC_COLS.name
                                  FROM    sys.tables AS T
                                                INNER JOIN sys.indexes INDEX_DATA_INC_COLS
                                                ON T.object_id = INDEX_DATA_INC_COLS.object_id
                                                INNER JOIN sys.index_columns INDEX_COLUMN_DATA_INC_COLS
                                                ON INDEX_DATA_INC_COLS.object_id = INDEX_COLUMN_DATA_INC_COLS.object_id
                                                AND INDEX_DATA_INC_COLS.index_id = INDEX_COLUMN_DATA_INC_COLS.index_id
                                                INNER JOIN sys.columns COLUMN_DATA_INC_COLS
                                                ON T.object_id = COLUMN_DATA_INC_COLS.object_id
                                                AND INDEX_COLUMN_DATA_INC_COLS.column_id = COLUMN_DATA_INC_COLS.column_id
                                  WHERE   INDEX_DATA.object_id = INDEX_DATA_INC_COLS.object_id
                                                AND INDEX_DATA.index_id = INDEX_DATA_INC_COLS.index_id
                                                AND INDEX_COLUMN_DATA_INC_COLS.is_included_column = 1
                                  ORDER BY INDEX_COLUMN_DATA_INC_COLS.key_ordinal
                                  FOR XML PATH('')), 1, 2, '') AS include_column_list,
       INDEX_DATA.is_disabled -- Check if index is disabled before determining which dupe to drop (if applicable)
       FROM sys.indexes INDEX_DATA
       INNER JOIN sys.tables TABLE_DATA
       ON TABLE_DATA.object_id = INDEX_DATA.object_id
       INNER JOIN sys.schemas SCHEMA_DATA
       ON SCHEMA_DATA.schema_id = TABLE_DATA.schema_id
       WHERE TABLE_DATA.is_ms_shipped = 0
       AND INDEX_DATA.type_desc IN ('NONCLUSTERED', 'CLUSTERED')
       --AND INDEX_DATA.name NOT LIKE '%$%'
       AND [TABLE_DATA].[object_id] = (select object_id from sys.objects where name = @TableName)
)
SELECT
       [DUPE1].*
       ,(SELECT COUNT(*) FROM CTE_INDEX_DATA DUPE2
         WHERE DUPE1.schema_name = DUPE2.schema_name
         AND DUPE1.table_name = DUPE2.table_name
         AND (DUPE1.key_column_list LIKE LEFT(DUPE2.key_column_list, LEN(DUPE1.key_column_list)) OR DUPE2.key_column_list LIKE LEFT(DUPE1.key_column_list, LEN(DUPE2.key_column_list)))
         AND DUPE1.index_name <> DUPE2.index_name) AS [CountOfSimilarIndexes]
FROM CTE_INDEX_DATA DUPE1
--WHERE EXISTS      /*Einschränkung auf Indexe, die in ähnlicher Form noch mal vorliegen*/
--(SELECT * FROM CTE_INDEX_DATA DUPE2
-- WHERE DUPE1.schema_name = DUPE2.schema_name
-- AND DUPE1.table_name = DUPE2.table_name
-- AND (DUPE1.key_column_list LIKE LEFT(DUPE2.key_column_list, LEN(DUPE1.key_column_list)) OR DUPE2.key_column_list LIKE LEFT(DUPE1.key_column_list, LEN(DUPE2.key_column_list)))
-- AND DUPE1.index_name <> DUPE2.index_name)
ORDER BY DUPE1.table_name, DUPE1.key_column_list

内容的提问来源于stack exchange,提问作者disnamedyna

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.17 20:07:01