在 Sql Server 维护计划中重组索引与重建索引
Posted
技术标签:
【中文标题】在 Sql Server 维护计划中重组索引与重建索引【英文标题】:Reorganise index vs Rebuild Index in Sql Server Maintenance plan 【发布时间】:2008-08-11 07:02:46 【问题描述】:在更好的 SQL Server 数据库的 SSW 规则中有一个完整的数据库维护计划的示例:SSW。在示例中,他们同时运行 Reorganize Index、Rebuild Index 和 Update Statistics。这有什么意义吗?我认为 Reorganize Index 是 Rebuild Index 的一个快速但效率较低的版本?并且索引重建也会自动更新统计信息(至少在聚集索引上)。
【问题讨论】:
【参考方案1】:重组和重建是不同的事情。
重组:它是索引的碎片整理。获取现有索引并对现有页面进行碎片整理。但是,如果页面不是连续的,它们会像以前一样保持不变。只有页面的内容在变化。
重建:实际上它会删除索引并从头开始重建它。这意味着您将获得一个全新的索引,其中包含碎片整理和连续的页面。
此外,通过重新构建,您可以更改分区或文件组,但通过重新组织,您不仅可以对整个索引进行碎片整理,还可以对索引的一个分区进行碎片整理。
更新统计信息在聚簇索引上是自动的,但在非聚簇索引上不是。
【讨论】:
对,但是在同一个维护子计划中同时进行重组和重建有什么用吗? 实际上,根据在线图书msdn.microsoft.com/en-us/library/ms189858.aspx Reorg 确实会重新组织页面以使它们在物理上是连续的。这是确切的报价:“重组索引通过对叶级页面进行物理重新排序以匹配叶节点的逻辑顺序(从左到右)来对表和视图上的聚集和非聚集索引的叶级进行碎片整理。让页面按顺序排列提高索引扫描性能。索引在分配给它的现有页面内重新组织;没有分配新页面。" @MichaelK.Campbell:您的引述有点断章取义。虽然 ReOrg 将对页面进行重新排序,但它只会在中间级别节点指向的最低级别对它们进行重新排序。在 ReOrg 之后,整个索引中的所有页面都不能保证是连续的。这里有一个更好的解释:dba.stackexchange.com/a/36817/6816【参考方案2】:在考虑维护索引之前,重要的是要回答两个主要问题:
-
碎片化程度如何?
什么是适当的操作?重组还是重建?
如本文http://solutioncenter.apexsql.com/why-when-and-how-to-rebuild-and-reorganize-sql-server-indexes/ 中所述,为了帮助您确定是否应该执行索引重建或索引重组,请了解以下内容:
索引重组是 SQL Server 遍历现有索引并清理它的过程。索引重建是一个繁重的过程,其中索引被删除,然后以全新的结构从头开始重新创建,没有所有堆积的碎片和空白页面。
虽然索引重组是一种纯粹的清理操作,它使系统状态保持原样而不锁定受影响的表和视图,但重建过程会在整个重建期间锁定受影响的表,这可能会导致较长的停机时间这在某些环境中是无法接受的。 考虑到这一点,很明显,索引重建是一个具有“更强”解决方案的过程,但它也有代价——受影响的索引表可能会长时间锁定。
另一方面,索引重组是一个“轻量级”过程,它会以不太有效的方式解决碎片问题——因为清理过的索引总是仅次于完全从头开始制作的新索引。但是从效率的角度来看,重组索引要好得多,因为它不会在操作过程中锁定受影响的索引表。
上述文章还解释了如何使用 SSMS、T-SQL(在表中重组/重建索引)和名为 ApexSQL Backup 的第三方工具来重组和重建索引。
【讨论】:
重建索引时,是否需要更新表statistcs?特别是如果有非聚集索引?【参考方案3】:在相同的索引上执行REORGANIZE
和REBUILD
是没有意义的,因为执行REBUILD
会丢失REORGANIZE
的任何更改。
比这更糟糕的是,在 SSW 的维护计划图中,它首先执行SHRINK
,这会将索引碎片化,这是它释放空间方式的副作用。然后REBUILD
在REBUILD
操作期间再次为数据库文件分配更多空间作为工作空间。
REORGANIZE
是一种在线操作,它使用很少的额外工作空间逐页对聚集或非聚集索引中的叶页进行碎片整理。
REBUILD
在企业版中是联机操作,在其他版本中是脱机操作,并且再次使用与索引大小一样多的额外工作空间。它创建索引的新副本,然后删除旧的副本,从而消除碎片。默认情况下,统计信息会作为此操作的一部分重新计算,但可以禁用。
更多信息请参见Reorganizing and Rebuilding Indexes。
不要使用SHRINK
,除非使用TRUNCATEONLY
选项,即使文件会再次增长,那么您应该认真考虑是否有必要:
sqlservercentral_SHRINKFILE
【讨论】:
令人惊讶的是有多少在线“权威”是完全不正确和误导的,即建议您应该对数据库进行缩减! 不正确。如果您首先“重组”,则可能会压缩数据页。这是所有版本的 SQL Server 中的在线操作,可以逐步完成。完全重建需要更多资源。通过首先进行重组并随着时间的推移逐渐压缩数据页面,您将减少稍后重建所需的 I/O,因为它将读取更少的数据页面,从而导致它执行更少的 I/O 并使用更少的内存。这将是可衡量的。说重组对后续重建没有影响是无稽之谈,IMO。【参考方案4】:在对索引进行重组时,如果索引分布在两个或多个物理文件中,则数据只会在数据文件中进行碎片整理。页面不会从一个数据文件移动到另一个数据文件。
当索引在单个文件中时,reorg 和 reindex 将具有相同的最终结果。
有时重组会更快,有时重建索引会更快,具体取决于索引的碎片程度。索引碎片越少,重组速度越快,碎片越多,重组速度越慢,但重组速度越快。
【讨论】:
【参考方案5】:正是Biri 所说的。以下是我将如何重新索引整个数据库:
EXEC [sp_MSforeachtable] @command1="RAISERROR('DBCC DBREINDEX(''?'') ...',10,1) WITH NOWAIT DBCC DBREINDEX('?')"
【讨论】:
现在首选的方法是不使用更改索引 - docs.microsoft.com/en-us/sql/t-sql/statements/…【参考方案6】:我用这个SP
CREATE PROCEDURE dbo.[IndexRebuild]
AS
DECLARE @TableName NVARCHAR(500);
DECLARE @SQLIndex NVARCHAR(MAX);
DECLARE @RowCount INT;
DECLARE @Counter INT;
DECLARE @IndexAnalysis TABLE
(
AnalysisID INT IDENTITY(1, 1)
NOT NULL
PRIMARY KEY ,
TableName NVARCHAR(500) ,
SQLText NVARCHAR(MAX) ,
IndexDepth INT ,
AvgFragmentationInPercent FLOAT ,
FragmentCount BIGINT ,
AvgFragmentSizeInPages FLOAT ,
PageCount BIGINT
)
BEGIN
INSERT INTO @IndexAnalysis
SELECT [objects].name ,
'ALTER INDEX [' + [indexes].name + '] ON ['
+ [schemas].name + '].[' + [objects].name + '] '
+ ( CASE WHEN ( [dm_db_index_physical_stats].avg_fragmentation_in_percent >= 20
AND [dm_db_index_physical_stats].avg_fragmentation_in_percent < 40
) THEN 'REORGANIZE'
WHEN [dm_db_index_physical_stats].avg_fragmentation_in_percent > = 40
THEN 'REBUILD'
END ) AS zSQL ,
[dm_db_index_physical_stats].index_depth ,
[dm_db_index_physical_stats].avg_fragmentation_in_percent ,
[dm_db_index_physical_stats].fragment_count ,
[dm_db_index_physical_stats].avg_fragment_size_in_pages ,
[dm_db_index_physical_stats].page_count
FROM [sys].[dm_db_index_physical_stats](DB_ID(), NULL, NULL,
NULL, 'LIMITED') AS [dm_db_index_physical_stats]
INNER JOIN [sys].[objects] AS [objects] ON ( [dm_db_index_physical_stats].[object_id] = [objects].[object_id] )
INNER JOIN [sys].[schemas] AS [schemas] ON ( [objects].[schema_id] = [schemas].[schema_id] )
INNER JOIN [sys].[indexes] AS [indexes] ON ( [dm_db_index_physical_stats].[object_id] = [indexes].[object_id]
AND [dm_db_index_physical_stats].index_id = [indexes].index_id
)
WHERE index_type_desc <> 'HEAP'
AND [dm_db_index_physical_stats].avg_fragmentation_in_percent > 20
END
SELECT @RowCount = COUNT(AnalysisID)
FROM @IndexAnalysis
SET @Counter = 1
WHILE @Counter <= @RowCount
BEGIN
SELECT @SQLIndex = SQLText
FROM @IndexAnalysis
WHERE AnalysisID = @Counter
EXECUTE sp_executesql @SQLIndex
SET @Counter = @Counter + 1
END
GO
并创建一个每周执行此 SP 的作业。
【讨论】:
【参考方案7】:更好的是:
EXEC sp_MSforeachtable 'ALTER INDEX ALL ON ? REINDEX'
或
EXEC sp_MSforeachtable 'ALTER INDEX ALL ON ? REORGANIZE'
【讨论】:
【参考方案8】:我在网络上进行了研究,发现了一些不错的文章。我在下面编写了函数和脚本,用于重组、重新创建或重建数据库中的所有索引。
首先您可能需要阅读this article 以了解我们为什么不只是重新创建所有索引。
其次,我们需要一个函数来为索引构建创建脚本。所以this article 可能会有所帮助。我也在下面分享工作功能。
最后一步是创建一个while循环来查找和组织数据库中的所有索引。 This video 是一个很好的例子。
功能:
create function GetIndexCreateScript(
@index_name nvarchar(100)
)
returns nvarchar(max)
as
begin
declare @Return varchar(max)
SELECT @Return = ' CREATE ' +
CASE WHEN I.is_unique = 1 THEN ' UNIQUE ' ELSE '' END +
I.type_desc COLLATE DATABASE_DEFAULT +' INDEX ' +
I.name + ' ON ' +
Schema_name(T.Schema_id)+'.'+T.name + ' ( ' +
KeyColumns + ' ) ' +
ISNULL(' INCLUDE ('+IncludedColumns+' ) ','') +
ISNULL(' WHERE '+I.Filter_definition,'') + ' WITH ( ' +
CASE WHEN I.is_padded = 1 THEN ' PAD_INDEX = ON ' ELSE ' PAD_INDEX = OFF ' END + ',' +
'FILLFACTOR = '+CONVERT(CHAR(5),CASE WHEN I.Fill_factor = 0 THEN 100 ELSE I.Fill_factor END) + ',' +
-- default value
'SORT_IN_TEMPDB = OFF ' + ',' +
CASE WHEN I.ignore_dup_key = 1 THEN ' IGNORE_DUP_KEY = ON ' ELSE ' IGNORE_DUP_KEY = OFF ' END + ',' +
CASE WHEN ST.no_recompute = 0 THEN ' STATISTICS_NORECOMPUTE = OFF ' ELSE ' STATISTICS_NORECOMPUTE = ON ' END + ',' +
-- default value
' DROP_EXISTING = ON ' + ',' +
-- default value
' ONLINE = OFF ' + ',' +
CASE WHEN I.allow_row_locks = 1 THEN ' ALLOW_ROW_LOCKS = ON ' ELSE ' ALLOW_ROW_LOCKS = OFF ' END + ',' +
CASE WHEN I.allow_page_locks = 1 THEN ' ALLOW_PAGE_LOCKS = ON ' ELSE ' ALLOW_PAGE_LOCKS = OFF ' END + ' ) ON [' +
DS.name + ' ] '
FROM sys.indexes I
JOIN sys.tables T ON T.Object_id = I.Object_id
JOIN sys.sysindexes SI ON I.Object_id = SI.id AND I.index_id = SI.indid
JOIN (SELECT * FROM (
SELECT IC2.object_id , IC2.index_id ,
STUFF((SELECT ' , ' + C.name + CASE WHEN MAX(CONVERT(INT,IC1.is_descending_key)) = 1 THEN ' DESC ' ELSE ' ASC ' END
FROM sys.index_columns IC1
JOIN Sys.columns C
ON C.object_id = IC1.object_id
AND C.column_id = IC1.column_id
AND IC1.is_included_column = 0
WHERE IC1.object_id = IC2.object_id
AND IC1.index_id = IC2.index_id
GROUP BY IC1.object_id,C.name,index_id
ORDER BY MAX(IC1.key_ordinal)
FOR XML PATH('')), 1, 2, '') KeyColumns
FROM sys.index_columns IC2
--WHERE IC2.Object_id = object_id('Person.Address') --Comment for all tables
GROUP BY IC2.object_id ,IC2.index_id) tmp3 )tmp4
ON I.object_id = tmp4.object_id AND I.Index_id = tmp4.index_id
JOIN sys.stats ST ON ST.object_id = I.object_id AND ST.stats_id = I.index_id
JOIN sys.data_spaces DS ON I.data_space_id=DS.data_space_id
JOIN sys.filegroups FG ON I.data_space_id=FG.data_space_id
LEFT JOIN (SELECT * FROM (
SELECT IC2.object_id , IC2.index_id ,
STUFF((SELECT ' , ' + C.name
FROM sys.index_columns IC1
JOIN Sys.columns C
ON C.object_id = IC1.object_id
AND C.column_id = IC1.column_id
AND IC1.is_included_column = 1
WHERE IC1.object_id = IC2.object_id
AND IC1.index_id = IC2.index_id
GROUP BY IC1.object_id,C.name,index_id
FOR XML PATH('')), 1, 2, '') IncludedColumns
FROM sys.index_columns IC2
--WHERE IC2.Object_id = object_id('Person.Address') --Comment for all tables
GROUP BY IC2.object_id ,IC2.index_id) tmp1
WHERE IncludedColumns IS NOT NULL ) tmp2
ON tmp2.object_id = I.object_id AND tmp2.index_id = I.index_id
WHERE I.is_primary_key = 0 AND I.is_unique_constraint = 0
AND I.[name] = @index_name
return @Return
end
Sql for while:
declare @RebuildIndex Table(
IndexId int identity(1,1),
IndexName varchar(100),
TableSchema varchar(50),
TableName varchar(100),
Fragmentation decimal(18,2)
)
insert into @RebuildIndex (IndexName,TableSchema,TableName,Fragmentation)
SELECT
B.[name] as 'IndexName',
Schema_Name(O.[schema_id]) as 'TableSchema',
OBJECT_NAME(A.[object_id]) as 'TableName',
A.[avg_fragmentation_in_percent] Fragmentation
FROM sys.dm_db_index_physical_stats(db_id(),NULL,NULL,NULL,'LIMITED') A
INNER JOIN sys.indexes B ON A.[object_id] = B.[object_id] and A.index_id = B.index_id
INNER JOIN sys.objects O ON O.[object_id] = B.[object_id]
where B.[name] is not null and B.is_primary_key = 0 AND B.is_unique_constraint = 0 and A.[avg_fragmentation_in_percent] >= 5
--select * from @RebuildIndex
declare @begin int = 1
declare @max int
select @max = Max(IndexId) from @RebuildIndex
declare @IndexName varchar(100), @TableSchema varchar(50), @TableName varchar(100) , @Fragmentation decimal(18,2)
while @begin <= @max
begin
Select @IndexName = IndexName from @RebuildIndex where IndexId = @begin
select @TableSchema = TableSchema from @RebuildIndex where IndexId = @begin
select @TableName = TableName from @RebuildIndex where IndexId = @begin
select @Fragmentation = Fragmentation from @RebuildIndex where IndexId = @begin
declare @sql nvarchar(max)
if @Fragmentation < 31
begin
set @sql = 'ALTER INDEX ['+@IndexName+'] ON ['+@TableSchema+'].['+@TableName+'] REORGANIZE WITH ( LOB_COMPACTION = ON )'
print 'Reorganized Index ' + @IndexName + ' for ' + @TableName + ' Fragmentation was ' + convert(nvarchar(18),@Fragmentation)
end
else
begin
set @sql = (select dbo.GetIndexCreateScript(@IndexName))
if(@sql is not null)
begin
print 'Recreated Index ' + @IndexName + ' for ' + @TableName + ' Fragmentation was ' + convert(nvarchar(18),@Fragmentation)
end
else
begin
set @sql = 'ALTER INDEX ['+@IndexName+'] ON ['+@TableSchema+'].['+@TableName+'] REBUILD PARTITION = ALL WITH (ONLINE = ON)'
print 'Rebuilded Index ' + @IndexName + ' for ' + @TableName + ' Fragmentation was ' + convert(nvarchar(18),@Fragmentation)
end
end
execute(@sql)
set @begin = @begin+1
end
【讨论】:
【参考方案9】:我的两分钱...此方法遵循技术网上概述的规范:http://technet.microsoft.com/en-us/library/ms189858(v=sql.105).aspx
USE [MyDbName]
GO
SET ANSI_NULLS OFF
GO
SET QUOTED_IDENTIFIER OFF
GO
CREATE PROCEDURE [maintenance].[IndexFragmentationCleanup]
AS
DECLARE @reIndexRequest VARCHAR(1000)
DECLARE reIndexList CURSOR
FOR
SELECT INDEX_PROCESS
FROM (
SELECT CASE
WHEN avg_fragmentation_in_percent BETWEEN 5
AND 30
THEN 'ALTER INDEX [' + i.NAME + '] ON [' + t.NAME + '] REORGANIZE;'
WHEN avg_fragmentation_in_percent > 30
THEN 'ALTER INDEX [' + i.NAME + '] ON [' + t.NAME + '] REBUILD with(ONLINE=ON);'
END AS INDEX_PROCESS
,avg_fragmentation_in_percent
,t.NAME
FROM sys.dm_db_index_physical_stats(NULL, NULL, NULL, NULL, NULL) AS a
INNER JOIN sys.indexes AS i ON a.object_id = i.object_id
AND a.index_id = i.index_id
INNER JOIN sys.tables t ON t.object_id = i.object_id
WHERE i.NAME IS NOT NULL
) PROCESS
WHERE PROCESS.INDEX_PROCESS IS NOT NULL
ORDER BY avg_fragmentation_in_percent DESC
OPEN reIndexList
FETCH NEXT
FROM reIndexList
INTO @reIndexRequest
WHILE @@FETCH_STATUS = 0
BEGIN
BEGIN TRY
PRINT @reIndexRequest;
EXEC (@reIndexRequest);
END TRY
BEGIN CATCH
DECLARE @ErrorMessage NVARCHAR(4000);
DECLARE @ErrorSeverity INT;
DECLARE @ErrorState INT;
SELECT @ErrorMessage = 'UNABLE TO CLEAN UP INDEX WITH: ' + @reIndexRequest + ': MESSAGE GIVEN: ' + ERROR_MESSAGE()
,@ErrorSeverity = 9
,@ErrorState = ERROR_STATE();
END CATCH;
FETCH NEXT
FROM reIndexList
INTO @reIndexRequest
END
CLOSE reIndexList;
DEALLOCATE reIndexList;
RETURN 0
GO
【讨论】:
这很好,但它确实会导致重复,这意味着重组或重建可能会发生多次,应该避免以上是关于在 Sql Server 维护计划中重组索引与重建索引的主要内容,如果未能解决你的问题,请参考以下文章