web-dev-qa-db-ja.com

実行中のトランザクション内のトランザクション分離レベルを変更する

私はSQL Serverのトランザクション分離レベルを研究していて、トランザクションの存続期間中に分離レベルが変化したときのSQL Serverの動作を理解しようとしています。

SQL Serverでは次のようなことが可能であるようです。

BEGIN TRANSACTION;

SET TRANSACTION ISOLATION LEVEL READ COMMITTED;

/* (some selects/inserts/updates/deletes) */

SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;

/* (some selects/inserts/updates/deletes) */

COMMIT TRANSACTION;

正直に言うと、分離レベルの低下が理にかなっている例を考えることはできません。トランザクションの一部がシリアル化可能な分離を必要とし、他の部分は必要としないいくつかのシナリオしか考えられません。スナップショットを使用した分離レベルと行のバージョン管理を他の種類の分離レベルと混在させることはうまくいかないように感じますが、これをバックアップするための多くの情報が見つかりません。

単一のトランザクションがその存続期間中に複数の分離レベルを切り替えるのは、実際に起こっていることですか?知っておくべき注意点と詳細はありますか?

6
Tom Pažourek

使用している構文は有効です。SET TRANSACTION ISOLATION LEVELコマンドの後に実行されるすべてのステートメントは、指定された分離レベルを使用します。

次の表を検討してください。

CREATE TABLE dbo.test
(
    ID int identity(1,1) Primary key
    , Field nvarchar(10)
)
GO

INSERT INTO dbo.test(Field)
Values ('XXXXXXXXXX')
GO 100

次に、トランザクションを開始し、2つのステートメントを実行します。
最初のステートメントはREPEATABLE READを使用し(ステートメントの完了時に共有ロックを解放しません)、2番目のステートメントはSERIALIZABLEを使用します(範囲ロックを取得して保持します)

BEGIN TRANSACTION

SET TRANSACTION ISOLATION LEVEL REPEATABLE READ

SELECT *
FROM dbo.test 
WHERE id = 100

-- This will show a shared lock on key 100, the lock is kept during the transaction because repeatable read is used
SELECT *
FROM sys.dm_tran_locks 
WHERE request_session_id = @@SPID

-- Isolation level 3 = repeatable read
SELECT transaction_isolation_level 
FROM sys.dm_exec_sessions 
WHERE session_id = @@SPID

SET TRANSACTION ISOLATION LEVEL SERIALIZABLE

SELECT *
FROM dbo.test
WHERE id >= 0 and id < 10

-- This will show the previous shared lock on key 100 + additional RangeS-S key locks taken by the serializable statement
SELECT *
FROM sys.dm_tran_locks
WHERE request_session_id = @@SPID

-- Isolation level 4 = Serializable
SELECT transaction_isolation_level 
FROM sys.dm_exec_sessions 
WHERE session_id = @@SPID

COMMIT TRANSACTION

sys.dm_tran_locksおよびsys.dm_exec_sessionsの出力を見ると、2つのステートメントが異なるロックを発行し、同じトランザクション内で異なる分離レベルを使用していることがわかります。技術的には可能ですが、私はトランザクション内で単一の分離レベルを使用する傾向があります。

SNAPSHOT分離を悲観的ロックと組み合わせると、予期しない結果が生じる可能性があります。

SNAPSHOT分離でトランザクションを開始し、いくつかのデータを読み取ることを想像してください。
次に、2番目のセッションで、読み取ったばかりのデータを更新します。
最初のトランザクションがREAD COMMITTEDに切り替わり、再度データを読み取ると、SQL Serverは更新された値を返します。
最後に、再びSNAPSHOT分離に切り替えて、データを読み取ります。以前に作成したスナップショットを使用して読み取り、古い値が返されます。

-- Note: you have to set your isolation level to snapshot before starting your transaction otherwise you will get an error

SET TRANSACTION ISOLATION LEVEL SNAPSHOT 

BEGIN TRANSACTION 

-- Returns XXXXXXXXXX
SELECT * 
FROM dbo.test 
WHERE id = 1

-- Run "UPDATE dbo.test SET Field = 'YYYYYYYYYY' WHERE id = 1" in a separate session

SET TRANSACTION ISOLATION LEVEL READ COMMITTED 

-- Returns YYYYYYYYYY
SELECT * 
FROM dbo.test
WHERE id = 1

SET TRANSACTION ISOLATION LEVEL SNAPSHOT 

-- Returns XXXXXXXXXX
SELECT * 
FROM dbo.test 
WHERE id = 1

COMMIT
3
Thomas Costers