【问题标题】:How can I fix "Snapshot isolation transaction aborted due to update conflict"?如何解决“由于更新冲突而中止快照隔离事务”?
【发布时间】:2011-07-03 09:53:06
【问题描述】:

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

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

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

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

【问题讨论】:

  • (为什么不在可序列化的 tx 中也对第二个表进行插入/更新?)
  • @pst 我可以,但我无法理解第二次操作的行为。它的作用就像设置了 SNAPSHOT 隔离级别,但事实并非如此。我没有为第二次操作声明任何隔离级别

标签: sql sql-server deadlock transaction-isolation


【解决方案1】:

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

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

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

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

确切地说,正如 VladV 指出的那样,它并不是真正使用 SNAPSHOT 隔离级别,而是 READ COMMITTED 带有行版本控制而不是锁定,但仅在一个statement基础,其中SNAPSHOT在transaction基础上使用行版本控制。

要了解差异,请查看以下内容:
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
和这里: Default SQL Server IsolationLevel Changes

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

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

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

【讨论】:

  • 我在第一次操作时使用'SERIALIZABLE',not'SNAPSHOT',在第二次操作时我没有声明任何隔离级别。让我感到困惑的是,第二个操作的行为就像设置了“SNAPSHOT”,但实际上并没有
  • 啊,好吧,这是因为如果您没有指定,那么每个更新/插入/删除都会通过一个迷你事务执行。然后它使用默认隔离级别READ COMITTED。在 MS SQL 2005 中,您可以告诉 SQL Server 始终对所有 READ COMMITTED 事务使用 SNAPSHOT 隔离(该选项称为 READ_COMMITTED_SNAPSHOT)。因此 second 表的操作在 SNAPSHOT Isolation mode 中执行。解决方案是对 second 表也使用SERIALIZABLE。
  • 我使用以下查询 SELECT name, snapshot_isolation_state, is_read_committed_snapshot_on FROM sys.databases 检查了我的数据库上的设置。它返回 snapshot_isolation_state=1, is_read_committed_snapshot_on=0
  • 好的,很高兴知道。我已经用另一种解释更新了这个问题。
  • 只是让你知道,我决定删除外键,它不是正确的解决方案,但它解决了问题
【解决方案2】:

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

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

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

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

不幸的是,我在网上找不到一个很好的解释,说明为什么创建唯一约束可以解决问题。我可以解释为什么这有效的最简单方法是因为 FK 现在只引用唯一约束 - 并且对父表的修改(即对非 FK 引用的列)不会导致快照事务中的更新冲突作为 FK现在引用 unchanged 唯一约束条目。将此与聚集键进行对比,其中对父表中任何列的更改都会影响此表中的行版本 - 由于 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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-02-25
    • 2016-09-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多