読み取りコミットされた分離レベルを利用してトランザクションを処理する場合、可能ではないと思われる問題に遭遇したことを理解しようとしています。キューとして使用されているテーブルがあります。1つのスレッド(接続1)で、20レコードの複数のバッチを各テーブルに挿入します。20レコードの各バッチは、トランザクション内で実行されます。2番目のスレッド(接続2)で、更新を実行して、キューに挿入されたレコードのステータスを変更します。これは、トランザクション内でも発生します。同時に実行する場合、接続1はトランザクション内の20行のバッチでテーブル挿入に行を挿入するため、更新(接続2)の影響を受ける行数は20の倍数である必要があります。
しかし、私のテストでは、これが常に当てはまるとは限らず、接続1のバッチからレコードのサブセットを更新できる場合があります。これは可能でしょうか、それともトランザクション、同時実行性、分離レベルについて何かが足りないのでしょうか?以下は、T-SQLでこの問題を再現するために作成した一連のテストスクリプトです。
このスクリプトは、20のトランザクションバッチで20,000レコードをテーブルに挿入します。
USE ReadTest
GO
SET TRANSACTION ISOLATION LEVEL READ COMMITTED
GO
SET NOCOUNT ON
DECLARE @trans_id INTEGER
DECLARE @cmd_id INTEGER
DECLARE @text_str VARCHAR(4000)
SET @trans_id = 0
SET @text_str = 'Placeholder String Value'
-- First empty the table
DELETE FROM TABLE_A
WHILE @trans_id < 1000 BEGIN
SET @trans_id = @trans_id + 1
SET @cmd_id = 0
BEGIN TRANSACTION
-- Insert 20 records into the table per transaction
WHILE @cmd_id < 20 BEGIN
SET @cmd_id = @cmd_id + 1
INSERT INTO TABLE_A ( transaction_id, command_id, [type], status, text_field )
VALUES ( @trans_id, @cmd_id, 1, 1, @text_str )
END
COMMIT
END
PRINT 'DONE'
このスクリプトは、テーブル内のレコードを更新し、ステータスを1から2に変更してから、更新操作からの行数をチェックします。行数が20の倍数ではなく、printステートメントがこれと影響を受ける行数を示している場合。
USE ReadTest
GO
SET TRANSACTION ISOLATION LEVEL READ COMMITTED
GO
SET NOCOUNT ON
DECLARE @loop_counter INTEGER
DECLARE @trans_id INTEGER
DECLARE @count INTEGER
SET @loop_counter = 0
WHILE @loop_counter < 100000 BEGIN
SET @loop_counter = @loop_counter + 1
BEGIN TRANSACTION
UPDATE TABLE_A SET status = 2
WHERE status = 1
and type = 1
SET @count = @@ROWCOUNT
COMMIT
IF ( @count % 20 <> 0 ) BEGIN
-- Records in concurrent transaction inserting in batches of 20 records before commit.
PRINT '*** Rowcount not a multiple of 20. Count = ' + CAST(@count AS VARCHAR) + ' ***'
END
IF @count > 0 BEGIN
-- Delete the records where the status was changed.
DELETE TABLE_A WHERE status = 2
END
END
PRINT 'DONE'
このスクリプトは、ReadTestという新しいデータベースにテストキューテーブルを作成します。
USE master;
GO
IF EXISTS (SELECT * FROM sys.databases WHERE name = 'ReadTest')
BEGIN;
DROP DATABASE ReadTest;
END;
GO
CREATE DATABASE ReadTest;
GO
ALTER DATABASE ReadTest
SET ALLOW_SNAPSHOT_ISOLATION OFF
GO
ALTER DATABASE ReadTest
SET READ_COMMITTED_SNAPSHOT OFF
GO
USE ReadTest
GO
CREATE TABLE [dbo].[TABLE_A](
[ROWGUIDE] [uniqueidentifier] NOT NULL,
[TRANSACTION_ID] [int] NOT NULL,
[COMMAND_ID] [int] NOT NULL,
[TYPE] [int] NOT NULL,
[STATUS] [int] NOT NULL,
[TEXT_FIELD] [varchar](4000) NULL
CONSTRAINT [PK_TABLE_A] PRIMARY KEY NONCLUSTERED
(
[ROWGUIDE] ASC
) ON [PRIMARY]
) ON [PRIMARY]
ALTER TABLE [dbo].[TABLE_A] ADD DEFAULT (newsequentialid()) FOR [ROWGUIDE]
GO