Sql 为什么会出现死锁?

Sql 为什么会出现死锁?,sql,sql-server-2008,transactions,deadlock,isolation-level,Sql,Sql Server 2008,Transactions,Deadlock,Isolation Level,我使用一个由两个简单查询组成的小事务:选择和更新: SELECT * FROM XYZ WHERE ABC = DEF 及 通常情况下,事务由两个线程启动,并根据隔离级别发生死锁RepeatableRead、Serialization。两个事务都尝试读取和更新完全相同的行。 我想知道为什么会这样。导致死锁的查询顺序是什么?我已经读了一些关于锁共享、独占以及每个隔离级别的锁持续时间的文章,但我仍然不完全理解 我甚至准备了一个总是导致死锁的简单测试。我已经在SSMS和SQL Server Prof

我使用一个由两个简单查询组成的小事务:选择和更新:

SELECT * FROM XYZ 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:deadlock等

(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秒钟内,查询还接受shared lock SELECT,但当同一行上存在共享锁时,无法进行独占锁更新,这将导致lock:timeout。但我无法解释为什么第二次查询会超时。可能我不太了解整个过程。有人能给个好的解释吗

我还没有注意到使用ReadCommitted时出现死锁,但我担心它们可能会发生。
你推荐什么解决方案

当两个或多个任务通过每个任务对其他任务试图锁定的资源进行锁定而永久性地相互阻塞时,就会发生死锁


但我无法解释为什么第二次查询会超时

因为第一个查询持有共享锁。然后,第一个查询中的更新也尝试获取独占锁,这会使他睡着。因此,第一个和第二个查询都在休眠,等待另一个被唤醒,这是一个导致超时的死锁:-

在mysql中,它工作得更好-死锁立即被检测到,其中一个事务被回滚,您无需等待超时:-

此外,在mysql中,您可以执行以下操作以防止死锁:


这将在事务开始时放置一个写锁,即独占锁,这样可以避免死锁情况!也许您可以在数据库引擎中执行类似的操作。

自从我上次处理这个问题以来,已经有很长一段时间了,但我相信select语句会创建一个读锁,它只会阻止数据被更改,因此多个查询可以持有并共享同一数据的读锁。共享读取锁定用于读取一致性,即如果事务中多次读取同一行,则读取一致性应意味着您应始终获得相同的结果

update语句需要独占锁,因此update语句必须等待读锁释放

这两个事务都不会释放锁,因此事务失败


不同的数据库实现有不同的策略来处理这一问题,Sybase和MS SQL Server使用带超时的锁升级从读锁升级到写锁-Oracle我相信在某种程度上通过使用回滚日志实现了读一致性,MySQL还有不同的策略。

对于MSSQL,有一种机制可以防止死锁。这里需要的是WITH NOLOCK提示

在99.99%的SELECT语句中,它是可用的,不需要将SELECT与更新捆绑在一起。也不需要在事务中放入SELECT。唯一的例外是不允许脏读

将查询更改为此表单将解决您的所有问题:

SELECT ...
FROM yourtable WITH (NOLOCK)
WHERE ...

SET TRANSACTION ISOLATION LEVEL SERIALIZABLE
BEGIN TRANSACTION
UPDATE ...
COMMIT

我的意思是第一次查询超时,但这是第二次超时。我的错误。在大卫的帖子之后,一切都很清楚:但是谢谢你的回答。这两者的原因是一样的——原因是僵局。试着看看你是否能做到我在回答中提到的预防措施。在SQL Server中处理死锁和锁升级时,可以通过使用隔离级别来解决一些锁争用问题。然而,这可能会带来数据不准确的问题。Kendra little在这里画了一幅小漫画进行比较:MSDN隔离级别artickle在这里:这不是你要问的,但为什么你要先选择,然后再更新?换句话说,为什么不使用只包含update语句的事务呢?我简化了我的问题。首先我检查上次修改日期,然后根据值做一些事情,然后更改日期。在这个事务中有更多的查询,但是上面的这些查询会导致死锁问题。我试图完全理解原因是什么,因为我以前从未听说过inter]net中的锁和信息不能满足我的需求
nough:在我的情况下,脏读不是问题,所以我决定选择ReadUncommitted。我使用realizable只是为了看看会发生什么,这是一种实验:应该是可序列化的,而不是可实现的:我忘了说我使用的是c,但这没关系。如果隔离级别是可序列化的,这怎么可能发生?!检查下面的链接,以更好地描述事务级别,Muhammad使用WITH NOLOCK更改所有查询?
select ... for update
SELECT ...
FROM yourtable WITH (NOLOCK)
WHERE ...

SET TRANSACTION ISOLATION LEVEL SERIALIZABLE
BEGIN TRANSACTION
UPDATE ...
COMMIT