23.8.25

SQL Server 2025 — Оптимизированные блокировки

Автор: Roger Schönmann. Краткий перевод статьи: SQL Server 2025 – Optimized Locking

В последние дни я изучал одну из новых возможностей SQL Server 2025 — Optimized Locking. Мне было интересно разобраться в её возможностях, поведении и ограничениях.

Функция основана на двух ключевых механизмах:

  • Transaction ID (TID)
  • Lock After Qualification (LAQ)

Transaction ID (TID)

Каждая транзакция получает уникальный идентификатор. Все строки, изменённые внутри транзакции, помечаются этим TID. Вместо множества блокировок на уровне строк или ключей (Key/RID) берётся только одна блокировка на TID. Технически блокировки Update и Exclusive всё же устанавливаются, но сразу освобождаются, не дожидаясь фиксации транзакции. Это сильно разгружает диспетчер блокировок и экономит память.

Lock After Qualification (LAQ)

При обновлении строк сначала выполняется «тихая проверка» в фоне, без установки Shared Lock. Блокировка Update накладывается только на те строки, которые реально подлежат изменению. Чтобы воспользоваться этим механизмом, нужно включить Read Committed Snapshot Isolation (RCSI) на уровне базы данных.

Среда тестирования

  • SQL Server 2025 CTP 2.0 (17.0.700.9)
  • SQL Server Management Studio 21 (21.3.6) с Copilot

Позже Microsoft выпустила CTP 2.1 с улучшениями (см. документацию).

Проверка настроек базы

-- Проверка параметров Optimized Locking
USE [OptimizedLocking];
GO
SELECT DATABASEPROPERTYEX(DB_NAME(), 'IsOptimizedLockingOn') AS IsOptimizedLockingOn;
GO

USE [master];
GO
SELECT name AS DatabaseName,
       is_read_committed_snapshot_on,
       is_optimized_locking_on,
       is_accelerated_database_recovery_on
FROM sys.databases
WHERE name = 'OptimizedLocking';
GO

Обязательно нужны:

  • ACCELERATED_DATABASE_RECOVERY — без него Optimized Locking не работает;
  • RCSI — включает LAQ.

Демонстрация

-- Создаём таблицу
USE [OptimizedLocking];
GO
DROP TABLE IF EXISTS T1;
GO
CREATE TABLE T1
(
  T1_ID INT NOT NULL,
  Value INT
);

INSERT INTO T1 (T1_ID, Value)
VALUES (1,10),(2,20),(3,30);
GO

SELECT * FROM dbo.T1;
GO

Таблица создана без кластерного индекса, то есть это Heap. Если параллельно выполнить два UPDATE (по разным строкам), один блокируется, так как не может получить Update Lock.

Включаем Optimized Locking

USE [master];
GO
ALTER DATABASE [OptimizedLocking] SET ACCELERATED_DATABASE_RECOVERY = ON;
ALTER DATABASE [OptimizedLocking] SET OPTIMIZED_LOCKING = ON;
GO

Теперь поведение изменилось: вместо 4 обычных блокировок видим только 2 XACT-блокировки. Нагрузка на Lock Manager снижается.

Добавляем RCSI

USE [master];
GO
ALTER DATABASE [OptimizedLocking] SET READ_COMMITTED_SNAPSHOT = ON;
GO

Теперь оба UPDATE проходят успешно — эксклюзивные блокировки (X) выданы без конфликта.

Heap против кластерного индекса

-- Добавляем кластерный индекс
USE [OptimizedLocking];
GO
CREATE CLUSTERED INDEX CI_T1 ON dbo.T1 (T1_ID);
GO

С кластерным индексом даже без включённых функций оба UPDATE работают: блокировки ставятся на разные ключи, Intent Exclusive (IX) на уровне страниц совместимы. Вывод: для таблиц с активными UPDATE/DELETE лучше использовать кластерные индексы. Heaps оставлять под чистые INSERT (например, логи).

Поведение при SERIALIZABLE

При уровне изоляции SERIALIZABLE Optimized Locking не помогает — вторая транзакция блокируется, потому что первая накладывает диапазонную X-блокировку, соответствующую требованиям уровня изоляции. Это ожидаемо: SERIALIZABLE всегда подразумевает жёсткие блокировки ради ACID.

Вывод

Optimized Locking — значимое улучшение в SQL Server 2025. Оно снижает нагрузку на диспетчер блокировок и экономит память, делая блокировки более «лёгкими». Лучший сценарий: включённый RCSI и (по возможности) кластерные индексы. Даже с Heaps мы выигрываем в памяти, но для конкурентности кластерные индексы предпочтительнее. Для строгих уровней изоляции (например, SERIALIZABLE) эффект отсутствует.

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

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