我试图理解我遇到的一个问题,我认为在处理使用读提交隔离级别的事务时应该不可能。我有一个用作队列的表。在一个线程(连接 1)中,我将多批 20 条记录插入到每个表中。每批 20 条记录在一个事务中执行。在第二个线程(连接 2)中,我执行更新以更改已插入队列的记录的状态,这也发生在事务中。当同时运行时,我的期望是受更新影响的行数(连接 2)应该是 20 的倍数,因为连接 1 在事务中以 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