スライドはこちら
speakerdeck.com
口頭での補足が多かったので、スライドはびみょいかも。
デモはこちら
/*============================================================== 00_セットアップ.sql デモ用データベースの作成と前提条件の設定 - ADR (Accelerated Database Recovery: 高速データベース復旧) … 必須 - RCSI (Read Committed Snapshot Isolation) … LAQ に必須 - OPTIMIZED_LOCKING はデモの中で ON/OFF を切り替えるので、 ここでは OFF のまま ==============================================================*/ USE master; GO IF DB_ID(N'OptimizedLockingDemo') IS NOT NULL BEGIN ALTER DATABASE OptimizedLockingDemo SET SINGLE_USER WITH ROLLBACK IMMEDIATE; DROP DATABASE OptimizedLockingDemo; END GO CREATE DATABASE OptimizedLockingDemo; GO -- ADR を有効化 (最適化されたロックの必須条件) ALTER DATABASE OptimizedLockingDemo SET ACCELERATED_DATABASE_RECOVERY = ON; -- RCSI を有効化 (LAQ の必須条件) ALTER DATABASE OptimizedLockingDemo SET READ_COMMITTED_SNAPSHOT ON WITH ROLLBACK IMMEDIATE; GO /*-------------------------------------------------------------- 設定の確認 is_accelerated_database_recovery_on = 1 is_read_committed_snapshot_on = 1 is_optimized_locking_on = 0 (まだ OFF) --------------------------------------------------------------*/ SELECT database_id, name, is_accelerated_database_recovery_on, is_read_committed_snapshot_on, is_optimized_locking_on FROM sys.databases WHERE name = N'OptimizedLockingDemo'; GO /*-------------------------------------------------------------- デモ用テーブルの作成 --------------------------------------------------------------*/ USE OptimizedLockingDemo; GO -- デモ①用: クラスター化インデックス付き、1,000 行 CREATE TABLE dbo.Demo1 ( a int NOT NULL PRIMARY KEY, b int NULL ); INSERT INTO dbo.Demo1 (a, b) SELECT value, value * 10 FROM GENERATE_SERIES(1, 1000); GO -- デモ②③用: インデックスなし (ヒープ) -- → UPDATE はテーブルスキャンになる CREATE TABLE dbo.Demo2 ( a int NOT NULL, b int NULL ); INSERT INTO dbo.Demo2 (a, b) VALUES (1, 10), (2, 20), (3, 30); GO
/*============================================================== 10_デモ1_ロック数の比較.sql (1 セッションで完結) 同じ「1,000 行 UPDATE」を最適化されたロック OFF / ON で実行し、 sys.dm_tran_locks で保持ロックを比較する。 期待結果: OFF … PAGE の IX ロック + 行ごとの KEY X ロックが大量 (1,000 個超) ON … XACT リソースへの X ロックが 1 個だけ ==============================================================*/ USE OptimizedLockingDemo; GO /*-------------------------------------------------------------- STEP 1: まず OFF の状態で実行 --------------------------------------------------------------*/ -- 現在の設定を確認 (0 = 無効) SELECT DATABASEPROPERTYEX(DB_NAME(), 'IsOptimizedLockingOn') AS is_optimized_locking_enabled; GO BEGIN TRANSACTION; UPDATE dbo.Demo1 SET b = b + 1; -- このトランザクションが保持しているロックを集計 SELECT resource_type, request_mode, COUNT(*) AS lock_count FROM sys.dm_tran_locks WHERE request_session_id = @@SPID AND resource_type IN ('PAGE', 'RID', 'KEY', 'XACT') GROUP BY resource_type, request_mode ORDER BY lock_count DESC; -- 明細も見たい場合はこちら -- SELECT resource_type, request_mode, resource_description -- FROM sys.dm_tran_locks -- WHERE request_session_id = @@SPID; ROLLBACK TRANSACTION; GO /*-------------------------------------------------------------- STEP 2: 最適化されたロックを ON にする --------------------------------------------------------------*/ USE master; GO ALTER DATABASE OptimizedLockingDemo SET OPTIMIZED_LOCKING = ON WITH ROLLBACK IMMEDIATE; GO USE OptimizedLockingDemo; GO -- 1 = 有効 になったことを確認 SELECT DATABASEPROPERTYEX(DB_NAME(), 'IsOptimizedLockingOn') AS is_optimized_locking_enabled; GO /*-------------------------------------------------------------- STEP 3: 同じ UPDATE をもう一度実行 → XACT への X ロック 1 個だけになる --------------------------------------------------------------*/ BEGIN TRANSACTION; UPDATE dbo.Demo1 SET b = b + 1; SELECT resource_type, request_mode, COUNT(*) AS lock_count FROM sys.dm_tran_locks WHERE request_session_id = @@SPID AND resource_type IN ('PAGE', 'RID', 'KEY', 'XACT') GROUP BY resource_type, request_mode ORDER BY lock_count DESC; ROLLBACK TRANSACTION; GO
/*============================================================== 20_デモ2_セッション1.sql (SSMS タブ 1) デモ②: LAQ (Lock After Qualification) によるブロッキング解消 「別々の行を更新しているだけなのにブロックされる」が ON で解消される。 dbo.Demo2 はヒープ (インデックスなし) なので UPDATE はテーブルスキャン。 OFF ではスキャン中に全行へ U ロックを取りにいくため、 セッション 1 が X ロックを持つ行 (a=1) でセッション 2 が詰まる。 ※ ヒープにしているのは確実に再現するため。a にインデックスがあれば この例は回避できるが、インデックスシークでもシーク範囲にロック中の 行が含まれれば同じブロッキングは起きる (詳細はスライド7のノート)。 ▼ 実行手順 (タブ 21_デモ2_セッション2.sql と交互に実行) [Phase A: OFF] 1. このタブ: STEP 1 (OFF に切り替え) 2. このタブ: STEP 2 (BEGIN TRAN + UPDATE a=1) 3. 相手タブ: STEP 1 (UPDATE a=2) → ★ブロックされて返ってこない 4. このタブ: STEP 3 (COMMIT) → 相手が解放される [Phase B: ON] 5. このタブ: STEP 4 (ON に切り替え) 6. このタブ: STEP 5 (BEGIN TRAN + UPDATE a=1) 7. 相手タブ: STEP 2 (UPDATE a=2) → ★即座に完了する! 8. このタブ: STEP 6 (COMMIT) ==============================================================*/ /*-------------------------------------------------------------- STEP 1: 最適化されたロックを OFF にする (Phase A) --------------------------------------------------------------*/ USE master; GO ALTER DATABASE OptimizedLockingDemo SET OPTIMIZED_LOCKING = OFF WITH ROLLBACK IMMEDIATE; GO USE OptimizedLockingDemo; GO SELECT DATABASEPROPERTYEX(DB_NAME(), 'IsOptimizedLockingOn') AS is_optimized_locking_enabled; -- 0 GO /*-------------------------------------------------------------- STEP 2: 行 a=1 を更新してトランザクションを開いたままにする --------------------------------------------------------------*/ BEGIN TRANSACTION; UPDATE dbo.Demo2 SET b = b + 10 WHERE a = 1; -- ここで COMMIT せずに、セッション 2 の STEP 1 を実行 -- → ブロックされる GO /*-------------------------------------------------------------- STEP 3: コミットしてセッション 2 を解放する --------------------------------------------------------------*/ COMMIT TRANSACTION; GO /*-------------------------------------------------------------- STEP 4: 最適化されたロックを ON にする (Phase B) --------------------------------------------------------------*/ USE master; GO ALTER DATABASE OptimizedLockingDemo SET OPTIMIZED_LOCKING = ON WITH ROLLBACK IMMEDIATE; GO USE OptimizedLockingDemo; GO SELECT DATABASEPROPERTYEX(DB_NAME(), 'IsOptimizedLockingOn') AS is_optimized_locking_enabled; -- 1 GO /*-------------------------------------------------------------- STEP 5: もう一度、行 a=1 を更新してトランザクションを開いたままにする --------------------------------------------------------------*/ BEGIN TRANSACTION; UPDATE dbo.Demo2 SET b = b + 10 WHERE a = 1; -- ここでセッション 2 の STEP 2 を実行 → 今度はブロックされない! -- (U ロックを取らず、最新コミット済みバージョンで -- 述語 a=2 を評価するため、a=1 の行はセッション 2 にとって -- 「条件を満たさない行」としてスキップされる) GO /*-------------------------------------------------------------- STEP 6: 後片付け --------------------------------------------------------------*/ COMMIT TRANSACTION; GO
/*============================================================== 21_デモ2_セッション2.sql (SSMS タブ 2) デモ②: LAQ によるブロッキング解消 — セッション 2 側 実行手順は 20_デモ2_セッション1.sql の冒頭コメントを参照。 ==============================================================*/ USE OptimizedLockingDemo; GO /*-------------------------------------------------------------- STEP 1: [Phase A: OFF] 行 a=2 を更新する → セッション 1 が a=1 に X ロックを持っているためブロックされる。 (スキャンで a=1 の行に U ロックを取ろうとして待たされる) セッション 1 が COMMIT すると完了する。 --------------------------------------------------------------*/ BEGIN TRANSACTION; UPDATE dbo.Demo2 SET b = b + 10 WHERE a = 2; COMMIT TRANSACTION; GO /*-------------------------------------------------------------- STEP 2: [Phase B: ON] もう一度、行 a=2 を更新する → 今度はブロックされず即座に完了する! --------------------------------------------------------------*/ BEGIN TRANSACTION; UPDATE dbo.Demo2 SET b = b + 10 WHERE a = 2; COMMIT TRANSACTION; GO
/*============================================================== 30_デモ3_診断情報.sql (時間があれば) デモ③: 最適化されたロック有効時の新しい診断情報を観察する - 待機の種類: LCK_M_S_XACT_MODIFY / LCK_M_S_XACT_READ - wait_resource / ロックリソースとしての XACT ▼ 実行手順 (3 タブ使用。前提: OPTIMIZED_LOCKING = ON) 1. タブ 1 (20_デモ2_セッション1.sql): STEP 5 を実行 → a=1 を更新してトランザクションを開いたままにする 2. タブ 2: 下の [セッション 2] を実行 → 今度は「同じ行 a=1」を更新するので、 ON でも正しくブロックされる (LAQ でも、条件を満たす行に別のアクティブトランザクションの X TID ロックがあれば完了を待つ = ACID は守られる) 3. タブ 3 (このタブ): [監視クエリ] を実行 → wait_type = LCK_M_S_XACT_MODIFY、 wait_resource に XACT が見える 4. タブ 1: COMMIT → タブ 2 が解放される ==============================================================*/ /*-------------------------------------------------------------- [セッション 2] タブ 2 で実行: セッション 1 と同じ行 a=1 を更新 --------------------------------------------------------------*/ USE OptimizedLockingDemo; GO BEGIN TRANSACTION; UPDATE dbo.Demo2 SET b = b + 100 WHERE a = 1; COMMIT TRANSACTION; GO /*-------------------------------------------------------------- [監視クエリ] タブ 3 (このタブ) で実行 --------------------------------------------------------------*/ USE OptimizedLockingDemo; GO -- ブロックされている要求と待機の種類 -- 期待値: wait_type = LCK_M_S_XACT_MODIFY -- (変更目的で XACT の S ロックを待機) SELECT r.session_id, r.blocking_session_id, r.status, r.command, r.wait_type, r.wait_time, r.wait_resource, t.text AS sql_text FROM sys.dm_exec_requests AS r CROSS APPLY sys.dm_exec_sql_text(r.sql_handle) AS t WHERE r.session_id > 50 AND r.blocking_session_id <> 0; GO -- XACT ロックリソースの一覧 -- 保持側 (GRANT の X) と待機側 (WAIT の S) が -- 同じ XACT リソースに見える SELECT request_session_id, resource_type, resource_description, request_mode, request_status FROM sys.dm_tran_locks WHERE resource_type = 'XACT'; GO
/*============================================================== 99_後片付け.sql デモ用データベースの削除 ==============================================================*/ USE master; GO IF DB_ID(N'OptimizedLockingDemo') IS NOT NULL BEGIN ALTER DATABASE OptimizedLockingDemo SET SINGLE_USER WITH ROLLBACK IMMEDIATE; DROP DATABASE OptimizedLockingDemo; END GO



