SQL Server搞出的写死锁:解决之道?

1. 概述

在进行数据库开发和维护时,我们经常会遇到死锁的问题。尤其是在高并发的情况下,死锁的发生概率会大大增加。本文将以SQL Server数据库为例,介绍以下内容:

什么是死锁。

为什么会发生死锁。

如何避免死锁。

如何解决死锁。

2. 什么是死锁

死锁是指两个或两个以上的进程在执行过程中,因互相持有资源而造成的一种互不相让的状态,导致进程永久阻塞的情况。简单来说,就是多个进程都在等待对方释放资源,导致所有进程都无法继续执行。

3. 为什么会发生死锁

死锁的发生通常需要以下条件:

互斥条件:资源不能被共享,一次只能被一个进程占用。

请求与保持条件:进程已经占用了一个资源,又请求其他的资源。

不剥夺条件:资源不能被其他进程抢占,只能由占用它的进程主动释放。

环路等待条件:多个进程形成一个环路,每个进程请求下一个进程占用的资源。

如果以上条件都满足,就有可能发生死锁。

4. 如何避免死锁

在SQL Server中,可以采取以下方法来避免死锁的发生:

尽量缩短事务的持续时间。

对事务使用合适的隔离级别。

对表、索引、视图等对象使用适当的锁。

尽量不要在事务中使用外部连接。

使用合理的索引。

5. 如何解决死锁

如果死锁已经发生了,可以采取以下方法来解决:

增大死锁检测时间间隔,减少死锁的发生概率。

修改应用程序代码,在使用时避免死锁的发生。

通过KILL SESSION或KILL CONNECTION语句终止死锁的会话。

通过T-SQL脚本解开死锁。

5.1 T-SQL脚本解开死锁

如果死锁已经发生,一种解决方法是使用T-SQL脚本解开死锁。

首先,需要通过以下语句来查看当前死锁的情况:

SELECT resource_type, resource_associated_entity_id, request_mode,request_status, request_mode,request_session_id,resource_description= CASE

WHEN resource_type='OBJECT' THEN OBJECT_SCHEMA_NAME(resource_associated_entity_id)+'.'+OBJECT_NAME(resource_associated_entity_id)

ELSE PVT.specific_resource_name

END,

request_sql_text=ST.text

FROM sys.dm_tran_locks L

JOIN sys.dm_exec_requests R ON L.request_session_id=R.session_id

OUTER APPLY sys.dm_exec_sql_text(R.sql_handle)St

CROSS APPLY (

SELECT CASE LEFT(l.resource_type,1)

WHEN 'K'

THEN 'lock_' + coalesce(KC.parent_class_desc, KC.lock_class_desc)

ELSE l.resource_type

END+' '+CASE WHEN Resource_database_id=Db_Id()THEN 'LOCAL'ELSE 'Global' END+' ' + COALESCE(OBJECT_NAME(l.resource_associated_entity_id), pvt.specific_resource_name,'') specific_resource_name

FROM sys.dm_tran_locks L

LEFT JOIN sys.dm_locks H ON L.lock_owner_address=H.lock_address

LEFT JOIN sys.dm_os_memory_cache_entries CE WITH (NOLOCK) ON H.lock_owner_address=CE.lock_owner_address

LEFT JOIN sys.dm_os_memory_objects O WITH (NOLOCK) ON CE.page_address=O.page_address

LEFT JOIN sys.dm_exec_connections C WITH (NOLOCK) ON L.request_session_id=C.session_id

LEFT JOIN sys.dm_exec_sessions S WITH (NOLOCK) ON C.session_id=S.session_id

LEFT JOIN (

SELECT parent_class_desc, lock_class_desc, request_mode

FROM sys.dm_tran_locks L

JOIN sys.dm_xe_objects xeo ON L.lock_owner_address=xeo.address

JOIN sys.dm_xe_object_columns xec ON xeo.object_name=xeC.object_name AND L.resource_type=xeC.column_value

WHERE xeo.object_type='lock/lock_owner'

GROUP BY parent_class_desc, lock_class_desc, request_mode

) KC ON KC.lock_class_desc=L.lock_class_desc AND KC.request_mode=L.request_mode AND KC.parent_class_desc=L.parent_class_desc

LEFT JOIN (

SELECT srm.session_id, ar.request_id, SUBSTRING(st.text, (ar.statement_start_offset/2)+1, ((CASE ar.statement_end_offset WHEN -1 THEN DATALENGTH(st.text) ELSE ar.statement_end_offset END - ar.statement_start_offset)/2)+1) request_text,ar.plan_generation_num,request_mode

FROM sys.dm_exec_requests ar WITH (NOLOCK)

JOIN sys.dm_exec_sessions srm WITH (NOLOCK) ON ar.session_id=srm.session_id

CROSS APPLY sys.dm_exec_sql_text(ar.sql_handle) st

) PVT ON PVT.session_id=L.request_session_id AND PVT.request_id=R.request_id AND PVT.plan_generation_num=R.plan_generation_num

WHERE L.resource_associated_entity_id > 0 OR L.parent_resource_address IS NOT NULL

) PVT ON PVT.request_session_id=L.request_session_id AND PVT.resource_type=L.resource_type AND PVT.resource_associated_entity_id=L.resource_associated_entity_id

WHERE R.session_id IN(SELECT blocking_session_id FROM sys.dm_exec_requests WHERE blocking_session_id>0)

OR R.blocking_session_id>0

ORDER BY L.request_session_id, L.resource_associated_entity_id, CASE WHEN (CASE WHEN R.blocking_session_id IS NULL THEN 0 ELSE 1 END) <>0 THEN 0 ELSE 1 END, R.blocking_session_id DESC

然后,可以在程序中执行以下语句来强制解开死锁:

KILL [session_id]

需要注意的是,强制解开死锁有可能会丢失数据,因此需要谨慎使用。

6. 总结

死锁是数据库开发中经常遇到的问题,如果不及时解决,会对系统造成严重的影响。因此,在进行数据库开发时,需要尽量避免死锁的发生,并准备好处理方法。

数据库标签