Имя: Пароль:
IT
Админ
MS SQL Долго выполняется реорганизация индекса. Куда смотреть?
0 arsik
 
гуру
06.07.26
09:30
MS SQL 2022
В плане обслуживания несколько задач
1) Включить страничные блокировки (EXEC sp_MSforeachtable 'ALTER INDEX ALL ON ? SET (ALLOW_PAGE_LOCKS = ON)' )
2) Реорганизация индекса (см. скриншот)
3) Выключить страничные блокировки (EXEC sp_MSforeachtable 'ALTER INDEX ALL ON ? SET (ALLOW_PAGE_LOCKS = OFF)' )
4) Обновление статистики
5) очистка кеша (dbcc freeproccache)
6) Сброс статистики ожидания (DBCC SQLPERF('sys.dm_os_wait_stats', CLEAR))

Судя по журналу - Продолжительность самой реорганизации - 11  минут, но делается это 7 часов.
1 Lama12
 
06.07.26
09:08
скриншота нет
2 arsik
 
гуру
06.07.26
09:33
(1) Сорян. Добавил.
+(0) Базы небольшие
- 3 бухгалтерии общим объемом (37 Гб - .mdf)
- 3 камина (15 Гб - .mdf)
- 1 альфа авто 6 (9 Гб - .mdf)
3 arsik
 
гуру
06.07.26
09:40
Если я правильно понял, скуль долго подготавливает tsql скрипт для реорганизации.
Т.к. пробовал посмотреть T-SQL в реорганизации - не дождался. Именно это время я думаю занимает 7 часов.
4 arsik
 
гуру
06.07.26
09:42
Ну и еще нюанс. Служба агента и служба скуля запускаються от учетной записи MSA пользователя домена на этом сервере. Может тут проблема?
5 Lama12
 
06.07.26
09:48
(4) Это вряд ли.
6 Fedor-1971
 
06.07.26
10:05
(3) Чисто предположение: может долго определяется % фрагментации? типа нужно обновить статистику БД, а потом отправлять в реиндекс?

Можно попробовать тупо Reindex всей БД. На сколько знаю, есть скрипт, тупой переиндексации самых больших таблиц БД с какой-то периодичностью, но это тема для конфигураций уровня ЕРП
7 arsik
 
гуру
06.07.26
10:50
(6) Ну мне ИИ сделал готовый скрипт конечно. Но я хотел разобраться в проблеме.
PS: Вот поправленный скрипт, с включением блокировки перед реорганизацией и выключением после
/*
=============================================================================
Скрипт обслуживания индексов (Index Maintenance)
Требования: SQL Server 2016 и выше (используется STRING_SPLIT)

ВАЖНО: Для операции REORGANIZE скрипт временно включает ALLOW_PAGE_LOCKS = ON,
а после завершения возвращает ALLOW_PAGE_LOCKS = OFF.
Убедитесь, что это соответствует требованиям вашей нагрузки!
=============================================================================
*/

-- ==================== ПАРАМЕТРЫ ====================

-- 1. Список баз данных через запятую
DECLARE @DatabaseList NVARCHAR(MAX) = N'Database1, Database2, Database3'; 

-- 2. Пороги фрагментации (согласно вашему ТЗ)
DECLARE @ThresholdReorganize FLOAT = 5.0;  -- Менее 5% - ничего не делаем
DECLARE @ThresholdRebuild FLOAT = 30.0;    -- Более 30% - перестроение (REBUILD)

-- 3. Дополнительные рекомендуемые параметры
DECLARE @MinPageCount INT = 1000;          -- Игнорировать индексы меньше 1000 страниц (8 МБ)
DECLARE @OnlineRebuild BIT = 0;            -- 1 = ONLINE = ON (требует Enterprise/Developer edition)
DECLARE @ReportOnly BIT = 1;               -- 1 = только отчет, 0 = выполнять

-- ==================== ЛОГИКА СКРИПТА ====================

IF @ReportOnly = 1
    PRINT N'*** РЕЖИМ ТОЛЬКО ОТЧЕТ - команды не будут выполнены ***';
ELSE
    PRINT N'*** РАБОЧИЙ РЕЖИМ - команды будут выполнены ***';

-- Таблица для хранения списка индексов, требующих обслуживания
DECLARE @IndexesToMaintain TABLE (
    DBName NVARCHAR(128),
    SchemaName NVARCHAR(128),
    TableName NVARCHAR(128),
    IndexName NVARCHAR(128),
    FragPercent FLOAT,
    Action NVARCHAR(20),
    OriginalPageLocks BIT,      -- Исходное состояние ALLOW_PAGE_LOCKS
    SqlCommand NVARCHAR(MAX)
);

-- Сбор информации о фрагментации
PRINT N'Анализ фрагментации индексов...';
INSERT INTO @IndexesToMaintain (DBName, SchemaName, TableName, IndexName, FragPercent, Action, OriginalPageLocks, SqlCommand)
SELECT 
    DB_NAME(ps.database_id) AS DBName,
    s.name AS SchemaName,
    t.name AS TableName,
    i.name AS IndexName,
    ps.avg_fragmentation_in_percent,
    CASE 
        WHEN ps.avg_fragmentation_in_percent <= @ThresholdRebuild THEN 'REORGANIZE'
        ELSE 'REBUILD'
    END AS Action,
    i.allow_page_locks AS OriginalPageLocks,
    CASE 
        WHEN ps.avg_fragmentation_in_percent <= @ThresholdRebuild 
        THEN N'-- Включаем блокировки на уровне страницы
ALTER INDEX ' + QUOTENAME(i.name) + N' ON ' + 
             QUOTENAME(DB_NAME(ps.database_id)) + N'.' + QUOTENAME(s.name) + N'.' + QUOTENAME(t.name) + 
             N' SET (ALLOW_PAGE_LOCKS = ON);
-- Реорганизация индекса
ALTER INDEX ' + QUOTENAME(i.name) + N' ON ' + 
             QUOTENAME(DB_NAME(ps.database_id)) + N'.' + QUOTENAME(s.name) + N'.' + QUOTENAME(t.name) + 
             N' REORGANIZE;
-- Выключаем блокировки на уровне страницы
ALTER INDEX ' + QUOTENAME(i.name) + N' ON ' + 
             QUOTENAME(DB_NAME(ps.database_id)) + N'.' + QUOTENAME(s.name) + N'.' + QUOTENAME(t.name) + 
             N' SET (ALLOW_PAGE_LOCKS = OFF);'
        ELSE N'ALTER INDEX ' + QUOTENAME(i.name) + N' ON ' + 
             QUOTENAME(DB_NAME(ps.database_id)) + N'.' + QUOTENAME(s.name) + N'.' + QUOTENAME(t.name) + 
             N' REBUILD' + 
             CASE WHEN @OnlineRebuild = 1 THEN N' WITH (ONLINE = ON)' ELSE N'' END + 
             N';'
    END AS SqlCommand
FROM sys.dm_db_index_physical_stats(NULL, NULL, NULL, NULL, 'LIMITED') ps
INNER JOIN sys.databases d ON ps.database_id = d.database_id
INNER JOIN sys.tables t ON ps.object_id = t.object_id
INNER JOIN sys.schemas s ON t.schema_id = s.schema_id
INNER JOIN sys.indexes i ON ps.object_id = i.object_id AND ps.index_id = i.index_id
WHERE d.name IN (SELECT LTRIM(RTRIM(value)) FROM STRING_SPLIT(@DatabaseList, ','))
  AND d.state = 0             -- Только ONLINE базы
  AND d.is_read_only = 0      -- Исключаем Read-Only базы
  AND ps.avg_fragmentation_in_percent > @ThresholdReorganize -- Фильтр: < 5% не берем
  AND i.type IN (1, 2)        -- 1 = Clustered, 2 = NonClustered
  AND i.is_disabled = 0       -- Исключаем отключенные индексы
  AND ps.page_count >= @MinPageCount; -- Исключаем очень маленькие таблицы

DECLARE @IndexCount INT = @@ROWCOUNT;
PRINT N'Найдено индексов для обслуживания: ' + CAST(@IndexCount AS NVARCHAR(10));
PRINT N'';

-- ==================== ВЫВОД ОТЧЕТА ====================

IF @ReportOnly = 1
BEGIN
    -- Выводим детальную таблицу с командами
    SELECT 
        DBName AS [База данных],
        SchemaName + '.' + TableName AS [Таблица],
        IndexName AS [Индекс],
        CAST(FragPercent AS DECIMAL(5,2)) AS [Фрагментация %],
        Action AS [Действие],
        CASE 
            WHEN Action = 'REORGANIZE' THEN 
                CASE WHEN OriginalPageLocks = 1 THEN 'Да (было ON)' ELSE 'Нет (было OFF)' END
            ELSE 'N/A'
        END AS [Исходные Page Locks],
        SqlCommand AS [SQL команды]
    FROM @IndexesToMaintain
    ORDER BY FragPercent DESC;

    PRINT N'';
    PRINT N'===================================================';
    PRINT N'Отчет сформирован. Команды не выполнены.';
    PRINT N'Для выполнения обслуживания установите @ReportOnly = 0';
END
ELSE
BEGIN
    -- Рабочий режим: выполняем команды
    DECLARE @db NVARCHAR(128), @sch NVARCHAR(128), @tbl NVARCHAR(128), @idx NVARCHAR(128);
    DECLARE @frag FLOAT, @sql NVARCHAR(MAX), @action NVARCHAR(20);
    DECLARE @originalPageLocks BIT;

    DECLARE cur CURSOR LOCAL FAST_FORWARD FOR
    SELECT DBName, SchemaName, TableName, IndexName, FragPercent, Action, SqlCommand, OriginalPageLocks
    FROM @IndexesToMaintain
    ORDER BY FragPercent DESC;

    OPEN cur;
    FETCH NEXT FROM cur INTO @db, @sch, @tbl, @idx, @frag, @action, @sql, @originalPageLocks;

    WHILE @@FETCH_STATUS = 0
    BEGIN
        PRINT N'---------------------------------------------------';
        PRINT N'База: ' + @db + N' | Таблица: ' + @sch + N'.' + @tbl + N' | Индекс: ' + @idx;
        PRINT N'Фрагментация: ' + CAST(@frag AS NVARCHAR(10)) + N'% | Действие: ' + @action;
        
        IF @action = 'REORGANIZE'
        BEGIN
            PRINT N'Исходное состояние ALLOW_PAGE_LOCKS: ' + 
                  CASE WHEN @originalPageLocks = 1 THEN 'ON' ELSE 'OFF' END;
        END

        BEGIN TRY
            -- Выполняем команды последовательно
            IF @action = 'REORGANIZE'
            BEGIN
                -- Команда 1: Включить ALLOW_PAGE_LOCKS
                DECLARE @sqlEnableLocks NVARCHAR(MAX) = 
                    N'ALTER INDEX ' + QUOTENAME(@idx) + N' ON ' + 
                    QUOTENAME(@db) + N'.' + QUOTENAME(@sch) + N'.' + QUOTENAME(@tbl) + 
                    N' SET (ALLOW_PAGE_LOCKS = ON);';
                
                PRINT N'  [1/3] Включаю ALLOW_PAGE_LOCKS = ON...';
                EXEC sp_executesql @sqlEnableLocks;
                
                -- Команда 2: Реорганизация
                DECLARE @sqlReorganize NVARCHAR(MAX) = 
                    N'ALTER INDEX ' + QUOTENAME(@idx) + N' ON ' + 
                    QUOTENAME(@db) + N'.' + QUOTENAME(@sch) + N'.' + QUOTENAME(@tbl) + 
                    N' REORGANIZE;';
                
                PRINT N'  [2/3] Выполняю REORGANIZE...';
                EXEC sp_executesql @sqlReorganize;
                
                -- Команда 3: Выключить ALLOW_PAGE_LOCKS
                DECLARE @sqlDisableLocks NVARCHAR(MAX) = 
                    N'ALTER INDEX ' + QUOTENAME(@idx) + N' ON ' + 
                    QUOTENAME(@db) + N'.' + QUOTENAME(@sch) + N'.' + QUOTENAME(@tbl) + 
                    N' SET (ALLOW_PAGE_LOCKS = OFF);';
                
                PRINT N'  [3/3] Выключаю ALLOW_PAGE_LOCKS = OFF...';
                EXEC sp_executesql @sqlDisableLocks;
            END
            ELSE
            BEGIN
                -- Для REBUILD выполняем одну команду
                PRINT N'  Выполняю REBUILD...';
                EXEC sp_executesql @sql;
            END
            
            PRINT N'Успешно выполнено.';
        END TRY
        BEGIN CATCH
            PRINT N'ОШИБКА при выполнении: ' + ERROR_MESSAGE();
        END CATCH

        FETCH NEXT FROM cur INTO @db, @sch, @tbl, @idx, @frag, @action, @sql, @originalPageLocks;
    END

    CLOSE cur;
    DEALLOCATE cur;

    PRINT N'';
    PRINT N'===================================================';
    PRINT N'Обслуживание индексов завершено.';
END
8 proger2021
 
06.07.26
10:53
(3) Похоже на ожидание на блокировке, возможно на какойнить системной таблице. Попробуйте запустить еще раз  и посмотреть блокировки в мониторе активности.
9 arsik
 
гуру
06.07.26
11:34
(8) Запустил по одной базе. Что то там шуршит, но в мониторе никаких блокировок.
10 arsik
 
гуру
06.07.26
13:37
Попробовал разное, но никаких изменений.
Для 1й базы задание выполнялось 2 часа.
Пробовал перед запуском задания обновить статистику - без результатно.
Из монитора активности скачал план выполнения - итить колотить 3 мегабайта. Ну нахер.
11 H A D G E H O G s
 
06.07.26
13:48
(10) а вооон в том мегаскрипте нет случайно crossjoin во всех этих связях 5ти таблиц из за отсутствующего условия соединения?