8.3.26

Фильтрованные индексы: сравнение производительности с примерами (Часть 2)

Автор: Luca Biondi, Filtered Index vs Indexed View in SQL Server: Complete Performance Comparison with Real Examples

Эта статья является продолжением предыдущего глубокого погружения в настройку производительности SQL Server!

Если вы пропустили Часть 1, вы можете прочитать её здесь:

Как фильтрованные индексы кардинально улучшают производительность запросов (часть 1)

В первой статье мы проанализировали, как фильтрованные индексы могут кардинально сократить логические чтения и оптимизировать планы выполнения.

Сегодня мы углубляемся и сравниваем фильтрованный индекс с индексированным представлением, используя практические, воспроизводимые SQL-скрипты.

Вы найдёте конкретные сценарии, которые сможете протестировать в своей собственной лаборатории — потому что настройка производительности — это не теория, это экспериментирование.

6.3.26

Анатомия задания очистки CDC в SQL Server (и как поддерживать его в рабочем состоянии)

Автор: Paul Ou Yang, Anatomy of a SQL Server CDC Cleanup Job (and How to Keep It Healthy)

Система отслеживания изменений данных (Change Data Capture, CDC) замечательна, пока не перестаёт ей быть. При высокой интенсивности изменений задание очистки CDC может начать отставать. Когда это происходит, таблицы изменений (Change Tables, CT) быстро разрастаются, а задание очистки может блокировать задание записи (capture job), что, в свою очередь, может задерживать или останавливать нижестоящие системы-потребители.

Эта статья объясняет: как быстро оценить рост таблиц изменений, как задание очистки на самом деле удаляет строки и три основных «рычага» настройки для поддержания стабильности CDC.

5.3.26

Почему LOB-колонки делают SHRINK очень медленным

Автор: Paul Randal, Why LOB data makes shrink run slooooowly (T-SQL Tuesday #006)

[Примечание от января 2015: Всё, что написано в этом посте, по-прежнему актуально для SQL Server 2012 и 2014.]

Тема этой статьи — LOB-данные, поэтому я немного отклонюсь от темы и объясню, почему LOB-данные делают производительность сжатия (shrink) действительно отвратительной (это технический термин :-).

4.3.26

Устранение ошибок включения CDC — Часть 2

Автор: Brass Contributor, Troubleshooting CDC enabling failure - Part 2

В предыдущей статье блога я рассказывал о сбое CDC из-за отключённого пользователя guest в MSDB. Тогда же мой заказчик столкнулся и с другой проблемой:

Msg 22832, Level 16, State 1, Procedure sp_cdc_enable_table_internal, Line 622

Could not update the metadata that indicates table [dbo].[Table_Name] is enabled 
for Change Data Capture. The failure occurred when executing the command 
'insert into [cdc].[change_tables]'. The error returned was 515: 
'Cannot insert the value NULL into column 'has_drop_pending', 
table 'LLCProduction.cdc.change_tables'; column does not allow nulls. 
INSERT fails.'. Use the action and error to determine the cause of the failure 
and resubmit the request.

Они обошли её, включив параметр ANSI_NULL_DEFAULT на уровне базы данных:

ALTER DATABASE DB_Name SET ANSI_NULL_DEFAULT ON;

После этого CDC включился успешно.

Вопрос в том, какова первопричина этой проблемы и как мы можем избежать её в будущем?

3.3.26

Устранение ошибок включения CDC — Часть 1

Автор: Brass Contributor, Troubleshooting CDC enabling failure - Part 1

Недавно мой заказчик столкнулся с двумя интересными проблемами, связанными с CDC, и сегодня я хотел бы поделиться процессом их диагностики и первопричинами.

Проблема:

В выходные заказчик попытался перенести некоторые изменения на производственный сервер. При попытке включить CDC на производственном сервере сначала возникла ошибка безопасности:

#1: Ошибка безопасности:

Msg 22832, Level 16, State 1, Procedure sp_cdc_enable_table_internal, Line 622

Could not update the metadata that indicates table [dbo].[Table_Name] is enabled for 
Change Data Capture. The failure occurred when executing the command 
'[sys].[sp_cdc_add_job] @job_type = N'capture''. 
The error returned was 916: 'The server principal "S-1-9-3-1293060401-1231192665-3834407059-1208013724." 
is not able to access the database "msdb" under the current security context.'. 
Use the action and error to determine the cause of the failure and resubmit the request

2.3.26

Безопасная очистка потерянных записей в Change Tracking (часть 2)

Автор: Mohamed_Baioumy_MSFT, Part 2: Safely Cleaning Orphaned Records in Change Tracking Side Tables

Область применения: База данных SQL Azure (с включенным отслеживанием изменений)

Краткое повторение (Часть 1)

В Части 1 мы рассмотрели, как обнаружить ""Orphaned записи во вспомогательных таблицах отслеживания изменений — строки, чей sys_change_xdes_id больше не имеет соответствующей записи транзакции в таблице фиксации (sys.syscommittab). Эта ситуация часто приводит к неожиданному росту размера отслеживания изменений и симптомам "stuck cleanup", потому что данные сопоставления, необходимые для нормальной очистки, отсутствуют.

Ссылка на Часть 1: Выявление потерянных записей в Change Tracking (часть 1)

Почему нужна Часть 2

Распространенный "корневой шаблон", который мы наблюдаем на практике:

  • Очистка вспомогательных таблиц пытается удалить устаревшие метаданные
  • Некоторые удаления из вспомогательных таблиц завершаются сбоем (блокировки, тайм-ауты, ошибки)
  • Очистка таблицы фиксации всё равно выполняется (или пользовательский рабочий процесс удаляет данные из таблицы фиксации без проверки удаления из вспомогательных таблиц)
  • Оставшиеся строки вспомогательных таблиц теперь ссылаются на значения xdes_id, которые больше не существуют в sys.syscommittab → образуются потерянные записи

В документации Microsoft Learn также подчеркивается, что очистка syscommittab зависит от очистки вспомогательных таблиц — очистка таблицы фиксации должна происходить только после очистки вспомогательных таблиц.

Этот скрипт из Части 2 сосредоточен на удалении потерянных, осиротевших строк из вспомогательных таблиц (и не затрагивает sys.syscommittab), чтобы логика очистки могла снова стабилизироваться.

Важные предварительные требования и ограничения (прочтите это сначала)

1) Доступ к внутренним таблицам в Базе данных SQL Azure

В Базе данных SQL Azure клиенты могут не иметь возможности напрямую обращаться к определенным внутренним артефактам отслеживания изменений (даже при попытке использования рабочих процессов в стиле DAC). В соответствующих обсуждениях случаев внутреннее тестирование показало, что самостоятельная очистка внутренних таблиц может быть неосуществима.

2) Примечание о CHECKPOINT (почему он в скрипте)

sys.dm_tran_commit_table предоставляет данные таблицы фиксации и поддерживается sys.syscommittab. В документации Microsoft Learn отмечается, что пользователи, имеющие доступ только для чтения, могут не видеть текущие изменения до выполнения CHECKPOINT.

Вот почему в ваш скрипт включен необязательный комментарий CHECKPOINT перед чтением состояния таблицы фиксации.

3) Поддерживаемые рекомендации против пользовательского исправления

В Microsoft Learn предоставлены официальные рекомендации по устранению неполадок и смягчению проблем с очисткой отслеживания изменений (включая проверку dbo.MSChange_tracking_history, оценку устаревших строк и использование sp_flush_commit_table_on_demand для очистки таблицы фиксации).

Этот скрипт представляет собой целенаправленный шаблон исправления для конкретного режима сбоя (осиротевшие строки вспомогательных таблиц). Используйте его осторожно, сначала протестируйте и следуйте процедурам, принятым в вашей организации.

Что делает этот скрипт (общий обзор)

Скрипт T-SQL, по сути, представляет собой обнаружение из Части 1 + дополнительная необязательная генерация необходимого для удаления кода:

  • Вычисляет "безопасную точку очистки" из периода хранения отслеживания изменений (календарное время → CSN) с помощью sp_changetracking_time_to_csn — та же концепция, что использовалась в Части 1.
  • Перечисляет вспомогательные таблицы отслеживания изменений через sys.internal_tables, где internal_type = 209 (вспомогательные таблицы отслеживания изменений).
  • Для каждой вспомогательной таблицы определяет кандидатные осиротевшие идентификаторы транзакций (sys_change_xdes_id), которые:
    • старше вычисленной границы (@minXdesId, полученной из sys.dm_tran_commit_table), и
    • не имеют соответствующего xdes_id в sys.syscommittab на/до точки очистки
  • Выводит количество осиротевших записей для каждой вспомогательной таблицы с помощью RAISERROR … WITH NOWAIT (удобный для оператора потоковый вывод).
  • Перекрестная проверка безопасности: прерывает работу, если какая-либо "осиротевшая" запись неожиданно существует в sys.syscommittab (защита от "дурака").
  • Генерирует инструкцию DELETE для текущей вспомогательной таблицы (выполнение закомментировано).

Скрипт (Часть 2) — "обнаружение + генерация удаления"

Ниже приведен скрипт T-SQL. Я оставил шаг удаления отключенным по умолчанию, чтобы его можно было безопасно распространять. (Вы можете включить выполнение только после согласования/тестирования.)

-- use <[DBName]> -- переключиться на нужную базу данных


-- сначала выполните checkpoint, чтобы убедиться, что все данные таблицы фиксации в памяти сохранены на диск (syscommittab)
-- checkpoint


SET NOCOUNT ON


-- найти недопустимую версию очистки на основе настроенного периода хранения
DECLARE @time DATETIME, @csn BIGINT = 0, @minCleanupPoint BIGINT = 0
DECLARE @retention_period INT, @retention_period_units NVARCHAR(10)
SELECT @retention_period = retention_period,
@retention_period_units = retention_period_units
FROM sys.change_tracking_databases where database_id = DB_ID()
SELECT @time = CASE WHEN @retention_period_units = 1 then DATEADD(minute, (-1 * @retention_period), GETUTCDATE())
WHEN @retention_period_units = 2 then DATEADD(hour, (-1 * @retention_period), GETUTCDATE())
ELSE DATEADD(day, (-1 * @retention_period), GETUTCDATE()) END

EXEC sp_changetracking_time_to_csn @time = @time, @csn = @csn OUTPUT


SELECT @minCleanupPoint = @csn
SELECT @minCleanupPoint as minCsn -- 688118


-- перебрать все вспомогательные таблицы отслеживания изменений
DECLARE @sideTable SYSNAME;
DECLARE ct_cursor CURSOR FAST_FORWARD FOR
SELECT name FROM sys.internal_tables WHERE internal_type = 209; -- internal_type = 209 для вспомогательных таблиц отслеживания изменений


OPEN ct_cursor;
FETCH NEXT FROM ct_cursor INTO @sideTable;


WHILE @@FETCH_STATUS = 0
BEGIN
-- найти минимальный истекший xdes id
declare @minXdesId BIGINT
SELECT @minXdesId = min(xdes_id) FROM sys.dm_tran_commit_table where commit_ts <= @minCleanupPoint
-- SELECT @minXdesId as minXdes

-- создать временную таблицу для хранения осиротевших xdes id
DROP TABLE IF EXISTS #OrphanedXdes;
    CREATE TABLE #OrphanedXdes
    (
        sys_change_xdes_id BIGINT NOT NULL
    );


DECLARE @sql NVARCHAR(MAX);
SET @sql = N'
    INSERT INTO #OrphanedXdes(sys_change_xdes_id)
    SELECT ct.sys_change_xdes_id
    FROM sys.' + QUOTENAME(@sideTable) + N' AS ct
    WHERE ct.sys_change_xdes_id < @minXdesId
      AND NOT EXISTS
      (
          SELECT 1
          FROM sys.syscommittab AS s
          WHERE s.xdes_id = ct.sys_change_xdes_id AND s.commit_ts <= @minCleanupPoint
      );';


    EXEC sys.sp_executesql
        @sql,
        N'@minXdesId BIGINT, @minCleanupPoint BIGINT',
        @minXdesId = @minXdesId,
@minCleanupPoint = @minCleanupPoint;


DECLARE @orphanedIdsCount BIGINT;
SET @sql = N'
SELECT @cnt = COUNT_BIG(sys_change_xdes_id)
FROM #OrphanedXdes;
';


EXEC sys.sp_executesql
@sql,
N'@cnt BIGINT OUTPUT',
@cnt = @orphanedIdsCount OUTPUT;


-- Вызвать ошибку, если существуют осиротевшие xdes
IF (@orphanedIdsCount > 0)
BEGIN
DECLARE @msg NVARCHAR(4000) =
@sideTable + N' : ' + CONVERT(NVARCHAR(30), @orphanedIdsCount);
RAISERROR (@msg, 16, 1) WITH NOWAIT;

DECLARE @newLine NVARCHAR(10) = CHAR(13) + CHAR(10)
PRINT (@newLine)
END




-- Перекрестная проверка: ни один xdes не должен существовать в syscommittab
-- !!!ВАЖНО!!! Вызвать ошибку и остановить очистку, если это произошло
SET @sql = N'
DECLARE @nonMatchingXdesCount BIGINT;


SELECT @nonMatchingXdesCount = COUNT_BIG(*)
FROM #OrphanedXdes AS ct
WHERE EXISTS (
SELECT 1
FROM sys.syscommittab AS s
WHERE s.xdes_id = ct.sys_change_xdes_id
);


-- SELECT @nonMatchingXdesCount as nonMatchingXdesCount

IF (COALESCE(@nonMatchingXdesCount, 0)  > 0)
BEGIN TRY
DECLARE @msg NVARCHAR(1024);
SET @msg = N''Cleanup aborted: orphan cross-check failed for side table [' + @sideTable + N'].'';
RAISERROR(@msg, 16, 1) WITH NOWAIT;
RETURN;
END TRY
BEGIN CATCH
THROW;
END CATCH
';


EXEC sys.sp_executesql @sql;


IF (@orphanedIdsCount > 0)
BEGIN
-- Подготовить запрос для удаления осиротевших строк из вспомогательной таблицы
SET @sql = N'DELETE ct FROM sys.' + @sideTable + N' ct WHERE EXISTS (SELECT 1 FROM #OrphanedXdes AS o WHERE o.sys_change_xdes_id = ct.sys_change_xdes_id);';


SELECT @sql -- проверить, что запрос на удаление сгенерирован правильно
-- Пример инструкции удаления: DELETE ct FROM sys.change_tracking_1221579390 ct WHERE EXISTS (SELECT 1 FROM #OrphanedXdes AS o WHERE o.sys_change_xdes_id = ct.sys_change_xdes_id);


-- ПРИМЕЧАНИЕ: Раскомментируйте приведенный ниже запрос, чтобы выполнить инструкцию удаления и удалить осиротевшие записи
-- EXEC sys.sp_executesql @sql;
END


DROP TABLE IF EXISTS #OrphanedXdes;


    FETCH NEXT FROM ct_cursor INTO @sideTable;
END


CLOSE ct_cursor;
DEALLOCATE ct_cursor;


SET NOCOUNT OFF

Почему "перекрестная проверка с прерыванием" — это хорошая идея

Обратите внимание на защитную проверку:

  • Вы сначала определяете осиротевшие записи как те, которые не существуют в sys.syscommittab (для горизонта очистки).
  • Затем вы перепроверяете: "Если какая-либо из них появляется в syscommittab, прервать выполнение".

Это предотвращает случайное удаление, если:

  • расчет горизонта очистки неверен,
  • в среде есть неожиданные различия в видимости, или
  • содержимое временной таблицы не соответствует ожидаемому.

Эта перестраховочная схема хорошо согласуется с общим принципом, задокументированным в Microsoft Learn: очистка таблицы фиксации должна происходить только после очистки вспомогательных таблиц, а устранение неполадок должно быть основано на данных и выполняться с осторожностью.

Как интерпретировать вывод

  • Если вы не видите строк RAISERROR, скрипт не обнаружил осиротевших строк в соответствии с заданными критериями.
  • Если вы видите: change_tracking_<id> : <count> это указывает на <count> осиротевших ссылок на транзакции в этой вспомогательной таблице отслеживания изменений. Это тот же стиль, который использовался в Части 1 для длительного потокового вывода прогресса.

Следующие шаги (рекомендуемый порядок)

  1. Подтвердите конфигурацию отслеживания изменений (период хранения + статус AUTO_CLEANUP), используя официальные рекомендации.
  2. Запустите обнаружение из Части 1 / Части 2, чтобы количественно оценить масштаб (какие вспомогательные таблицы, сколько).
  3. Если вам нужно провести исправление:
    • Предпочитайте поддерживаемые меры смягчения, где это возможно (например, отключение/включение отслеживания изменений для таблицы, чтобы очистить метаданные отслеживания для этой таблицы, указано как "самое быстрое средство" в Microsoft Learn для определенных сценариев конфликтов блокировок при очистке).
    • Если отключение/включение на уровне таблицы неприемлемо, используйте подход, основанный на утверждении, для целевой очистки.

Заключение

Осиротевшие записи во вспомогательных таблицах отслеживания изменений — это одно из тех состояний "тихого роста", которое легко пропустить, пока не станет болезненным либо использование хранилища, либо производительность CHANGETABLE. Часть 1 помогает вам обнаружить проблему на ранней стадии; Часть 2 помогает подготовить безопасный, целевой рабочий процесс очистки — с явными защитными проверками и шагом удаления, который остается отключенным по умолчанию.

Ссылки

1.3.26

Выявление потерянных записей в Change Tracking (часть 1)

Автор: Mohamed_Baioumy_MSFT, Identifying Orphaned Records in Change Tracking Side Tables (Read‑Only Health Check)

Когда в SQL Server включено отслеживание изменений (CT, Change Tracking), компонент ядра СУБД сохраняет облегчённые метаданные об изменениях, чтобы приложения могли запрашивать: "что изменилось, начиная с версии X?". Внутреннее устройство CT подразумевает наличие:

  • Вспомогательной таблицы для каждой отслеживаемой пользовательской таблицы. В ней хранятся метаданные изменений на уровне строк (включая идентификатор транзакции).
  • Таблицы фиксации (commit table), в которой хранятся зафиксированные транзакции, затронувшие любую таблицу с включенным отслеживанием изменений. Динамическое административное представление (DMV) sys.dm_tran_commit_table отображает объединение частей этой таблицы фиксации, находящихся в памяти и на диске.

При нормальной работе очистка CT удаляет устаревшие строки из вспомогательных таблиц, а затем — соответствующие более старые строки из таблицы фиксации, основываясь на настроенном периоде хранения. Внутренняя логика очистки использует "безопасную точку очистки", получаемую из настроенного периода хранения, и сопоставляет это астрономическое время с версией CT с помощью системной хранимой процедуры sys.sp_changetracking_time_to_csn.

27.2.26

Как фильтрованные индексы кардинально улучшают производительность запросов (часть 1)

Автор: Luca Biondi, SQL Server Performance Tuning: How Filtered Indexes Drastically Improve Query Performance

Сегодня мы погружаемся в мощную технику настройки производительности SQL Server, которая может кардинально сократить логические чтения, оптимизировать планы выполнения и значительно улучшить производительность запросов в реальных производственных средах.

Если вы работаете с Microsoft SQL Server и боретесь с медленными запросами, высокими логическими чтениями или неэффективными планами выполнения, эта продвинутая техника настройки производительности SQL Server может кардинально улучшить производительность запросов.

В этой статье мы проанализируем, как фильтрованные индексы в SQL Server могут уменьшить ввод-вывод, оптимизировать планы выполнения и значительно повысить производительность OLTP.

26.2.26

Обнаружение длинных цепочек IAM

Автор: Paul Randal, The Curious Case of… finding long IAM chains

В предыдущей статье Любопытный случай периодического сбоя запроса к крошечной таблице я описал проблему, с которой Джонатан столкнулся у клиента: очень длинные цепочки IAM и обстоятельства, к ним приведшие.

Вопрос заключался в том, как доказать, что некоторые единицы распределения имеют длину цепочки IAM, непропорциональную объёму данных в единице распределения, без утомительного прохода по каждой цепочке IAM, начиная с первой IAM-страницы (чей идентификатор всегда хранится во внутренней таблице sys.allocation_units).

25.2.26

Как оптимизировать переключение реплик групп доступности SQL Server

Автор: Aaron Bertrand , Serializing Deletes From Clustered Columnstore Indexes

Мы часто выполняем плановые переключения реплик в группах доступности SQL Server для обслуживания, установки исправлений, обновлений и даже ротации оборудования. Обычно наши переключения выполняются быстро, но иногда они занимают больше времени — и не всегда интуитивно понятно почему, поскольку нет очевидной связи со временем суток, размером базы данных или объёмом транзакций.

Сокращение даже нескольких секунд из этого процесса может улучшить взаимодействие с приложением и конечным пользователем; это также может значительно снизить количество оповещений или, по крайней мере, сократить время, в течение которого оповещения должны быть отключены. Существует множество материалов о том, как правильно выполнять переключения в AG (без потери данных), но гораздо меньше тех, которые сосредоточены на сокращении окна прерывания доступности. Разница обычно заключается в некоторой комбинации объёма повторного выполнения (redo), поведения контрольных точек, открытых транзакций и готовности вторичной реплики.

Я хотел поделиться некоторыми методами, которые я использую, чтобы сделать плановые переключения более быстрыми и предсказуемыми. Некоторые из этих методов хорошо документированы, другие выпестованы из реальных шаблонов, которые я наблюдал во многих средах с SQL Server. Я расскажу о том, что я делаю до, во время и после переключения первичной реплики, чтобы минимизировать прерывания работы пользователей и повысить вероятность того, что они не заметят, что что-то произошло.

24.2.26

Введение в транзакции SQL Server

Автор: Paul Randal, SQL101: Introduction to SQL Server Transactions

Одним из фундаментальных понятий в любой реляционной системе управления базами данных (СУБД), такой как SQL Server, является транзакция. За свою консультационную карьеру я видел множество случаев проблем с производительностью, вызванных тем, что разработчики не понимают, как работают транзакции в SQL Server, поэтому в этом учебном руководстве я объясню, что такое транзакции и почему они необходимы, а также некоторые детали их работы в SQL Server. В использовании всего этого есть нюансы, когда задействовано Ускоренное восстановление базы данных (ADR) — темы для будущих статей.

20.2.26

Журнал транзакций SQL Server, Часть 2: Архитектура журналирования

Автор: Paul Randal, The SQL Server Transaction Log, Part 2: Log Architecture

В первой части этой серии я познакомил вас с основной терминологией, касающейся журналирования, поэтому рекомендую прочитать её, прежде чем продолжать чтение этой части. Всё остальное, что я расскажу в серии, требует знания некоторой архитектуры журнала транзакций, так что именно это я и собираюсь обсудить на этот раз. Даже если вы не собираетесь следить за серией, некоторые концепции, которые я объясню ниже, полезно знать для повседневных задач, с которыми сталкиваются в своей работе администраторы баз данных.