7

我看到与事务隔离级别相关的错误消息。涉及两个表,一个经常更新,事务隔离级别设置为SERIALIZABLE第二个在一个上有外键。

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

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

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

4

2 回答 2

7

第一:
看来,你不是在使用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为类似事务。然后导致您收到错误消息。 SNAPSHOTREAD 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 Changes

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

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

解决方案:
您需要按顺序执行操作,您可以通过在两个操作上专门使用具有SERIALIZABLE隔离级别的事务来执行此操作:插入/更新第一个操作时和插入/更新第二个操作时。
这样你就可以阻止另一个,直到它完成。

于 2011-02-24T09:15:58.233 回答
5

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

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

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

注意:缺点是当对父表进行更新时,您现在对需要由 SQL Server 维护的表有一个看似多余的约束。也就是说,这可能是您考虑使用不同/备用聚集键的好机会……如果幸运的话,它甚至可以取代此表上对另一个索引的需求……

不幸的是,我在网上找不到一个很好的解释,说明为什么创建唯一约束可以解决问题。我可以解释为什么这有效的最简单方法是因为 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
于 2015-11-11T03:48:48.420 回答