阻塞:是指当一个数据库会话中的事务,正在锁定其他会话事务想要读取或修改的资源,造成这些会话发出的请求进入等待的状态。SQL Server 默认会让被阻塞的请求无限期地一直等待
,直到原来的事务释放相关的锁,或直到它超时 (根据 SET LOCK_TIMEOUT参数)、进程被杀死、服务器关闭。一般系统中,偶尔有短时间的阻塞是正常且合理的;但若设计不良的程序,就可能导致长时间的阻塞,这样就不必要地锁定了资源,而且阻塞了其他会话欲读取或更新的需求,系统表现为很卡。
项目中平时遇到的大多数是阻塞问题,比如系统页面报超时错误、SSMS中执行一条原本很快的SQL却卡住执行不了或需要执行很久、甚至整个数据库处于瘫痪卡死状态(严重情况)等。
阻塞的原因有很多,这里简单罗列下:
1.并发执行大量表扫描的SQL,由于缺失索引,造成大量阻塞,这种现象非常常见,例如网站首页中某个模块加载的SQL中缺失索引,导致网站首页访问超时。
2.隔离级别的问题,SQL Server默认隔离级别为READ COMMITTED,级别越高,并发性越差,造成阻塞的几率也越高。
3.长时间运行的SQL或者存储过程,由于执行效率较低或内容较多,事务期间会阻塞住其他会话执行
死锁:是指多个事务会话互相等待对方释放持有的锁而造成的环路,如果没有外部干预,死锁将无限期持续下去。Sqlserver 中默认产生死锁时,会根据事务开销选择终止并回滚其中某一个事务,让其他事务获取所需的锁完成事务提交
。如果系统出现大量死锁,会导致用户使用过程中出现大量死锁报错页面,事务数据无法正常提交,影响系统使用。
死锁发生的现象往往是很明显的,页面会出现报错,如下图
阻塞现象:系统访问超时、卡慢
死锁现象:系统访问报错
死锁其实也是由于阻塞造成的,会话之间先是阻塞,然后又因为产生交叉访问,造成环路,最终死锁。死锁的发生其实和阻塞是类似的,只不过阻塞是一种串行的等待,而死锁是交叉环路。
等待方式:默认情况下,当数据库出现阻塞时,如果没有人工干预,那么该会话会一直阻塞下去,直到阻塞该会话的事务结束,或直到LOCK_TIMEOUT设置的超时等待时间(默认为0即无限); 而当出现死锁时,数据库会立即根据事务的回滚开销,来选择终止(回滚)其中一个事务,进而解除死锁,所以死锁会造成操作无效。
之前在公司平台发布栏目里发布了SQLServer 性能排查指南
,可以下载参阅,下面简单说下操作。
当系统突然发生了超时或卡顿等情况,可以先使用下面的脚本查看数据库阻塞情况,正常情况下没有结果或者出现少量短时间阻塞,问题发生时,通常会出现大量长时间阻塞,可以根据显示的BlockingSession_TSQL进行查看是SQL性能问题还是程序问题,必要时可以选择kill掉阻塞的会话来解决。
SELECT R.session_id AS WaitingSessionID ,
S.session_id AS BlockingSessionID ,
Q1.text AS WaitingSession_TSQL ,
Q2.text AS BlockingSession_TSQL ,
A.wait_duration_ms as WAIT_DURATION_MS ,
S.original_login_name AS BlockingSession_LoginName ,
S.program_name AS BlockingSession_ApplicationName ,
S.host_name AS BlockingSession_HostName
FROM sys.dm_exec_requests AS R
INNER JOIN sys.dm_exec_sessions AS S ON R.blocking_session_id = S.session_id
INNER JOIN sys.dm_exec_connections AS C1 ON R.session_id = C1.most_recent_session_id
INNER JOIN sys.dm_exec_connections AS C2 ON S.session_id = C2.most_recent_session_id
INNER JOIN sys.dm_os_waiting_tasks AS A ON A.session_id = C1.most_recent_session_id
CROSS APPLY sys.dm_exec_sql_text(C1.most_recent_sql_handle) AS Q1
CROSS APPLY sys.dm_exec_sql_text(C2.most_recent_sql_handle) AS Q2
示例:
当系统中出现死锁时,可以使用SQl Profile进行死锁监控(具体方法参考SQLServer 性能排查指南),捕捉到死锁后进行分析,看是否是由于SQL缺失索引导致。
SQL Server 通过设置最大并行度来限制并行计划执行时所用的处理器数,默认为0,即不限制,由SQL Server自己控制并行。
并行开销阈值:指定SQLServer创建和运行并行查询计划的阈值,仅当运行同一查询的串行计划的估计开销高于在“并行的开销阈值”中设置的值时,SQLServer才创建和运行该查询的并行计划。开销指的是在特定硬件配置中运行串行计划估计需要花费的时间(秒)。
我们的业务系统一般都是为OLTP
,理想情况下事务很短,这时候其实就没必要通过并行来提高运行速度,所以对于这类系统来说,可以把最大并行度设为1,也就是不适用并行操作,从而减少不必要的资源等待。
如果是OLAP
,由于事务普遍较长,所以并行操作往往能提高速度和资源利用率。
个人观点:对于最大并行度,项目上出现过几次该问题,导致系统访问超时,修改为1也就是关闭最大并行度后,问题解决。对于我们公司的系统来说,大部分事务都很短,不需要并行,小部分较长的事务可以改写SQL来使用并行; 另外也可以调整并行阈值,同时设置并行度为0或是固定值;
SELECT * FROM TEST WHERE OBJECT_ID =1 OPTION (MAXDOP 8)
没有最好的方法,只有最适合的方法