# 各类数据库死锁、阻塞问题排查

# 一、Sqlserver数据库

1、sqlserver头锁检查:

DECLARE @TRACK_DATETIME VARCHAR(23);


IF object_id(N'tempdb..#TAB_TRACK_EXECSQL') IS NOT NULL
BEGIN
      EXEC('DROP TABLE #TAB_TRACK_EXECSQL'); 
END;
	 
CREATE TABLE #TAB_TRACK_EXECSQL(
   HEAD_LOCK             INT,              -- 头锁
   SQL_TEXT              NVARCHAR(MAX),    -- SQL语句
   FETCH_CURSOR          INT,              -- fetch_cursor状态
   CPU_TIME              INT,              -- 运行时间
   SESSION_ID            INT,              -- 会话ID
   PROC_COUNT            INT,              -- 运行线程数量
   EXEC_COMMAND          NVARCHAR(50),     -- 命令
   BLOCKING_SESSION_ID   INT,              -- 阻塞其会话的会话ID
   DATABASE_NAME         NVARCHAR(128),    -- 数据库名称
   REQUEST_ID            INT,              -- 请求ID
   START_TIME            NVARCHAR(25),     -- 开始时间
   EXEC_STATUS           NVARCHAR(50),     -- 状态
   PHY_READS             BIGINT,           -- 物理读次数
   PHY_WRITES            BIGINT,           -- 写次数
   LOGICAL_READS         BIGINT,           -- 逻辑读次数
   ROW_COUNT             BIGINT,           -- 返回结果行数
   WAIT_TYPE             NVARCHAR(80),     -- 等待资源类型
   WAIT_TIME             INT,              -- 等待时间
   WAIT_RESOURCE         NVARCHAR(256),    -- 等待的资源
   CLIENT_NET_ADDRESS    NVARCHAR(80),     -- 客户端地址
   P_LOGINAME            NVARCHAR(80),     -- 登录用户名
   QUERY_HASH            binary(8),        -- SQL的HASH
   TRACK_DATETIME        NVARCHAR(25),     -- 采集时刻 
    QUERY_PLAN        NVARCHAR(MAX),    -- SQL执行计划 
    SQL_PARAM       NVARCHAR(MAX),    -- SQL变量绑定参数
    TOTAL_ELAPSED_TIME  INT,                 -- 总的经过时间
    TRANSACTION_ID  BIGINT,                  -- 执行此请求的事务的 ID
    OPEN_TRANSACTION_COUNT INT,     --为此请求打开的事务数
    OPEN_RESULTSET_COUNT INT,         --为此请求打开的结果集的个数
    LOCAL_NET_ADDRESS NVARCHAR(80),     --提供服务的本地ip
    TRACK_JOB_NAME NVARCHAR(50)          --采集job名称       
);
	  
    SET @TRACK_DATETIME=convert(char(23),getdate(),121);
	
	with t_proc AS (SELECT spid,count(*) proc_count,max(loginame) loginame from  sys.sysprocesses group by spid)
       INSERT INTO #TAB_TRACK_EXECSQL
           (HEAD_LOCK
           ,SQL_TEXT
           ,FETCH_CURSOR
           ,CPU_TIME
           ,SESSION_ID
           ,PROC_COUNT
           ,EXEC_COMMAND
           ,BLOCKING_SESSION_ID
           ,DATABASE_NAME
           ,REQUEST_ID
           ,START_TIME
           ,EXEC_STATUS
           ,PHY_READS
           ,PHY_WRITES
           ,LOGICAL_READS
           ,ROW_COUNT
           ,WAIT_TYPE
           ,WAIT_TIME
           ,WAIT_RESOURCE
           ,CLIENT_NET_ADDRESS
           ,P_LOGINAME
           ,QUERY_HASH
           ,TRACK_DATETIME
           ,QUERY_PLAN
           ,SQL_PARAM
           ,TOTAL_ELAPSED_TIME
           ,TRANSACTION_ID
           ,OPEN_TRANSACTION_COUNT
           ,OPEN_RESULTSET_COUNT
	   ,LOCAL_NET_ADDRESS
           ,TRACK_JOB_NAME)				  
     SELECT  
        0 as HEAD_LOCK, --头锁
        (case when substring(dest.text,1,16)='FETCH API_CURSOR' then
          (SELECT top 1 t.text
             FROM sys.dm_exec_cursors (der.session_id ) c
             CROSS APPLY sys.dm_exec_sql_text (c.sql_handle) t)
         else dest.text END) AS SQL_TEXT,  --SQL语句
        (case when substring(dest.text,1,16)='FETCH API_CURSOR' then 1 else 0 END) AS FETCH_CURSOR, --fetch_cursor状态
        der.cpu_time, --运行时间
        der.session_id, --会话ID
        t_proc.proc_count, --运行线程数
        der.command, --命令
        der.blocking_session_id, --阻塞其会话的会话ID
        DB_NAME(der.database_id) AS DATABASE_NAME, --数据库名称
        der.request_id, --请求ID
        convert(char(23),der.start_time,121) AS start_time,--开始时间
        der.status, --状态
        der.reads, --物理读次数
        der.writes, --写次数
        der.logical_reads, --逻辑读次数
        der.row_count, --返回结果行数
        der.wait_type, --等待资源类型
        der.wait_time, --等待时间
        der.wait_resource, --等待的资源
        conn.client_net_address, --客户端地址
        t_proc.loginame, --登录用户名
        der.query_hash, --SQL的HASH
        @TRACK_DATETIME as TRACK_DATETIME, --采集时刻
        dest_plan.query_plan,  -- '执行计划XML'
        (case when charindex('<ParameterList>',dest_plan.query_plan)>0 then
           substring(dest_plan.query_plan,charindex('<ParameterList>',dest_plan.query_plan),
		          charindex('</ParameterList>',dest_plan.query_plan)-charindex('<ParameterList>',dest_plan.query_plan)+16
				  )
        else '' end) AS SQL_PARAM,  -- SQL变量绑定参数
		der.total_elapsed_time, --总经过时间
		der.transaction_id,-- 请求的事务id
		der.open_transaction_count,--打开的事务数
		der.open_resultset_count,  --结果集的个数
		conn.local_net_address, --服务端本地地址
                'TRACK_EXECSQLTEXT_JOB024' as TRACK_JOB_NAME --采集job名称
        FROM sys.dm_exec_requests  der 
        left join sys.dm_exec_connections conn  on (conn.session_id = der.session_id)
        left join t_proc on (t_proc.spid=der.session_id)
        CROSS APPLY sys.dm_exec_sql_text(der.sql_handle) AS dest 
        CROSS APPLY sys.dm_exec_text_query_plan(der.plan_handle,DEFAULT,DEFAULT) AS dest_plan
        WHERE der.session_id<> @@spid; -- 不取本数据库的操作


-- 写入执行完sql但是头锁的连接最近执行sql内容

with blocked_sid AS (SELECT blocking_session_id,MAX(TRACK_JOB_NAME) AS TRACK_JOB_NAME,MAX(TRACK_DATETIME) AS TRACK_DATETIME 
                     from #TAB_TRACK_EXECSQL 
					 where TRACK_DATETIME=@TRACK_DATETIME and blocking_session_id>0 and blocking_session_id<>session_id 
                     GROUP BY blocking_session_id)
INSERT INTO #TAB_TRACK_EXECSQL(HEAD_LOCK,BLOCKING_SESSION_ID,SESSION_ID,SQL_TEXT,SQL_PARAM,FETCH_CURSOR,CLIENT_NET_ADDRESS,TRACK_DATETIME,TRACK_JOB_NAME,LOCAL_NET_ADDRESS,P_LOGINAME)
SELECT 0 AS HEAD_LOCK,0 AS BLOCKING_SESSION_ID,C.SESSION_ID,S.TEXT AS SQL_TEXT,'最近执行内容' AS SQL_PARAM,-1 AS FETCH_CURSOR,C.CLIENT_NET_ADDRESS,A.TRACK_DATETIME,A.TRACK_JOB_NAME
,LOCAL_NET_ADDRESS,(SELECT max(loginame) loginame from  sys.sysprocesses WHERE SPID=A.blocking_session_id) AS P_LOGINAME
from blocked_sid a,sys.dm_exec_connections C  --执行连接,最近执行的查询信息
cross apply sys.dm_exec_sql_text(C.most_recent_sql_handle) S
where C.SESSION_ID=a.blocking_session_id and not exists (select 1 from #TAB_TRACK_EXECSQL where TRACK_DATETIME=A.TRACK_DATETIME and session_id=a.blocking_session_id);

-- 改写头锁标志
       UPDATE #TAB_TRACK_EXECSQL SET HEAD_LOCK=1 
       WHERE SESSION_ID IN (select blocking_session_id 
                            FROM #TAB_TRACK_EXECSQL 
                            WHERE TRACK_DATETIME=@TRACK_DATETIME AND blocking_session_id>0 group by blocking_session_id)
       AND blocking_session_id=0 AND TRACK_DATETIME=@TRACK_DATETIME;

-- 返回查询记录
WITH CX_ROWS AS (SELECT * FROM #TAB_TRACK_EXECSQL  WHERE TRACK_DATETIME=@TRACK_DATETIME)
    ,NOCX_ROWS AS (
	SELECT BLOCKING_SESSION_ID,MAX(TRACK_DATETIME) AS TRACK_DATETIME
	               FROM CX_ROWS A WHERE  BLOCKING_SESSION_ID>0 AND NOT EXISTS (SELECT 1 FROM CX_ROWS WHERE SESSION_ID=A.BLOCKING_SESSION_ID)
				    GROUP BY BLOCKING_SESSION_ID
				   )
    ,CX_TAB AS (
	            SELECT * FROM CX_ROWS
	            UNION ALL
				SELECT 1,'无SQL',0,0,BLOCKING_SESSION_ID,0,'',0,'',0,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL
				,NULL,NULL,NULL,TRACK_DATETIME,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL
				FROM NOCX_ROWS
				)
    ,CTE AS ( 
	 select a.*,rowNumber = ROW_NUMBER() OVER(PARTITION BY a.TRACK_DATETIME ORDER BY a.HEAD_LOCK DESC,a.SESSION_ID) from CX_TAB a  
	             WHERE BLOCKING_SESSION_ID=0
                union all
                select a.*,rowNumber = ROW_NUMBER() OVER(ORDER BY a.SESSION_ID) from CX_TAB a 
				join cte b on (a.BLOCKING_SESSION_ID = b.SESSION_ID AND A.TRACK_DATETIME=B.TRACK_DATETIME )
            )
     ,CTE_ALL AS (
	SELECT A.*, locks_path=CONVERT(VARCHAR(100), session_id), sortOrder = CONVERT(VARCHAR(100), RIGHT(CONCAT('00000', rowNumber), 5)) FROM CTE A 
	WHERE A.BLOCKING_SESSION_ID=0 
	UNION ALL
	SELECT S.*, locks_path=CONVERT(VARCHAR(100), P.locks_path +'->' + cast(S.session_id as varchar)) ,sortOrder = CONVERT(VARCHAR(100), P.sortOrder + RIGHT(CONCAT('00000', S.rowNumber), 5))  
	FROM CTE S INNER JOIN CTE_ALL P ON S.BLOCKING_SESSION_ID = P.SESSION_ID AND S.TRACK_DATETIME=P.TRACK_DATETIME
    )
SELECT 
   HEAD_LOCK AS N'头锁',
   locks_path AS N'锁树',
   '"'+SQL_TEXT+'"' AS N'SQL语句',
   FETCH_CURSOR AS N'fetch状态',
   '"'+SQL_PARAM+'"' AS N'变量参数',
   TOTAL_ELAPSED_TIME AS N'总经过时间',
   CPU_TIME AS N'CPU时间',
   SESSION_ID AS N'会话ID',
   PROC_COUNT AS N'线程数量',
   EXEC_COMMAND AS N'命令',
   BLOCKING_SESSION_ID AS N'阻塞其会话ID',
   DATABASE_NAME AS N'数据库名称',
   REQUEST_ID AS N'请求ID',
   START_TIME AS N'开始时间',
   EXEC_STATUS AS N'状态',
   PHY_READS AS N'物理读次数',
   PHY_WRITES AS N'写次数',
   LOGICAL_READS AS N'逻辑读次数',
   ROW_COUNT AS N'返回结果行数',
   WAIT_TYPE AS N'等待资源类型',
   WAIT_TIME AS N'等待时间',
   WAIT_RESOURCE AS N'等待的资源',
   CLIENT_NET_ADDRESS AS N'客户端地址',
   P_LOGINAME AS N'登录用户名',
   QUERY_HASH AS N'SQL的HASH',
   TRACK_DATETIME AS N'采集时刻',
   TRANSACTION_ID AS N'请求的事务id',
   OPEN_TRANSACTION_COUNT AS N'打开的事务数',
   OPEN_RESULTSET_COUNT AS N'结果集的个数',
   LOCAL_NET_ADDRESS   AS N'提供服务的本地ip',
   sortOrder
FROM CTE_ALL
union all
	select
	   HEAD_LOCK AS N'头锁',
   cast(SESSION_ID as varchar) AS N'锁树',
   '"'+SQL_TEXT+'"' AS N'SQL语句',
   FETCH_CURSOR AS N'fetch状态',
   '"'+SQL_PARAM+'"' AS N'变量参数',
   TOTAL_ELAPSED_TIME AS N'总经过时间',
   CPU_TIME AS N'CPU时间',
   SESSION_ID AS N'会话ID',
   PROC_COUNT AS N'线程数量',
   EXEC_COMMAND AS N'命令',
   BLOCKING_SESSION_ID AS N'阻塞其会话ID',
   DATABASE_NAME AS N'数据库名称',
   REQUEST_ID AS N'请求ID',
   START_TIME AS N'开始时间',
   EXEC_STATUS AS N'状态',
   PHY_READS AS N'物理读次数',
   PHY_WRITES AS N'写次数',
   LOGICAL_READS AS N'逻辑读次数',
   ROW_COUNT AS N'返回结果行数',
   WAIT_TYPE AS N'等待资源类型',
   WAIT_TIME AS N'等待时间',
   WAIT_RESOURCE AS N'等待的资源',
   CLIENT_NET_ADDRESS AS N'客户端地址',
   P_LOGINAME AS N'登录用户名',
   QUERY_HASH AS N'SQL的HASH',
   TRACK_DATETIME AS N'采集时刻',
   TRANSACTION_ID AS N'请求的事务id',
   OPEN_TRANSACTION_COUNT AS N'打开的事务数',
   OPEN_RESULTSET_COUNT AS N'结果集的个数',
  LOCAL_NET_ADDRESS   AS N'提供服务的本地ip',
   '自身或父类存在交叉无法生成' AS sortOrder 
	from CX_TAB cc
	where not exists (select 1 from CTE_ALL where session_id=cc.session_id)
ORDER BY TRACK_DATETIME DESC,sortOrder ;

# 二、达梦数据库

1、查询当前sql

select datediff(ss,last_recv_time,sysdate) ss,dbms_lob.substr(sf_get_session_sql(sess_id)),sess_id,substr(clnt_ip,8,13) from v$sessions where state='ACTIVE' order by 1 desc;

2、查询阻塞

SELECT SYSDATE STATTIME,DATEDIFF(SS,S1.LAST_SEND_TIME,SYSDATE) SS,

 '被阻塞的信息'  WT,S1.SESS_ID WT_SESS_ID,S1.SQL_TEXT WT_SQL_TEXT,S1.STATE WT_STATE,S1.TRX_ID WT_TRX_ID,

 S1.USER_NAME WT_USER_NAME,S1.CLNT_IP WT_CLNT_IP,S1.APPNAME WT_APPNAME,S1.LAST_SEND_TIME WT_LAST_SEND_TIME,

 '引起阻塞的信息' FM,S2.SESS_ID FM_SESS_ID,S2.SQL_TEXT FM_SQL_TEXT,S2.STATE FM_STATE,S2.TRX_ID FM_TRX_ID,

 S2.USER_NAME FM_USER_NAME,S2.CLNT_IP FM_CLNT_IP,S2.APPNAME FM_APPNAME,S2.LAST_SEND_TIME FM_LAST_SEND_TIME

 FROM V$SESSIONS S1,V$SESSIONS S2,V$TRXWAIT W

 WHERE S1.TRX_ID=W.ID

  AND S2.TRX_ID=W.WAIT_FOR_ID;

3、查询死锁涉及的事务信息

select dh.trx_id,sh.sess_id,wm_concat(top_sql_text) from V$DEADLOCK_HISTORY dh,V$SQL_HISTORY sh where dh.trx_id=sh.trx_id and dh.sess_id=sh.sess_id group by dh.trx_id,sh.sess_id;

# 三、Oracle数据库

1、查询会话、活动会话

select * from v$session where username is not null and status = 'ACTIVE' order by logon_time, sid;

select * from v$session where username is not null order by logon_time, sid;

2、查oracle各种锁源头

SELECT   
         SUBSTR(sys_connect_by_path(s.INST_ID||'#'||s.SID, '-->'),4) AS TREE_SID,
         S.INST_ID,  -- 对于RAC的节点
         S.SID,      -- 会话ID
         S.SERIAL# , 
         S.STATUS,   -- 会话状态
		 S.MACHINE,  -- 客户端机器名
         S.PROGRAM,  -- 客户端运行程序
         S.SQL_ID,   -- 执行sql的id
         B.SQL_TEXT, -- 执行sql的文本
         (CASE WHEN LENGTHB(B.SQL_TEXT)<3990 THEN TO_CLOB('') ELSE B.SQL_FULLTEXT END) SQL_FULLTEXT, -- sql长度超过3990字节才取fulltext,否则返回空
         S.WAIT_CLASS,  -- 等待类型
         S.EVENT,        -- 等待事件
         S.SECONDS_IN_WAIT, --等待时间(秒)
         TO_CHAR(S.SQL_EXEC_START,'YYYY-MM-DD HH24:MI:SS') SQL_EXEC_START, -- SQL执行开始时间
         TO_CHAR(S.LOGON_TIME,'YYYY-MM-DD HH24:MI:SS') LOGON_TIME,         -- 会话登录时间
         ( select LISTAGG(B.object_name,','||CHR(13)) WITHIN group (ORDER BY B.OWNER) from GV$LOCKED_OBJECT A,DBA_OBJECTS B
           where A.inst_id=S.inst_id AND A.session_id =S.SID
           AND B.owner=S.username AND B.object_id=A.object_id) LOCK_OBJECT_NAME  -- 锁定对象名称
FROM     GV$SESSION S 
          LEFT JOIN GV$SQL B ON (B.INST_ID=S.INST_ID AND B.SQL_ID=S.SQL_ID)
WHERE    S.TYPE='USER'
and S.sid<>(select sys_context('userenv','sid') from dual)   -- 不含本次查询连接会话
AND (S.BLOCKING_SESSION IS NOT NULL
OR       (S.INST_ID||'#'||S.SID) IN(SELECT DISTINCT BLOCKING_INSTANCE||'#'||BLOCKING_SESSION FROM GV$SESSION)
OR       S.status='ACTIVE'
OR       (S.status='INACTIVE' AND S.TADDR IS NOT NULL)
)
 START WITH  (s.BLOCKING_INSTANCE||'#'||s.BLOCKING_SESSION) = '#'
 CONNECT BY PRIOR (S.INST_ID||'#'||S.SID) = (s.BLOCKING_INSTANCE||'#'||s.BLOCKING_SESSION)
order by TREE_SID;


3、锁表检查

-- 最右边是锁的源头

select * from (select a.sid, a.serial#,a.sql_id,a.event,a.status,a.username,a.machine,a.program,a.logon_time,connect_by_isleaf as isleaf,sys_connect_by_path(SID, '<-') tree,level as tree_level from v$session a start with a.blocking_session is not null connect by nocycle a.sid = prior a.blocking_session) where isleaf = 1 order by tree_level asc;

4、查慢sql

select *
 from (select sa.SQL_TEXT,
        sa.SQL_FULLTEXT,
        sa.EXECUTIONS "执行次数",
        round(sa.ELAPSED_TIME / 1000000, 2) "总执行时间",
        round(sa.ELAPSED_TIME / 1000000 / sa.EXECUTIONS, 2) "平均执行时间",
        sa.COMMAND_TYPE,
        sa.PARSING_USER_ID "用户ID",
        u.username "用户名",
        sa.HASH_VALUE
     from v$sqlarea sa
     left join all_users u
      on sa.PARSING_USER_ID = u.user_id
     where sa.EXECUTIONS > 0
     order by (sa.ELAPSED_TIME / sa.EXECUTIONS) desc) where rownum <= 50

# 四、Mysql数据库

# 4.1 查询锁阻塞

# mysql 8.0

SELECT
	-- b.trx_id blocking_trx_id,
	b.trx_mysql_thread_id 阻塞会话id,
	b.trx_query 阻塞sql,
	-- r.trx_id waiting_trx_id,
	r.trx_mysql_thread_id 被阻塞会话id,
	r.trx_query 被阻塞sql
FROM
	PERFORMANCE_SCHEMA .data_lock_waits w,
	information_schema.innodb_trx r,
	information_schema.innodb_trx b
WHERE
	b.trx_id = w.BLOCKING_ENGINE_TRANSACTION_ID
AND r.trx_id = w.REQUESTING_ENGINE_TRANSACTION_ID;

# mysql 5.7

SELECT
	-- b.trx_id blocking_trx_id,
	b.trx_mysql_thread_id 阻塞会话id,
	b.trx_query 阻塞sql,
	-- r.trx_id waiting_trx_id,
	r.trx_mysql_thread_id 被阻塞会话id,
	r.trx_query 被阻塞sql
FROM
	information_schema.innodb_lock_waits w,
	information_schema.innodb_trx b,
	information_schema.innodb_trx r
WHERE
	b.trx_id = w.blocking_trx_id
AND r.trx_id = w.requesting_trx_id

# 4.2 其他查询方式

 1、查看正在进行中的事务
SELECT * FROM information_schema.INNODB_TRX;
2、查看正在锁的事务
SELECT * FROM INFORMATION_SCHEMA.INNODB_LOCKS;
3、查看等待锁的事务
SELECT * FROM INFORMATION_SCHEMA.INNODB_LOCK_WAITS;
4、查询是否锁表
SHOW OPEN TABLES where In_use > 0;
在发生死锁时,这几种方式都可以查询到和当前死锁相关的信息。

5、查看最近死锁的日志
show engine innodb status
解除死锁
如果需要解除死锁,有一种最简单粗暴的方式,那就是找到进程id之后,直接干掉。
查看当前正在进行中的进程

show full processlist;
// 也可以使用
SELECT * FROM information_schema.INNODB_TRX;
这两个命令找出来的进程id 是同一个。

杀掉进程对应的进程 id
kill id
验证(kill后再看是否还有锁)
SHOW OPEN TABLES where In_use > 0;

# 五、人大金仓数据库

正在执行的sql

select state,query from pg_stat_activity where query not in ('COMMIT','ROLLBACK');

查慢sql记录

select * from pg_stat_activity where state<>'idle' and now()-query_start > interval '5 s' order by query_start ; 
select query,calls,round(mean_exec_time,2)as mean_exec_time from sys_stat_statements order by mean_exec_time desc; 

死锁

select * from sys_locks where granted = 'f';
编撰人:yangtao、wangyxyf、het、zhangshuang、admin