Автор: Steve Stedman, When TempDB Grows “Up and to the Right”: How RCSI and Open Transactions Quietly Break SQL Server
Одна из самых опасных проблем SQL Server — та, которую вы не замечаете, пока диск не заполнится, а производственная система не упадёт. TempDB особенно хороша в таком типе отказа — она незаметно растёт в фоновом режиме, пока внезапно не становится проблемой для всех.
Недавно мы просматривали отчёт о распределении пространства TempDB (TempDB Allocation History Report), и он прекрасно проиллюстрировал проблему, с которой мы сталкиваемся всё чаще по мере того, как растёт применение READ COMMITTED Snapshot Isolation (RCSI). RCSI обычно является правильным выбором, но когда что-то идёт не так, TempDB может расти взрывным образом, и большинство команд не понимают почему, пока не становится слишком поздно.
Почему отчёт о распределении пространства TempDB важен
Отчёт о распределении пространства TempDB наиболее ценен на производственных системах с реальной рабочей нагрузкой. Тестовые серверы редко показывают что-то интересное, потому что на них нет длительных транзакций, высокой конкурентности или продолжительной пользовательской активности.
Однако на производственных серверах этот отчёт рассказывает подлинную историю. Он показывает, как используется пространство TempDB с течением времени, включая:
- Хранилище версий (version store)
- Пользовательские объекты (user objects)
- Внутренние объекты (internal objects)
- Временные таблицы (temporary tables)
Когда TempDB начинает неожиданно расти, этот отчёт позволяет визуально определить коренную причину вместо того, чтобы гадать.
RCSI — не враг
Давайте проясним: RCSI — это не плохая функция. На самом деле, мы рекомендуем её примерно для 99% производственных сред SQL Server. Она кардинально снижает блокировки, позволяя читателям обращаться к версиям строк вместо ожидания блокировок.
Но у RCSI есть цена, и эта цена оплачивается в TempDB.
Когда RCSI включена, SQL Server хранит старые версии строк в хранилище версий (version store), которое находится в TempDB. В нормальных условиях эти версии автоматически удаляются после завершения транзакций.
Проблема возникает, когда они не завершаются.
Открытые транзакции и хранилище версий
Открытая транзакция при включённой RCSI предотвращает очистку старых версий строк в хранилище версий. Пока эта транзакция остаётся открытой, SQL Server должен сохранять каждую версию строки, необходимую для поддержки согласованного чтения.
Именно здесь всё идёт наперекосяк.
Мы видели клиентов, у которых приложение оставляло транзакцию открытой на часы или даже дни, в то время как другая активность продолжалась нормально. Каждая операция обновления, удаления или вставки в течение этого времени создавала всё новые версии. Хранилище версий продолжало расти, и TempDB следовала за ним.
В одном реальном случае у клиента образовалась TempDB размером четыре терабайта в системе, где ни одна из пользовательских баз данных даже близко не была такого размера. Виновником оказалась одна единственная открытая транзакция в сочетании с RCSI.
Что показал бы график
Когда вы смотрите на отчёт о распределении пространства TempDB в подобном сценарии, проблема выявляет себя мгновенно.
Если график почти полностью состоит из использования хранилища версий, вы знаете, что проблема связана с RCSI. Никаких догадок. Никакого копания в десятках запросов. Просто чёткий сигнал, указывающий на открытые транзакции.
То, что вы часто увидите, — это шаблон роста «вверх и вправо» (up and to the right growth pattern). Это хорошо, когда вы смотрите на цифры продаж. Это ужасно, когда вы смотрите на размер TempDB.
TempDB должна колебаться. Она должна расти и сжиматься. Когда она только растёт — и никогда не возвращается обратно — у вас проблема с очисткой.
Связываем с открытыми транзакциями
Именно здесь объединение отчётов становится мощным инструментом.
Когда проблема заключается в росте хранилища версий TempDB, отчёт об открытых транзакциях (Open Transactions Report) почти всегда подтверждает это. Обычно вы найдёте:
- Длительные транзакции
- Спящие сеансы (sleeping sessions), удерживающие транзакции открытыми
- Код приложения, который никогда не выполняет фиксацию (
COMMIT) или откат (ROLLBACK) должным образом
Пока эти транзакции остаются открытыми, TempDB будет продолжать расти. Резервные копии не помогут. Перезапуск заданий не поможет. Единственное реальное исправление — это выявление и устранение открытой транзакции.
Почему большинство команд упускают это из виду
Большинство ИТ-команд не следят за TempDB достаточно внимательно. Ещё меньше понимают, как взаимодействуют RCSI, очистка хранилища версий и открытые транзакции.
К тому времени, когда TempDB заполняет диск, ущерб уже нанесён. Сервер может перестать отвечать, приложения падают, и внезапно все спрашивают: «Как это произошло?»
Ответ почти всегда один: это произошло медленно, тихо и без надлежащего мониторинга.
Как мы выявляем это до того, как это перерастёт в кризис
Именно такие проблемы предотвращаются с помощью проактивного мониторинга SQL Server, нужно непрерывно отслеживать использование TempDB, рост хранилища версий и открытые транзакции. Когда TempDB начинает демонстрировать тенденцию к росту «вширь», мы расследуем это немедленно — задолго до того, как диск заполнится.
Такая видимость позволяет нам:
- Выявлять раздувание хранилища версий, связанное с RCSI, на раннем этапе
- Точно определять открытые транзакции, вызывающие задержки очистки
- Работать с командами разработчиков для исправления коренной причины
- Предотвращать сбои вместо того, чтобы реагировать на них
SELECT SUM(user_object_reserved_page_count) * 8 / 1024.0 AS user_objects_mb, SUM(internal_object_reserved_page_count) * 8 / 1024.0 AS internal_objects_mb, SUM(version_store_reserved_page_count) * 8 / 1024.0 AS version_store_mb, SUM(unallocated_extent_page_count) * 8 / 1024.0 AS unallocated_mb, SUM(version_store_reserved_page_count) * 8 / 1024.0 / NULLIF( (SUM(user_object_reserved_page_count) + SUM(internal_object_reserved_page_count) + SUM(version_store_reserved_page_count) + SUM(unallocated_extent_page_count)) * 8 / 1024.0, 0) * 100 AS version_store_pct FROM tempdb.sys.dm_db_file_space_usage;
SELECT tst.session_id, tst.transaction_id, tat.transaction_begin_time, DATEDIFF(SECOND, tat.transaction_begin_time, GETDATE()) AS open_duration_seconds, tat.transaction_state, s.program_name, s.host_name, s.login_name, s.status, tst.session_id AS blocker_session_id FROM sys.dm_tran_active_transactions tat JOIN sys.dm_tran_session_transactions tst ON tst.transaction_id = tat.transaction_id JOIN sys.dm_exec_sessions s ON s.session_id = tst.session_id WHERE tat.transaction_state = 2 -- активная (не зафиксирована) AND tat.transaction_begin_time IS NOT NULL ORDER BY tat.transaction_begin_time ASC; -- самые старые сверху
Что делать
- Найти проблемную транзакцию по session_id.
- Проверить, можно ли её закрыть (KILL <session_id> — крайняя мерия, если это не системный процесс).
- Исправить код приложения, который не завершает транзакцию (добавить COMMIT или ROLLBACK).
- Настроить мониторинг, чтобы алерт срабатывал при version_store_mb > 10 ГБ или при наличии транзакций старше N секунд.

Комментариев нет:
Отправить комментарий