我使用一个包含两个简单查询的小事务:select和update:
SELECT * FROM XYZ WHERE ABC = DEF
和
UPDATE XYZ SET ABC = 123
WHERE ABC = DEF
通常由两个线程启动事务的情况,并且取决于隔离级别发生死锁(RepeatableRead,Serialization)。两个事务都尝试读取和更新完全相同的行。 我想知道为什么会这样。导致死锁的查询顺序是什么?我已经阅读了一些关于锁(共享,独占)以及每个隔离级别锁定的持续时间,但我仍然不完全理解......
我甚至准备了一个总是导致死锁的简单测试。我查看了SSMS和SQL Server Profiler中的测试结果。我开始第一次查询,然后立即开始第二次查询。
首先查询:
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE
BEGIN TRANSACTION
SELECT ...
WAITFOR DELAY '00:00:04'
UPDATE ...
COMMIT
第二次查询:
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE
BEGIN TRANSACTION
SELECT ...
UPDATE ...
COMMIT
现在我无法向您显示详细的日志,但它看起来不那么像这样(我很可能错过了Lock:死锁等等):
(1) SQL:BatchStarting: First query
(2) SQL:BatchStarting: Second query
(3) Lock:timeout for second query
(4) Lock:timeout for first query
(5) Deadlock graph
如果我理解了锁,在(1)中第一个查询采用共享锁(执行SELECT),然后进入休眠状态并保持共享锁直到事务结束。在(2)中,第二个查询也采用共享锁(SELECT),但在同一行上存在共享锁时不能采用独占锁(UPDATE),这会导致Lock:timeout。但我无法解释为什么发生第二次查询的超时。可能我不太了解整个过程。任何人都可以给出一个很好的解释吗?
我没有注意到使用ReadCommitted的死锁,但我担心它们可能会发生。 你推荐什么解决方案?
答案 0 :(得分:5)
当两个或多个任务通过锁定其他任务试图锁定的资源的每个任务永久阻塞时发生死锁
答案 1 :(得分:3)
“但我无法解释为什么会发生第二次查询的超时。”
因为第一个查询包含共享锁。然后第一个查询中的更新也尝试获取独占锁,这使他睡眠。所以第一个和第二个查询都在等待另一个唤醒 - 这是一个导致超时的死锁: - )
在mysql中它运行得更好 - 立即检测死锁并回滚其中一个事务(你不需要等待超时: - ))。
此外,在mysql中,您可以执行以下操作防止死锁:
select ... for update
它将在事务开始时放置一个写锁(即独占锁),这样就可以避免死锁情况!也许你可以在数据库引擎中做类似的事情。
答案 2 :(得分:3)
对于MSSQL,有一种机制可以防止死锁。您在这里需要的是WITH NOLOCK
提示。
在99.99%的SELECT
语句中,它是可用的,不需要将SELECT与UPDATE捆绑在一起。也没有必要将SELECT放入事务中。唯一的例外是不允许脏读。
将查询更改为此表单可以解决您的所有问题:
SELECT ...
FROM yourtable WITH (NOLOCK)
WHERE ...
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE
BEGIN TRANSACTION
UPDATE ...
COMMIT
答案 3 :(得分:1)
自从我上次处理这个问题以来已经很长时间了,但我相信select语句会创建一个读锁定,它只会阻止数据被更改 - 因此多个查询可以保存并共享读锁定在相同的数据上。共享读取锁用于读取一致性,即如果事务中多次读取同一行,则读取一致性应该意味着您应该始终获得相同的结果。
update语句需要一个独占锁,因此update语句必须等待释放读锁。
这两个交易都不会释放锁,因此交易失败。
不同的数据库实现有不同的策略来处理这个问题,Sybase和MS-SQL服务器使用锁定升级和超时(从读写锁定升级) - Oracle我相信(在某些时候)通过使用回滚日志来实现读取一致性,其中MySQL具有不同的策略。