5

我有两个假设的查询:

UPDATE BankAccounts SET HomePhone = '+1 252-555-0912' 
WHERE AccountNumber = 14400000619

SELECT * FROM BankAccounts 
WHERE HomePhone = '555-1212'

在没有额外索引的假设表上:

CREATE TABLE BankAccounts 
( 
   AccountNumber bigint NOT NULL PRIMARY KEY CLUSTERED,
   FirstName nvarchar(50) NOT NULL,
   MiddleName nvarchar(50) NULL,
   LastName nvarchar(50) NOT NULL,
   HomePhone varchar(50) NULL,
   IsClosed tinyint DEFAULT 0
)

一切都会很好。如果我在以下位置添加索引HomePhone

CREATE INDEX IX_BankAccounts_HomePhone ON BankAccounts 
( HomePhone)

现在我的SELECT陈述可能成为僵局的受害者:

Tranasction(进程 ID 169)与另一个进程在锁资源上死锁,并已被选为死锁牺牲品。重新运行事务。

常见的建议是:

  • 以相同的顺序访问表
  • 尽可能缩短交易时间

除了这种情况:

  • 我正在以相同的顺序访问(一个)表(1 选择 1 是 1)
  • 交易是一个单一的声明;我不能比那更短

消除这种死锁的长期解决方案是什么?

我正在考虑将我的交易隔离级别更改为READ UNCOMMITTED(即消除完整性),但因为我实际上正在处理一个金融系统,所以我很犹豫是否允许客户两次提取他的全部余额。

我能找到的唯一其他解决方案来自知识库文章 83252

SQL Server 技术公告 - 如何解决死锁

...死锁无法避免。这就是为什么应该设计前端应用程序来处理死锁。

在设计良好的应用程序中,前端应用程序应捕获 1205 错误,重新连接到 SQL Server,然后重新提交事务。

我猜这是在说:“不能赢;不要尝试”

还要别的吗?

4

2 回答 2

4

如果将 替换SELECT *为列列表(或考虑所有列),然后将它们作为INCLUDEd添加到索引中,则SELECT查询将不需要查询聚集索引即可完成。然后SELECT可以始终运行到完成。

如果您不想这样做,那么与其更改整个隔离级别,我会考虑是否适当的锁定提示。您是否需要仔细考虑SELECT适当的提示(在理想情况下,有一种方法可以指定但获取有关是否实际发生任何跳过行的信息)。NOLOCKREADPASTREADPAST

当然,也可以考虑SNAPSHOT 隔离(如果没有锁,就没有死锁)。


(一个贪吃的惩罚也可以考虑TABLOCKXSELECT语句上使用。它可以防止死锁并确保你读取一行(如果存在),对并发的巨大惩罚)

于 2013-01-28T14:46:51.303 回答
3

赢不了;不要尝试

这是正确的。您可能看到了读写死锁(没有死锁图只是我们的推测)。反向顺序访问的发生正是因为您正在做正确的事情(即您确实拥有正确的索引)。即使您访问不同的密钥(查找一部手机,更新另一部手机),由于哈希冲突,您仍然在玩概率游戏。

您可以接受死锁或诉诸快照隔离模型。

于 2013-01-28T15:20:10.090 回答