28.10.25

TempDB: призрак Version Store

Автор: SWYS SQL, TEMPDB: The Ghost of VersionStore

Ближе к 30 октября некоторые серверы баз данных начинают вести себя странно, словно хотят поддержать атмосферу Хэллоуина. На этой неделе один из моих серверов вёл себя именно так. Последние месяцы он работал без проблем, но сегодня его TempDB начала стремительно заполняться. Разумеется, это потребовало расследования — и я обнаружил, что причиной неожиданного роста TempDB стал Version Store. Самое странное было в том, что одна база данных занимала почти всё пространство TempDB в Version Store, хотя в ней не было открытых транзакций. Действительно жуткая история!

Итак, призрак, которого я исследовал сегодня, — это данные Version Store одной базы данных, которые не удалялись из TempDB после завершения транзакции. Чтобы показать, как работает этот «призрак» и как его устранить, я приведу примеры кода.

Подготовка окружения

Поскольку я работаю на консолидированном сервере, начну с создания двух баз данных, которые никак не связаны друг с другом, кроме того, что находятся на одном экземпляре сервера и используют общие ресурсы.

CREATE DATABASE TESTDB_1
GO

CREATE DATABASE TESTDB_2
GO
    

Далее создам таблицу в каждой базе данных:

USE [TESTDB_1]
GO

CREATE TABLE TestTable
(TTID int identity(-2147483648,1), c1 varchar(200))
GO

USE [TESTDB_2]
GO

CREATE TABLE TestTable
(TTID int identity(-2147483648,1), c1 varchar(200))
GO
    

Теперь добавлю данные в таблицы:

USE [TESTDB_1]

DECLARE @Insert int
SET @Insert = 1

WHILE @Insert <= 10000
BEGIN
    INSERT INTO dbo.TestTable VALUES ('TEST' + CAST(@Insert AS varchar))
    SET @Insert = @Insert + 1
END
GO

SELECT TOP 10000 * FROM dbo.TestTable
GO

USE [TESTDB_2]
GO

DECLARE @Insert int
SET @Insert = 1

WHILE @Insert <= 10000
BEGIN
    INSERT INTO dbo.TestTable VALUES ('TEST' + CAST(@Insert AS varchar))
    SET @Insert = @Insert + 1
END
GO

SELECT TOP 10000 * FROM dbo.TestTable
GO
    

Теперь разрешу базам данных использовать snapshot, чтобы можно было работать с Version Store:

ALTER DATABASE TESTDB_1 SET allow_snapshot_isolation ON
GO

ALTER DATABASE TESTDB_2 SET allow_snapshot_isolation ON
GO
    

Выполню обновление одной из таблиц в завершённой транзакции:

SET TRANSACTION ISOLATION LEVEL READ COMMITTED

BEGIN TRAN

USE TESTDB_1

UPDATE dbo.TestTable SET c1 = 'UpdatedValue'

COMMIT
    

Если теперь проверить Version Store, то увидим, что там есть данные:

SELECT COUNT(*) AS amountofrec, d.name AS DBName
FROM sys.dm_tran_version_store tvs
INNER JOIN sys.databases d ON tvs.database_id = d.database_id
GROUP BY d.name

Если подождать, пока сработает сборщик мусора TempDB (это займёт около минуты), и выполнить запрос снова, мы увидим, что Version Store снова пуст. Это нормальное поведение.

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

Вот открытая маленькая транзакция:

SET TRANSACTION ISOLATION LEVEL READ COMMITTED

BEGIN TRAN

USE TESTDB_1

UPDATE dbo.TestTable SET c1 = 'SmallTransactionUpdateNotCommitted'
WHERE TTID = -2147483648
    

Если теперь проверить Version Store, мы увидим одну строку. 


Если поискать открытую транзакцию, то можно убедиться, что транзакция всё ещё открыта в базе данных. Поскольку это открытая транзакция, строка не будет удалена из Version Store.

Теперь я начну массово обновлять таблицу в другой базе данных (TESTDB_2):

SET TRANSACTION ISOLATION LEVEL READ COMMITTED

BEGIN TRAN

USE TESTDB_2

UPDATE dbo.TestTable SET c1 = 'MultipleUpdates'

COMMIT
GO 10
    

Как видим, это обновило все значения 10 раз и завершило транзакцию. Во второй базе данных нет открытых транзакций.

Во второй базе данных не осталось открытых транзакций.

 Если проверить Version Store, мы увидим, что строки теперь находятся в нём. 


Если подождать минуту, пока сработает сборщик мусора, мы обнаружим, что… 

Строки всё ещё в Version Store!

Таким образом, в Version Store остаётся не только строка из незавершённой транзакции одной базы данных, но и все строки из транзакций, завершённых после открытия первой транзакции! Это означает, что одна открытая транзакция может привести к заполнению TempDB данными из других баз данных!

Чтобы подтвердить это, я завершу открытую транзакцию и проверю, очистится ли Version Store. 

Если подождать около минуты и снова проверить Version Store, мы увидим, что он очищен!

Действительно жуткая проблема! Это означает, что даже самая маленькая открытая транзакция, использующая Version Store в консолидированной среде баз данных, может «кошмарить» все ваши базы данных!

Решение этой проблемы довольно простое: завершите открытую транзакцию или, если это невозможно, прервите её. Через минуту Version Store снова будет пуст.



Комментариев нет:

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