Réorganiser l'index ou reconstruire l'index dans le plan de maintenance SQL Server
-
08-06-2019 - |
Question
Dans les règles SSW visant à améliorer la base de données SQL Server, vous trouverez un exemple de plan de maintenance complet de la base de données : SSW.Dans l'exemple, ils exécutent à la fois un index de réorganisation, puis un index de reconstruction, puis une mise à jour des statistiques.Est-ce que cela a un sens ?Je pensais que Reorganize Index était une version rapide mais moins efficace de Rebuild Index ?et qu'une reconstruction d'index mettrait également à jour automatiquement les statistiques (au moins sur l'index clusterisé).
La solution
Faire un REORGANIZE
et puis un REBUILD
sur les mêmes index est inutile, car toute modification par le REORGANIZE
serait perdu en faisant le REBUILD
.
Pire encore, dans le diagramme du plan de maintenance de SSW, il effectue une SHRINK
premièrement, qui fragmente les index comme effet secondaire de la façon dont il libère de l'espace.Puis le REBUILD
alloue à nouveau plus d'espace aux fichiers de base de données comme espace de travail pendant le REBUILD
opération.
REORGANIZE
est une opération en ligne qui défragmente les pages feuilles d'un index clusterisé ou non clusterisé page par page en utilisant peu d'espace de travail supplémentaire.REBUILD
est une opération en ligne dans les éditions Enterprise, hors ligne dans d'autres éditions et utilise à nouveau autant d'espace de travail supplémentaire que la taille de l'index.Il crée une nouvelle copie de l'index puis supprime l'ancienne, éliminant ainsi la fragmentation.Les statistiques sont recalculées par défaut dans le cadre de cette opération, mais cela peut être désactivé.
Voir Réorganisation et reconstruction des index pour plus d'informations.
Ne pas utiliser SHRINK
sauf avec le TRUNCATEONLY
option et même dans ce cas, si le fichier grossit à nouveau, vous devriez réfléchir sérieusement à la question de savoir si cela est nécessaire :
Autres conseils
La réorganisation et la reconstruction sont des choses différentes.
Réorganiser:c'est une défragmentation pour les index.Prend le(s) index existant(s) et défragmente les pages existantes.Cependant si les pages ne sont pas contiguës, elles restent comme avant.Seul le contenu des pages change.
Reconstruire:en fait, il supprime l'index et le reconstruit à partir de zéro.Cela signifie que vous obtiendrez un tout nouvel index, avec des pages défragmentées et contiguës.
De plus, avec la reconstruction, vous pouvez modifier le partitionnement ou les groupes de fichiers, mais avec la réorganisation, vous pouvez défragmenter non seulement l'ensemble de l'index, mais également une seule partition de l'index.
La mise à jour des statistiques est automatique sur les index clusterisés, mais pas sur les index non clusterisés.
Avant d’envisager la maintenance des index, il est important de répondre à deux questions principales :
- Quel est le degré de fragmentation ?
- Quelle est l’action appropriée ?Réorganiser ou reconstruire ?
Comme décrit dans cet article http://solutioncenter.apexsql.com/why-when-and-how-to-rebuild-and-reorganize-sql-server-indexes/, et pour vous aider à déterminer si vous devez effectuer une reconstruction ou une réorganisation d'index, veuillez comprendre ce qui suit :
La réorganisation de l'index est un processus dans lequel SQL Server parcourt l'index existant et le nettoie.La reconstruction de l'index est un processus intensif dans lequel l'index est supprimé puis recréé à partir de zéro avec une structure entièrement nouvelle, exempte de fragments empilés et de pages vides.
Alors que la réorganisation de l'index est une pure opération de nettoyage qui laisse l'état du système tel quel sans verrouiller les tables et vues affectées, le processus de reconstruction verrouille la table affectée pendant toute la période de reconstruction, ce qui peut entraîner de longs temps d'arrêt qui ne pourraient pas être acceptables dans certains environnements.En gardant cela à l’esprit, il est clair que la reconstruction de l’index est un processus avec une solution « plus solide », mais il a un prix : d’éventuels verrous longs sur les tables indexées concernées.
D’un autre côté, la réorganisation de l’index est un processus « léger » qui résoudra la fragmentation de manière moins efficace – puisque l’index nettoyé sera toujours deuxième après le nouveau entièrement créé à partir de zéro.Mais la réorganisation de l'index est bien meilleure du point de vue de l'efficacité, car elle ne verrouille pas la table indexée affectée au cours de l'opération.
L'article mentionné ci-dessus explique également comment réorganiser et reconstruire les index à l'aide de SSMS, T-SQL (pour réorganiser/reconstruire les index dans une table) et un outil tiers appelé ApexSQL Backup.
Lors de la réorganisation d'un index, si l'index est réparti sur deux fichiers physiques ou plus, les données ne seront défragmentées que dans le fichier de données.Les pages ne sont pas déplacées d'un fichier de données à un autre.
Lorsque l'index est dans un seul fichier, la réorganisation et la réindexation auront le même résultat final.
Parfois, la réorganisation sera plus rapide, et parfois la réindexation sera plus rapide en fonction du degré de fragmentation de l'index.Moins l'index est fragmenté, une réorganisation sera plus rapide, plus fragmentée, plus la réorganisation sera lente, mais plus une réindexation sera rapide.
Exactement quoi Biri dit.Voici comment réindexer une base de données entière :
EXEC [sp_MSforeachtable] @command1="RAISERROR('DBCC DBREINDEX(''?'') ...',10,1) WITH NOWAIT DBCC DBREINDEX('?')"
J'utilise ce 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
et créez un travail qui exécute ce SP chaque semaine.
Mieux encore, c'est :
EXEC sp_MSforeachtable 'ALTER INDEX ALL ON ? REINDEX'
ou
EXEC sp_MSforeachtable 'ALTER INDEX ALL ON ? REORGANIZE'
Mes deux centimes...Cette méthode suit les spécifications décrites sur Tech Net : 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
J'ai fait des recherches sur le Web et j'ai trouvé de bons articles.À l'époque, j'ai écrit la fonction et le script ci-dessous qui permettent de réorganiser, recréer ou reconstruire tous les index d'une base de données.
Vous devrez peut-être d'abord lire Cet article pour comprendre pourquoi nous ne recréons pas simplement tous les index.
Deuxièmement, nous avons besoin d'une fonction pour créer un script de création pour l'index.Donc Cet article peut aider.Je partage également la fonction de travail ci-dessous.
Dernière étape créant une boucle while pour rechercher et organiser tous les index de la base de données. Cette vidéo est un excellent exemple pour faire cela.
Fonction:
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 pendant un moment :
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