4

这两条语句在关系数据库中是否可能死锁?我试图简化我的问题和示例——请假设这些选择,我认为通常只需要共享读锁定,现在需要独占读锁:

Concurrent Connection 1:
SELECT {...}
FROM A 
JOIN B ON {...}

Concurrent Connection 2:
SELECT {...}
FROM B 
JOIN A ON {...}

也就是说,连接的顺序是否重要?SQL中的单个语句是原子的吗?是否在第一个语句中先锁定 A 然后 B 并且在第二个语句中先锁定 B 然后再锁定 A?

我认为不会——我的直觉告诉我,无论多么复杂,像这样的两个单一语句都不会死锁。我相信一个语句是作为一个整体进行分析的,并且需要锁定的资源是使用某种确定性的全局顺序(即按字母顺序)锁定的。但我需要的不仅仅是直觉——我想不出一种方法来证明它,我也找不到它的记录。

我对 MS SQL 2005 感兴趣,但我认为问题不是特定于实现的。

其次:由于它与 MS SQL 相关,我还想知道 Common Table Expressions 也有这个保证——CTE 主要是一种语法优势(+递归),由引擎合并到一个传统的单个语句中。

4

3 回答 3

9

SELECT 不能与其他 SELECT 死锁,因为它们只获取共享锁。您说我们应该考虑这些 SELECT 现在“需要独占读锁”,但这对我们来说是不可能的,因为 1) 没有 anexlusive read lock和 2) 读取不获取独占锁。

但是你确实提出了一个更普遍的问题,简单的语句是否会死锁。答案是肯定的、响亮的YES。锁是在执行时获取的,而不是预先分析和排序,然后以某种顺序获取。引擎不可能预先知道所需的锁,因为它们依赖于磁盘上的实际数据,并且读取引擎需要的数据...锁定数据。

由于不同的索引访问顺序而导致的简单语句(SELECT vs. UPDATE 或 SELECT vs. DELETE)之间的死锁很常见,并且很容易调查、诊断和修复。但请注意,始终涉及写入操作,因为读取不能相互阻塞。对于本次讨论,向 SELECT 添加 UPDLOCK 或 XLOCK 提示应视为写入。您甚至不需要 JOIN,二级索引很可能会引入导致死锁的访问顺序问题,请参阅读/写死锁

最后,写作SELECT FROM A JOIN B或写作SELECT FROM B JOIN A完全无关紧要。查询优化器可以自由地重新排列它认为合适的访问顺序,查询的实际文本不会以任何方式强加执行顺序。

更新

那么,我们如何构建一个不会死锁的 READ COMMITTED “多实体”数据库的通用策略呢?

恐怕没有千篇一律的食谱。解决方案将取决于具体情况。最终,在数据库应用程序中,死锁已成为现实。我理解这可能听起来很荒谬,就像“我们登陆月球但我们无法编写正确的数据库应用程序”一样,但有很多因素在起作用,这几乎可以保证应用程序最终会遇到死锁。幸运死锁是容易处理错误的,简单的再次读取状态,应用逻辑,重新写入新状态。话虽如此,有一些好的做法可以显着降低死锁的频率,直到它们几乎消失了:

  • 尝试对Writes使用一致的访问模式。有明确定义的规则说明诸如“事务应始终按以下顺序排列:Customers-> OrderHeaders-> OrderLines。” 请注意,必须在transaction中遵守 order 。基本上,对架构中的所有表进行排名,并指定所有更新必须按排名顺序进行。这最终归结为编写代码的个人贡献者的代码纪律,因为它必须确保它的写入是在事务中以正确的顺序更新。
  • 减少写入的持续时间。通常的智慧是这样的:在事务开始时进行所有读取(读取现有状态),然后处理逻辑并计算新值,然后在事务结束时写入所有更新。避免像“读->写->逻辑->读->写”这样的模式,而是做“读->读->逻辑->写->写”。当然,真正的技巧在于如何处理实际的、真实的、个别的情况,而显然这是必须的。必须在事务中写入。这里必须特别注意一种特定类型的事务:由队列驱动的事务,根据定义,它们通过从队列中出列(= 写入)开始其活动。众所周知,这些应用程序总是难以编写并且容易出错(特别是死锁),幸运的是有办法做到这一点,请参阅使用表作为队列
  • 减少读取量。表扫描是导致死锁最常见原因。正确的索引不仅可以消除死锁,还可以提高过程中的性能。
  • 快照隔离。在避免僵局方面,这是最接近免费午餐的方法。我故意把它放在最后,因为它可能掩盖其他问题(如不正确的索引)而不是修复它们。

LockCustomerByXXX恐怕用一种方法来解决这个问题是行不通的。悲观锁定无法扩展。如果您想获得任何体面的性能,乐观的并发更新是您的选择。

于 2010-07-15T18:54:50.573 回答
0

据我所知,您是对的:SQL 引擎确定它需要做什么(可能在它解析查询时),锁定所有必需的资源,执行查询,然后解锁它们。

于 2010-07-15T17:07:20.393 回答
0

读取不会彼此死锁。您还必须进行一些写作。

你可以做一些事情来减少死锁的数量。例如,在支持行锁定并避免更新记录的平台上,仅在聚集索引的末尾插入。啊哈哈,现在 Facebook 的 UI 更有意义了。

有时处理死锁比避免死锁更容易。服务器将失败并返回报告,您可以重试。

于 2010-07-15T21:42:11.347 回答