【IT168 专稿】死锁产生的情形是由于两个事务彼此互相等待对方放弃各自的锁造成的。
当出现这种情况时,SQL Server会自动选择一个关掉进程,允许另一个进程继续执行来结束死锁。关闭的事务会被回滚并抛出一个错误的消息发送给执行该进程的用户。一般来说,事务需要最少数量的开销来回滚锁撤销的事务。
这篇文章将解释如何以一种友好的方式来处理死锁问题。
死锁
事务A企图更新表1并且同时从第2张表执行读或更新操作,而事务B其它更新表2并同时从表1执行读或更新操作。再这种情形下,事务A打开锁以便事务B需要完成它的任务,反之亦然;这样事务都不能完成直到其它事务释放锁为止。
死锁的解决方案
下面的示例展示了两个事务之间造成死锁的情形。
事务 A
UPDATE Customer SET LastName = 'John' WHERE CustomerId=111
WAITFOR DELAY '00:00:05' -- Wait for 5 ms
UPDATE Orders SET CustomerId = 1 WHERE OrderId = 221
COMMIT TRANSACTION
事务B
UPDATE Orders SET ShippingId = 12 WHERE OrderId = 221
WAITFOR DELAY '00:00:05' -- Wait for 5 ms
UPDATE Customer SET FirstName = 'Mike' WHERE CustomerId=111
COMMIT TRANSACTION
如果两个事务都在同一时间执行,那么事务A会锁住并更新Customer表,而此时事务B会锁住并更新Orders表。延迟5 ms之后,事务A会寻找锁住的Orders表而该表已经被事务B锁住,此时,事务B会寻找被事务A锁住的Customer表。因此,两个事务一直都不能执行,死锁产生了,并且SQL server会为放弃的事务返回一个错误消息1205.
Msg 1205, Level 13, State 45, Line 5
Transaction (Process ID 52) was deadlocked on lock resources with
another process and has been chosen as the deadlock victim.
Rerun the transaction.
但是如果你不想看到系统默认的处理行为(放弃事务)应该如何实现呢?你可以改变它吗?是的,可以,通过重写下面展示的事务A和事务B即可实现。
事务 A
BEGIN TRANSACTION
BEGIN TRY
UPDATE Customer SET LastName = 'John' WHERE CustomerId=111
WAITFOR DELAY '00:00:05' -- Wait for 5 ms
UPDATE Orders SET CustomerId = 1 WHERE OrderId = 221
COMMIT TRANSACTION
END TRY
BEGIN CATCH
PRINT 'Rollback Transaction'
ROLLBACK TRANSACTION
IF ERROR_NUMBER() = 1205 -- Deadlock Error Number
BEGIN
WAITFOR DELAY '00:00:00.05' -- Wait for 5 ms
GOTO RETRY -- Go to Label RETRY
END
END CATCH
事务 B
BEGIN TRANSACTION
BEGIN TRY
UPDATE Orders SET ShippingId = 12 Where OrderId = 221
WAITFOR DELAY '00:00:05' -- Wait for 5 ms
UPDATE Customer SET FirstName = 'Mike' WHERE CustomerId=111
COMMIT TRANSACTION
END TRY
BEGIN CATCH
PRINT 'Rollback Transaction'
ROLLBACK TRANSACTION
IF ERROR_NUMBER() = 1205 -- Deadlock Error Number
BEGIN
WAITFOR DELAY '00:00:00.05' -- Wait for 5 ms
GOTO RETRY -- Go to Label RETRY
END
END CATCH
这里我在两个事务的开始时使用了标签RETRY来实现。TRY/CATCH 方法用来在事务中处理异常。如果写在TRY块中的代码失败了,控制器会自动地跳转到CATCH块,让事务回滚,并且如果异常是由于死锁(Error_Number 1205)而产生的异常,事务将等待5毫秒。这里使用延迟是因为其它的事务(未放弃的事务)在延迟时间内能够完成它的操作并释放自己锁住的表。你可以根据事务的大小来增加延迟的时间。延迟之后,通过使用下面的元素事务开始从开始标签执行(RETRY: Label RETRY在事务的开始处):
该语句用于转移名为RETRY的控制标签(开始处)。
现在,同时执行事务A和事务B。两个事务将会执行成功。再次看看出现事务异常发生的地方将会得到不通的信息。
Rollback Transaction
(1 row(s) affected)
//(1 row(s) affected)