SQL事务隔离级别的问题

时间:2011-02-24 09:00:54

标签: sql sql-server deadlock transaction-isolation

我遇到了事务隔离级别的问题。涉及两个表,第一个一个经常更新,事务隔离级别设置为 SERIALIZABLE 第二个一个在首先一个。

插入或更新第二个表时出现问题。几个小时后我收到错误消息:

  

由于更新冲突导致快照隔离事务中止。您不能使用快照隔离直接或间接访问数据库'DB'中的表'dbo.first'来更新,删除或插入已被另一个事务修改或删除的行。重试事务或更改更新/删除语句的隔离级别。

插入或更新 second 表时,我没有设置事务隔离级别,还运行了命令 DBCC USEROPTIONS ,它返回read_committed

我需要尽快消​​除此错误,谢谢你提前

2 个答案:

答案 0 :(得分:6)

第一:
看来,你没有使用SERIALIZABLE,而是使用MSSQL 2005引入的快照隔离。这是一篇了解差异的文章:
http://blogs.msdn.com/b/craigfr/archive/2007/05/16/serializable-vs-snapshot-isolation-level.aspx

=>这是基于错误消息,但正如您在评论中再次解释的那样,编辑第二个表时会出现错误。

第二:
对于修改,MSSQL Server总是尝试获取锁,并且由于第一个表上有锁(通过使用事务),因为它会升级到第二个表上的锁。 (外键)操作失败。因此,每次修改都会导致一个小型交易。

MSSQL上的默认事务级别为READ COMMITTED,但如果您打开选项READ_COMMITTED_SNAPSHOT,则每次使用时READ COMMITTED都会将SNAPSHOT转换为READ COMMITTED类似事务{1}}。然后会导致您收到的错误消息。

准确地说, VladV 指出,它并非真正使用SNAPSHOT隔离级别,而是使用行版本控制<{1}} 而不是锁定,但仅限于声明READ COMMITTED交易的基础上使用行版本控制。

要了解差异,请查看以下内容:
http://msdn.microsoft.com/en-us/library/ms345124(SQL.90).aspx

要了解有关SNAPSHOT的更多信息,请在此详细说明:
http://msdn.microsoft.com/en-us/library/tcbchxcb(VS.80).aspx
和这里: Default SQL Server IsolationLevel Changes

如果您未指定READ_COMMITTED_SNAPSHOT隔离,则另一个原因是使用隐式事务。在打开此选项后,您实际上并未在修改语句(您没有)上指定隔离级别,MS SQL服务器将选择他认为是正确隔离级别的任何内容。以下是详细信息:
http://msdn.microsoft.com/en-us/library/ms188317(SQL.90).aspx

对于所有这些场景,解决方案是相同的。

解决方案:
您需要按顺序执行操作,并且可以通过专门在两个操作上使用SNAPSHOT隔离级别的事务来执行此操作:插入/更新第一个时以及插入/更新时第二,
这样就可以阻止相应的其他人,直到完成为止。

答案 1 :(得分:5)

我们遇到了类似的问题 - 您很高兴知道您应该能够在不删除FK约束的情况下解决问题。

具体来说,在我们的场景中,我们经常在READ COMMITTED事务中更新父表。我们还经常发生并发(长时间运行)快照事务,这些事务需要将行插入带有FK到父表的子表 - 所以基本上它与您的情况相同,除了我们使用READ COMMITTED而不是SEREALIZABLE事务。

要解决此问题,请在FK列上的主表上创建新的UNIQUE NONCLUSTERED约束。此外,您还必须在创建唯一约束后重新创建FK,因为这将确保FK现在引用约束(而不是聚簇键)。

  

注意:缺点是您现在对表有一个看似冗余的约束,当对父表进行更新时,SQL Server需要对其进行维护。也就是说,这可能是您考虑使用不同/备用群集密钥的好机会......如果您很幸运,它甚至可以替换此表中另一个索引的需求......

不幸的是,我无法在网上找到一个很好的解释为什么创建一个独特的约束来解决问题。我能解释其原因的最简单方法是因为FK现在只引用唯一约束 - 并且对父表的修改(即对非FK引用的列)不会导致快照事务中的更新冲突,因为FK现在引用未更改的唯一约束条目。将此与群集密钥进行对比,其中对父表中任何列的更改将影响此表中的行版本 - 并且由于FK看到更新的版本号,因此快照事务需要中止。

此外,如果在非快照事务中删除父行,则会影响聚簇和唯一约束,并且正如预期的那样,快照事务将回滚(因此保持FK完整性)。

我已经能够使用上面从this blog entry改编的示例代码重现此问题

---------------------- 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