[sqlserver] 2025/2/8 12:45:02
最近遇到一个奇怪的情况,同样的库及表结构,同样的执行sql语句,在库1查询执行很快,在库2查询执行就会超时,问题是库2的表相对于库1的表中数量要小很多。
然后再次对比了表结构、索引等情况,发现还是一致。
但是在针对这个sql语句做查询计划分析的时候,居然不一样,想到会不会是因为索引导致的。因为之前有耳闻索引失效的问题。
于是针对此查询条件中主要的几个表做索引碎片查询,看看是不是不一样:
-- 查看表中具体的碎片情况 SELECT OBJECT_NAME(ind.OBJECT_ID) AS TableName, ind.name AS IndexName, indexstats.avg_fragmentation_in_percent, indexstats.page_count FROM sys.dm_db_index_physical_stats(DB_ID(), NULL, NULL, NULL, NULL) indexstats INNER JOIN sys.indexes ind ON ind.object_id = indexstats.object_id AND ind.index_id = indexstats.index_id WHERE OBJECT_NAME(ind.OBJECT_ID) = 'YourTableName' -- 替换为你的表名 ORDER BY avg_fragmentation_in_percent DESC;
-- 检查所有索引碎片情况 SELECT OBJECT_NAME(ind.OBJECT_ID) AS TableName, ind.name AS IndexName, indexstats.avg_fragmentation_in_percent FROM sys.dm_db_index_physical_stats(DB_ID(), NULL, NULL, NULL, NULL) indexstats INNER JOIN sys.indexes ind ON ind.object_id = indexstats.object_id AND ind.index_id = indexstats.index_id WHERE indexstats.avg_fragmentation_in_percent > 30;--碎片率
-- 重组索引(碎片率 5-30%) ALTER INDEX IndexName ON TableName REORGANIZE; -- 重建索引(碎片率 >30%) ALTER INDEX IndexName ON TableName REBUILD; -- 重建表的所有索引 ALTER INDEX ALL ON TableName REBUILD;因为我的表数据量很小,所以直接选择重建表的所有索引,执行完后,在查询对应的查询语句后,是正常了,然后看查询计划也是一样的了。
以前一直以为建立了索引就行了,然后就不会去关注。 今天的这个情况在网上搜索下,频繁的数据插入、更新、删除操作、 大批量数据导入、 数据按非聚集索引顺序插入、 页面分裂(Page Split)操作频繁 都会导致索引碎片产生。针对此情况,也可以做以下的措施:
-- 创建定期维护作业 CREATE PROCEDURE sp_MaintainIndexes AS BEGIN SET NOCOUNT ON; DECLARE @TableName NVARCHAR(255) DECLARE @IndexName NVARCHAR(255) DECLARE @Fragmentation FLOAT -- 创建临时表存储需要维护的索引 CREATE TABLE #FragmentedIndexes ( TableName NVARCHAR(255), IndexName NVARCHAR(255), Fragmentation FLOAT ) -- 获取碎片率超过5%的索引 INSERT INTO #FragmentedIndexes SELECT OBJECT_NAME(s.[object_id]), i.name, s.avg_fragmentation_in_percent FROM sys.dm_db_index_physical_stats(DB_ID(), NULL, NULL, NULL, 'LIMITED') s INNER JOIN sys.indexes i ON s.[object_id] = i.[object_id] AND s.index_id = i.index_id WHERE s.avg_fragmentation_in_percent > 5 -- 处理每个碎片索引 DECLARE index_cursor CURSOR FOR SELECT TableName, IndexName, Fragmentation FROM #FragmentedIndexes OPEN index_cursor FETCH NEXT FROM index_cursor INTO @TableName, @IndexName, @Fragmentation WHILE @@FETCH_STATUS = 0 BEGIN BEGIN TRY IF @Fragmentation >= 30 EXEC('ALTER INDEX [' + @IndexName + '] ON [' + @TableName + '] REBUILD;') ELSE EXEC('ALTER INDEX [' + @IndexName + '] ON [' + @TableName + '] REORGANIZE;') -- 更新统计信息 EXEC('UPDATE STATISTICS [' + @TableName + '] ([' + @IndexName + ']) WITH FULLSCAN;') END TRY BEGIN CATCH -- 记录错误信息 PRINT 'Error maintaining index: ' + @IndexName + ' on table: ' + @TableName END CATCH FETCH NEXT FROM index_cursor INTO @TableName, @IndexName, @Fragmentation END CLOSE index_cursor DEALLOCATE index_cursor DROP TABLE #FragmentedIndexes END
-- 创建维护作业 USE [msdb] GO DECLARE @jobId BINARY(16) EXEC msdb.dbo.sp_add_job @job_name=N'IndexMaintenance', @enabled=1, @notify_level_eventlog=0, @notify_level_email=2, @notify_level_page=2, @delete_level=0, @job_id = @jobId OUTPUT -- 添加作业步骤 EXEC msdb.dbo.sp_add_jobstep @job_id=@jobId, @step_name=N'Maintain Indexes', @command=N'EXEC sp_MaintainIndexes' -- 设置作业计划(例如每周日凌晨2点执行) EXEC msdb.dbo.sp_add_jobschedule @job_id=@jobId, @name=N'WeeklyMaintenance', @freq_type=8, @freq_interval=1, -- Sunday @freq_subday_type=1, @freq_subday_interval=0, @freq_relative_interval=0, @freq_recurrence_factor=1, @active_start_time=20000
-- 定期检查未使用的索引 SELECT OBJECT_NAME(i.[object_id]) AS TableName, i.name AS IndexName, i.type_desc AS IndexType, ius.user_seeks, ius.user_scans, ius.user_lookups, ius.user_updates, ius.last_user_seek, ius.last_user_scan, ius.last_user_lookup, ius.last_user_update FROM sys.indexes i LEFT JOIN sys.dm_db_index_usage_stats ius ON i.[object_id] = ius.[object_id] AND i.index_id = ius.index_id WHERE ius.database_id = DB_ID() AND i.type_desc <> 'HEAP' AND (ius.user_seeks = 0 AND ius.user_scans = 0 AND ius.user_lookups = 0) ORDER BY ius.user_updates DESC;
-- 示例维护脚本,手动去执行 DECLARE @TableName NVARCHAR(255) DECLARE @IndexName NVARCHAR(255) DECLARE @Fragmentation FLOAT DECLARE IndexCursor CURSOR FOR SELECT OBJECT_NAME(ind.OBJECT_ID) AS TableName, ind.name AS IndexName, indexstats.avg_fragmentation_in_percent FROM sys.dm_db_index_physical_stats(DB_ID(), NULL, NULL, NULL, NULL) indexstats INNER JOIN sys.indexes ind ON ind.object_id = indexstats.object_id AND ind.index_id = indexstats.index_id WHERE indexstats.avg_fragmentation_in_percent > 5 OPEN IndexCursor FETCH NEXT FROM IndexCursor INTO @TableName, @IndexName, @Fragmentation WHILE @@FETCH_STATUS = 0 BEGIN IF @Fragmentation >= 30 EXEC('ALTER INDEX ' + @IndexName + ' ON ' + @TableName + ' REBUILD;') ELSE EXEC('ALTER INDEX ' + @IndexName + ' ON ' + @TableName + ' REORGANIZE;') FETCH NEXT FROM IndexCursor INTO @TableName, @IndexName, @Fragmentation END CLOSE IndexCursor DEALLOCATE IndexCursor