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