Проблема с уровнем изоляции транзакции SQL - PullRequest
7 голосов
/ 24 февраля 2011

У меня проблема с уровнями изоляции транзакций. Используются две таблицы: first одна часто обновляется с уровнем изоляции транзакции, установленным на SERIALIZABLE , second одна имеет внешний ключ на first один.

Проблема возникает при выполнении вставки или обновления таблицы second . Раз в несколько часов я получаю сообщение об ошибке:

Транзакция изоляции моментального снимка прервана из-за конфликта обновления. Вы не можете использовать изоляцию моментальных снимков для прямого или косвенного доступа к таблице «dbo.first» в базе данных «БД», чтобы обновить, удалить или вставить строку, которая была изменена или удалена другой транзакцией. Повторите транзакцию или измените уровень изоляции для оператора update / delete.

Я не устанавливаю уровень изоляции транзакции при вставке или обновлении второй таблицы, также я выполнил команду DBCC USEROPTIONS , и она возвращает read_committed

Мне нужно как можно скорее устранить эту ошибку, спасибо вперед

Ответы [ 2 ]

6 голосов
/ 24 февраля 2011

Первое:
Кажется, вы используете не SERIALIZABLE, а изоляцию моментального снимка, которая была представлена ​​в MSSQL 2005. Вот статья, чтобы понять разницу:
http://blogs.msdn.com/b/craigfr/archive/2007/05/16/serializable-vs-snapshot-isolation-level.aspx

=> Это было основано на ошибке, сообщении, но, как вы уже объяснили в комментариях, ошибка возникает при редактировании второй таблицы.

Second:
Для изменений MSSQL Server всегда пытается получить блокировкии поскольку в таблице first имеются блокировки (с помощью транзакции), которые перерастают в блокировки в таблице second из-за (внешнего ключа) операция завершается неудачей.Таким образом, каждая модификация фактически вызывает мини-транзакцию.

Уровень транзакции по умолчанию на MSSQL равен READ COMMITTED, но если вы включите опцию READ_COMMITTED_SNAPSHOT, она будет конвертировать READ COMMITTED в SNAPSHOT подобную транзакцию каждый раз, когда вы используете READ COMMITTED.Который затем приводит к сообщению об ошибке, которое вы получаете.

Если быть точным, как указал VladV , он на самом деле не использует SNAPSHOT уровень изоляции, а READ COMMITTED с версионированием строк , а не блокировкой, а толькона выражении , где SNAPSHOT использует управление версиями строк на транзакции .

Чтобы понять разницу, проверьте это:
http://msdn.microsoft.com/en-us/library/ms345124(SQL.90).aspx

Чтобы узнать больше о READ_COMMITTED_SNAPSHOT, подробно его объясните здесь:
http://msdn.microsoft.com/en-us/library/tcbchxcb(VS.80).aspx
и здесь: Изменения по умолчанию SQL Server IsolationLevel

Другая причина, по которой вы видите SNAPSHOT изоляцию, если вы ее не указали, заключается в использовании неявной транзакции .После включения этой опции, и вы фактически не указываете уровень изоляции в модифицирующем операторе (чего вы не делаете), сервер MS SQL выберет то, что он считает правильным уровнем изоляции.Вот подробности:
http://msdn.microsoft.com/en-us/library/ms188317(SQL.90).aspx

Для всех этих сценариев решение одно и то же.

Решение:
Вам необходимо выполнить операции последовательно, и выэто можно сделать, в частности, , используя транзакцию с SERIALIZABLE уровнем изоляции для обеих операций : при вставке / обновлении первой и при вставке / обновлении второй.
Таким образом, вы блокируете соответствующую другую до тех пор, показавершено.

5 голосов
/ 11 ноября 2015

У нас была похожая проблема - и вы были бы рады узнать, что вы сможете решить эту проблему, не снимая ограничения FK.

В частности, в нашем сценарии мы часто обновлялиродительская таблица в транзакции READ COMMITTED.У нас также были частые параллельные (длительные) транзакции моментальных снимков, которые требовались для вставки строк в дочернюю таблицу с FK в родительскую таблицу - так что по сути это тот же сценарий, что и у вас, за исключением того, что мы использовали транзакцию READ COMMITTED вместо SEREALIZABLE.

Чтобы решить эту проблему, создайте новое ограничение UNIQUE NONCLUSTERED для первичной таблицы над столбцом FK.Кроме того, вы также должны заново создать FK после того, как создали уникальное ограничение, поскольку это гарантирует, что FK теперь ссылается на ограничение (а не на кластерный ключ).

Примечание. Недостатком является то, что теперь у вас есть внешне избыточное ограничение для таблицы, которое должно поддерживаться сервером SQL при обновлении родительской таблицы.Тем не менее, это может быть хорошей возможностью для вас рассмотреть другой / альтернативный кластеризованный ключ ... и, если вам повезет, это может даже заменить потребность в другом индексе в этой таблице ...

К сожалению, я не могу найти хорошего объяснения в Интернете о том, почему создание уникального ограничения решает проблему.Самый простой способ объяснить, почему это работает, заключается в том, что FK теперь ссылается только на уникальное ограничение, а изменение родительской таблицы (т. Е. Столбцов, не связанных с FK) не вызывает конфликт обновления в транзакции моментального снимка как FK.теперь ссылается на неизменную уникальную запись ограничения.Сравните это с кластеризованным ключом, где изменение любого столбца в родительской таблице повлияет на версию строки в этой таблице - и, поскольку FK видит обновленный номер версии, транзакцию моментального снимка необходимо прервать.

Кроме того, еслиродительская строка удаляется в транзакции без снимка, затем будут затронуты как кластерные, так и уникальные ограничения, и, как и ожидалось, транзакция моментального снимка будет откатываться (так что целостность FK сохраняется).

У меня естьудалось воспроизвести эту проблему, используя приведенный выше пример кода, который я адаптировал из этой записи в блоге

---------------------- SETUP Test database
-- Creating Customers table without unique constraint
USE master;
go

IF EXISTS (SELECT * FROM sys.databases WHERE name = 'SnapshotTest')
BEGIN;
DROP DATABASE SnapshotTest;
END;
go

CREATE DATABASE SnapshotTest;
go

ALTER DATABASE SnapshotTest
SET ALLOW_SNAPSHOT_ISOLATION ON;
go

USE SnapshotTest;
go

CREATE TABLE Customers
   (CustID int NOT NULL PRIMARY KEY,CustName varchar(40) NOT NULL);

CREATE TABLE Orders
  (OrderID char(7) NOT NULL PRIMARY KEY,
   OrderType char(1) CHECK (OrderType IN ('A', 'B')),
   CustID int NOT NULL REFERENCES Customers (CustID)
  );

INSERT INTO Customers (CustID, CustName) VALUES (1, 'First test customer');

INSERT INTO Customers (CustID, CustName) VALUES (2, 'Second test customer');
GO

---------------------- TEST 1: Run this test before test 2
USE SnapshotTest;
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
BEGIN TRANSACTION;

-- Check to see that the customer has no orders
SELECT * FROM Orders WHERE  CustID = 1;

-- Update the customer
UPDATE Customers SET CustName='Updated customer' WHERE  CustID = 1;
-- Twiddle thumbs for 10 seconds before commiting
WAITFOR DELAY '0:00:10';

COMMIT TRANSACTION;
go

-- Check results
SELECT * FROM Customers (NOLOCK);
SELECT * FROM Orders (NOLOCK);
GO

---------------------- TEST 2: Run this test in a new session shortly after test 1
USE SnapshotTest;
SET TRANSACTION ISOLATION LEVEL SNAPSHOT;
BEGIN TRANSACTION;

SELECT * FROM   Customers WHERE  CustID = 1;
INSERT INTO Orders (OrderID, OrderType, CustID) VALUES ('Order01', 'A', 1);

-- Twiddle thumbs for 10 seconds before commiting
WAITFOR DELAY '0:00:10';

COMMIT TRANSACTION;
go

-- Check results
SELECT * FROM Customers (NOLOCK);
SELECT * FROM Orders (NOLOCK);
go

И чтобы исправить вышеприведенный сценарий, переустановите тестовую базу данных.Затем запустите следующий скрипт перед запуском Тестов 1 и 2.

ALTER TABLE Customers 
ADD CONSTRAINT UX_CustID_ForSnapshotFkUpdates UNIQUE NONCLUSTERED (CustID)

-- re-create the existing FK so it now references the constraint instead of clustered index (the existing FK probably has a different name in your DB)
ALTER TABLE [dbo].[Orders] DROP CONSTRAINT [FK__Orders__CustID__1367E606]

ALTER TABLE [dbo].[Orders]  WITH CHECK ADD FOREIGN KEY([CustID])
REFERENCES [dbo].[Customers] ([CustID])
GO
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...