SQL面试问题-用一个例子来定义死锁的解答

30 阅读5分钟

在这篇文章中,我们将尝试找出 "你能不能用一个简单的例子来定义死锁 "这个问题的答案,这个问题在数据库管理员的面试中可能会被问到。

用一个简单的例子来定义死锁

死锁是一个非常复杂的问题,有时理解这个概念会很费劲。因此,让我们试着用现实生活中的一个更具体的例子来解释死锁。在下面的图片中,每辆汽车都打算穿过十字路口,但没有一辆车得到空车道。如果没有司机把自己的车开回来,这种情况会一直持续下去。

Define Deadlock with a simple example

死锁是一种情况,即一组事务被阻塞,因为每个进程都阻塞了一个资源,并等待另一个已经被另一个进程获得锁的资源。如果以下四个条件(Coffman的条件)同时发生,就可能发生死锁。

  • 相互排斥。 一个资源在同一时间只能由一个进程持有。
  • 保持和等待: 一些进程在持有一些资源时可能会请求新的资源
  • **没有抢占。**不能从持有资源的进程中强行夺取资源,资源只能由进程自己释放。
  • 循环等待: 两个或多个进程形成一个循环链,每个进程都在等待下一个进程释放其资源

为SQL Server定义一个死锁

锁机制的主要任务是保护数据的完整性,因为数据库是由用户同时使用来读取和修改数据的。简单地说,锁机制确保每次只有一个用户可以修改数据,当另一个用户想访问相同的数据时,就会发生阻塞。一旦阻塞资源的进程完成,被阻塞的进程继续执行其任务。

死锁与这两个概念密切相关,但它是一种更复杂的情况。资源被两个或多个进程锁定,然后每个进程都想获得已经被其他进程锁定的资源上的锁。在这种情况下,两个进程都不能完成它的任务,开始等待另一个进程释放锁。然而,除非SQL Server介入,否则这种锁定状态不会被解决。 在SQL Server检测到死锁后,会根据哪个会话终止会比其他会话使用更少的资源来选择一个受害者。作为最后的行动,被选为受害者的进程被回滚,其他进程可以完成他们的工作。在定义了死锁之后,让我们用一个例子来加强这个概念。首先,我们将创建两个表并在其中插入一些样本数据。

CREATE TABLE CityListA
(
    ID INT IDENTITY PRIMARY KEY,
    CityName NVARCHAR(50)
)
INSERT INTO CityListA VALUES ('Paris'),('London'),('NewYork')
CREATE TABLE CityListB
(
    ID INT IDENTITY PRIMARY KEY,
    CityName NVARCHAR(50)
)
INSERT INTO CityListB VALUES ('Helsinki'),('Istanbul'),('Warsaw')

创建完表后,我们将在SQL Server Management Studio(SSMS)中打开两个不同的查询窗口,然后在分开的窗口中同时执行查询。不久之后,其中一个查询将返回一个死锁错误。

--Query Window 1
SET TRANSACTION ISOLATION LEVEL READ COMMITTED
BEGIN TRANSACTION
UPDATE CityListB Set 
CityName = 'Oslo' WHERE Id = 1
WAITFOR DELAY '00:00:10'
UPDATE CityListA 
Set CityName = 'Moscow' WHERE Id = 1
ROLLBACK TRAN
--Query Window 2
SET TRANSACTION ISOLATION LEVEL READ COMMITTED
BEGIN TRANSACTION
UPDATE CityListA Set 
CityName = 'Berlin' WHERE Id = 1 
WAITFOR DELAY '00:00:10'
UPDATE CityListB Set 
CityName = 'Bucharest' WHERE Id = 1	
ROLLBACK TRAN

Simulate a deadlock

我们可以看到,错误信息非常清楚,Query-2被选为受害者,并且因为死锁问题被SQL Server终止。现在让我们来详细研究一下这个死锁是如何发生的。在这个死锁场景中,我们有两个查询,他们想修改CityListA和CityListB表中的一些记录。查询-1获得了CityListA表的独占锁。同时,查询2获得了CityListB表的独占锁。在这之前,一切都在进行中,因为他们在不同的表上工作,可以访问这些表。然而,在查询等待10秒后,查询-1想要访问CityListB,但是这个表已经被查询-2锁定。同时,查询-1想要访问CityListA,但是这个表已经被查询-1锁定了。就在这个时候,死锁发生了,两个查询开始等待对方,除非有人干预,否则这种等待会一直持续下去。 SQL Server选择了其中一个查询作为受害者,然后回滚其进程。下面的图片定义了死锁的发生方式。

Define deadlock with a simple graph

监控死锁

system_healthsession是SQL Server的默认扩展事件,它在SQL Server引擎启动时开始收集数据。它有助于监控一些错误日志、锁和锁存问题,当SQL Server中出现死锁时,这个事件也会捕捉到。出于这个原因,我们可以使用这个事件来监控死锁。我们可以在SQL Server Management Studio > Management > Extended Events -> system_health下找到system_health事件。

Using system_health extended event

我们可以点击package0.event_file上的查看目标数据...来报告所有捕获的事件。我们点击过滤器按钮,然后对捕获的事件名称应用一个过滤器。

Apply filter to the extended event

我们可以在这个事件的细节中找到死锁图。

Deadlock graph

图形的形状和边缘非常清楚地定义了死锁。矩形表示参与死锁的对象。这个对象可以是一个索引,表,或行。边缘表示获得和请求获得哪些对象的锁的类型。在圆圈中,我们可以找到与死锁有关的进程细节。 被蓝线划掉的圆圈向我们展示了这个死锁的受害者。

总结

在这篇文章中,我们用一个简单的例子定义了死锁,这是一个可能被放在SQL面试中的问题。