什么是 SQL Server 中的死锁以及何时出现?
死锁有哪些问题以及如何解决?
一般来说,死锁意味着两个或多个实体正在阻塞某些源,并且它们都无法完成,因为它们以循环方式阻塞源。
一个例子:假设我有表 A 和表 B,我需要在 A 和 B 中进行一些更新,然后我决定在使用时锁定它们(这确实是愚蠢的行为,但它现在可以达到它的目的)。同时,其他人以相反的顺序做同样的事情——先锁定 B,然后锁定 A。
按时间顺序,会发生这种情况:
proc1: 锁 A
proc2:锁 B
proc1: Lock B - 开始等待直到 proc2 释放 B
proc2:锁定 A - 开始等待直到 proc1 释放 A
他们都不会完成。那是一个僵局。实际上,这通常会导致超时错误,因为不希望任何查询永远挂起,并且底层系统(例如数据库)将终止未及时完成的查询。
一个真实世界的死锁示例是,当您将房钥匙锁在车内,车钥匙锁在房内时。
当两个并发事务无法进行时,就会发生死锁,因为每个事务都在等待另一个释放锁,如下图所示。
因为两个事务都处于锁获取阶段,所以没有一个事务在获取下一个之前释放锁。
如果您使用依赖于锁的并发控制算法,那么总是存在在死锁情况下运行的风险。死锁可能发生在任何并发环境中,而不仅仅是在数据库系统中。
例如,如果两个或多个线程正在等待先前获得的锁,则多线程程序可能会死锁,因此没有线程可以取得任何进展。如果这发生在 Java 应用程序中,JVM 不能仅仅强制线程停止执行并释放其锁。
即使Thread
该类公开了一个stop
方法,该方法自 Java 1.1 以来已被弃用,因为它可能导致对象在线程停止后处于不一致的状态。取而代之的是,Java 定义了一种interrupt
方法,该方法充当提示,因为被中断的线程可以简单地忽略中断并继续执行。
出于这个原因,Java 应用程序无法从死锁情况中恢复,应用程序开发人员有责任以永远不会发生死锁的方式对锁获取请求进行排序。
但是,数据库系统无法强制执行给定的锁获取顺序,因为无法预见某个事务将进一步获取哪些其他锁。保持锁定顺序成为数据访问层的职责,而数据库只能协助从死锁情况中恢复。
数据库引擎运行一个单独的进程,该进程扫描当前冲突图的锁定等待周期(由死锁引起)。当检测到一个循环时,数据库引擎选择一个事务并中止它,导致其锁被释放,以便另一个事务可以进行。
与 JVM 不同,数据库事务被设计为一个原子工作单元。因此,回滚使数据库处于一致状态。
虽然数据库选择回滚两个被卡住的事务之一,但并不总是可以预测哪一个会被回滚。根据经验,数据库可能会选择以较低的回滚成本回滚事务。
根据Oracle 文档,检测到死锁的事务是其语句将被回滚的事务。
DEADLOCK_PRIORITY
SQL Server 允许您通过会话变量控制在死锁情况下哪个事务更有可能回滚。
会话可以接受 -10 到 10 之间的DEADLOCK_PRIORITY
任何整数,或预定义的值,例如LOW (-5)
、NORMAL (0)
或HIGH (5)
。
如果发生死锁,当前事务将回滚,除非其他事务具有较低的死锁优先级值。如果两个事务具有相同的优先级值,则 SQL Server 以最低的回滚成本回滚事务。
如文档中所述,PostgreSQL 不保证要回滚哪个事务。
MySQL 尝试回滚修改最少记录数的事务,因为释放更少的锁成本更低。
死锁是当两个人需要多个资源来执行,并且其中一些资源被每个人锁定时发生的情况。这导致了一个事实,即如果没有 B 拥有的东西,A 就无法执行,反之亦然。
假设我有 Person A 和 Person B。他们都需要运行两行(Row1 和 Row2)。
人员 A 无法运行,因为它需要 Row2,人员 B 无法运行,因为它需要 Row1。任何人都无法执行,因为他们锁定了对方需要的东西,反之亦然。
减少死锁的一种相当简单的方法是在所有复杂事务中,您应该以相同的顺序执行操作。换句话说,以相同的顺序访问 Table1 然后访问 Table2。这将有助于减少发生的死锁数量。
当两个(或更多)事务都在等待释放对方持有的锁时,可能会导致僵局。
死锁是当一个进程或线程进入等待状态,因为请求的系统资源被另一个等待进程占用