Реорганизация индекса и перестроение индекса в плане обслуживания Sql Server
-
08-06-2019 - |
Вопрос
В правилах SSW для улучшения базы данных SQL Server приведен пример полного плана обслуживания базы данных: ЮЮЗ.В этом примере они запускают индекс реорганизации, затем индекс перестроения, а затем обновление статистики.Есть ли в этом какой-то смысл?Я думал, что Reorganize Index — это быстрая, но менее эффективная версия Rebuild Index?и что перестроение индекса также автоматически обновит статистику (по крайней мере, в кластерном индексе).
Решение
Делаю REORGANIZE
а затем REBUILD
по одним и тем же показателям бессмысленно, так как любые изменения со стороны REORGANIZE
было бы потеряно, если бы вы сделали REBUILD
.
Хуже того, на диаграмме плана обслуживания из SSW выполняется SHRINK
во-первых, который фрагментирует индексы как побочный эффект освобождения места.Тогда REBUILD
снова выделяет больше места для файлов базы данных в качестве рабочего пространства во время REBUILD
операция.
REORGANIZE
— это онлайн-операция, которая постранично дефрагментирует конечные страницы в кластерном или некластеризованном индексе, используя при этом мало дополнительного рабочего пространства.REBUILD
это онлайн-операция в выпусках Enterprise и автономная операция в других выпусках, и она снова использует столько же дополнительного рабочего пространства, сколько и размер индекса.Он создает новую копию индекса, а затем удаляет старую, избавляясь таким образом от фрагментации.Статистика пересчитывается по умолчанию как часть этой операции, но это можно отключить.
Видеть Реорганизация и восстановление индексов Чтобы получить больше информации.
Не используйте SHRINK
кроме как с TRUNCATEONLY
вариант, и даже в этом случае, если файл снова вырастет, вам следует хорошенько подумать, необходимо ли это:
Другие советы
Реорганизация и перестройка – это разные вещи.
Реорганизовать:это дефрагментация индексов.Берет существующие индексы и дефрагментирует существующие страницы.Однако, если страницы не расположены подряд, они остаются прежними.Меняется только содержание страниц.
Перестроить:на самом деле он удаляет индекс и перестраивает его с нуля.Это означает, что вы получите совершенно новый индекс с дефрагментированными и смежными страницами.
Более того, с помощью перестроения вы можете изменить разделы или группы файлов, а с помощью реорганизации вы можете дефрагментировать не только весь индекс, но и только один раздел индекса.
Статистика обновления выполняется автоматически для кластеризованных индексов, но не для некластеризованных.
Прежде чем рассматривать обслуживание индексов, важно ответить на два основных вопроса:
- Какова степень фрагментации?
- Каково соответствующее действие?Реорганизовать или перестроить?
Как описано в этой статье http://solutioncenter.apexsql.com/why-when-and-how-to-rebuild-and-reorganize-sql-server-indexes/, и чтобы помочь вам определить, следует ли вам выполнить перестроение индекса или реорганизацию индекса, поймите следующее:
Реорганизация индекса — это процесс, при котором SQL Server просматривает существующий индекс и очищает его.Перестроение индекса — это трудоемкий процесс, при котором индекс удаляется, а затем воссоздается с нуля с совершенно новой структурой, свободной от всех скопившихся фрагментов и пустых страниц.
Хотя реорганизация индекса представляет собой чистую операцию очистки, которая оставляет состояние системы как есть, без блокировки затронутых таблиц и представлений, процесс перестроения блокирует затронутую таблицу на весь период перестроения, что может привести к длительным простоям, которые неприемлемы в некоторые среды.Учитывая это, становится ясно, что перестроение индекса — это процесс с «более сильным» решением, но он имеет свою цену — возможные длительные блокировки затронутых индексированных таблиц.
С другой стороны, реорганизация индекса — это «легкий» процесс, который решит проблему фрагментации менее эффективным способом, поскольку очищенный индекс всегда будет вторым после нового, полностью созданного с нуля.Но реорганизация индекса намного лучше с точки зрения эффективности, поскольку она не блокирует затронутую индексированную таблицу в ходе работы.
В вышеупомянутой статье также объясняется, как реорганизовать и перестроить индексы с помощью SSMS, T-SQL (для реорганизации/перестроения индексов в таблице) и стороннего инструмента под названием ApexSQL Backup.
Если при реорганизации индекса индекс распределен по двум или более физическим файлам, данные будут дефрагментированы только внутри файла данных.Страницы не перемещаются из одного файла данных в другой.
Когда индекс находится в одном файле, реорганизация и переиндексация будут иметь одинаковый конечный результат.
В некоторых случаях реорганизация будет проходить быстрее, а в некоторых случаях переиндексация будет быстрее, в зависимости от того, насколько фрагментирован индекс.Чем менее фрагментирован индекс, тем быстрее будет реорганизация; чем более фрагментирован, тем медленнее будет реорганизация, но тем быстрее будет переиндексация.
Именно то, что Бири сказал.Вот как я бы переиндексировал всю базу данных:
EXEC [sp_MSforeachtable] @command1="RAISERROR('DBCC DBREINDEX(''?'') ...',10,1) WITH NOWAIT DBCC DBREINDEX('?')"
Я использую этот 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 каждую неделю.
Еще лучше:
EXEC sp_MSforeachtable 'ALTER INDEX ALL ON ? REINDEX'
или
EXEC sp_MSforeachtable 'ALTER INDEX ALL ON ? REORGANIZE'
Мои два цента...Этот метод соответствует спецификации, изложенной в технической сети: 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
Я исследовал Интернет и нашел несколько хороших статей.Я написал функцию и сценарий ниже, которые реорганизуют, воссоздают или перестраивают все индексы в базе данных.
Сначала вам, возможно, придется прочитать Эта статья чтобы понять, почему мы не просто воссоздаем все индексы.
Во-вторых, нам нужна функция для создания сценария создания индекса.Так Эта статья может помочь.Также я делюсь рабочей функцией ниже.
Последний шаг — создание цикла while для поиска и организации всех индексов в базе данных. Это видео это отличный пример для этого.
Функция:
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 на время:
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