Я пытаюсь понять проблему, с которой я столкнулся, и я не верю, что это возможно при работе с транзакциями, использующими уровень изоляции чтения. У меня есть таблица, которая используется в качестве очереди. В одном потоке (соединение 1) я вставляю несколько пакетов по 20 записей в каждую таблицу. Каждая партия из 20 записей выполняется внутри транзакции. Во втором потоке (соединение 2) я выполняю обновление для изменения статуса записей, которые были вставлены в очередь, что также происходит внутри транзакции. При параллельной работе я ожидаю, что количество строк, затронутых обновлением (соединение 2), должно быть кратно 20, поскольку соединение 1 вставляет строки в таблицу, вставляется пакетами по 20 строк в рамках транзакции.
Но мое тестирование показывает, что это не всегда так, и иногда мне удается обновить подмножество записей из пакета соединения 1. Должно ли это быть возможно, или я что-то упускаю из виду в отношении транзакций, параллелизма и уровней изоляции? Ниже приведен набор тестовых сценариев, которые я создал для воспроизведения этой проблемы в T-SQL.
Этот скрипт вставляет в таблицу 20 000 записей пакетами по 20 транзакций.
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, оператор печати указывает это и количество затронутых строк.
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