13

以下陈述:

INSERT INTO dbo.Changes([Content], [Date], [UserId], [CompanyId]) 
  VALUES (@1, @2, @3, @4);
SELECT @@identity;

给了我这个 SQL 错误 3960:

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

据我了解,从错误消息中,我不应该dbo.Companies在另一个连接正在修改期间更新、删除或插入表dbo.Companies

但是为什么当我向另一个表dbo.Changes(具有外键dbo.Companies)插入新行并且我没有删除引用的行时会发生这种情况dbo.Companies,但我只是更新行dbo.Companies而不是主键?这应该可以正常工作,不是吗?(这是 SQL Server 中的错误吗?)

更新:

表格如下所示:

dbo.Changes([Id] int PK, [Content] nvarchar, 
  [Date] datetime, [UserId] int, [CompanyId] int -> dbo.Companies.[Id])
dbo.Companies([Id] int PK, [Name] nvarchar)

第二次更新正在做:

UPDATE dbo.Companies WHERE [Id] = @1 SET [Name] = @2;
4

2 回答 2

8

似乎 SQL Server将在它必须读取的任何记录上获取更新锁,即使它没有修改它

有关此microsoft.public.sqlserver.server 线程的更多信息:

如果没有关于 CustomerContactPerson 的支持索引,则声明

从联系人中删除,其中 ID = @ID;

将需要对 CustomerContactPerson 中的所有行进行“当前”读取,以确保没有引用已删除 ContactPerson 行的 CustomerContactPerson 行。使用索引,DELETE 可以确定 CustomerContactPerson 中没有相关行,而无需读取受其他事务影响的行。

此外,在快照事务中,用于读取您将要转身和更新的数据的模式是在您读取时采用 UPDLOCK。这可确保您基于“当前”数据而不是“一致”(快照)数据进行更新,并且当您发布 DML 时,数据不会被锁定,并且您不会无意中覆盖另一个会话的变化。

我们的解决方法是向外键添加索引

在您的示例中,我怀疑向 Changes.CompanyId 添加索引会有所帮助。我不确定这是否是一个真正的解决方案。SQL Server 优化器可以选择不使用索引吗?

于 2012-12-11T16:30:27.360 回答
3

SQL Server 可以看到对依赖表的更新,它可以修改插入的行为......对我来说似乎很公平,因为 SQL 无法猜测其他逻辑可能依赖于 [name] 列(触发器等)

如果您的应用程序实现了死锁重试逻辑,您可以修改它们以将错误号 3960 与错误号 1205 相同并自动重试...

于 2012-07-14T09:29:09.430 回答