1. 锁等待:“为什么我的查询一直卡着不动?”(阻塞)
1.1 初步排查:数据库卡顿,不确定是否是锁阻塞
SELECT der.[session_id], der.[request_id], der.percent_complete, der.estimated_completion_time/1000/60, con.[client_net_address], con.local_net_address, der.[start_time] AS '开始时间', der.[status] AS '状态', der.[command] AS '命令', dest.[text] AS 'sql语句', DB_NAME(der.[database_id]) AS '数据库名', der.[blocking_session_id] AS '正在阻塞其他会话的会话ID', der.[wait_type] AS '等待资源类型', der.[wait_time] AS '等待时间', der.[wait_resource] AS '等待的资源', der.[reads] AS '物理读次数', der.[writes] AS '写次数', der.[logical_reads] AS '逻辑读次数', der.[row_count] AS '返回结果行数', der.[cpu_time], der.percent_complete FROM sys.[dm_exec_requests] AS der outer APPLY sys.[dm_exec_sql_text](der.[sql_handle]) AS dest left join sys.dm_exec_connections con on der.session_id=con.session_id left join sys.dm_exec_sessions es on es.session_id=con.session_id WHERE es.[is_user_process] = 1 --AND DB_NAME([database_id])='UserSystemBusiness' ORDER BY der.[reads] DESC
1.2 精准定位:已知有锁等待,需快速解决阻塞
1.2.1 统计阻塞汇总信息:按照阻塞者汇总
SELECT TOP 10 blocking_session_id AS blocking_session, -- 阻塞者会话ID COUNT(*) AS block_count, -- 阻塞次数 MAX(der.wait_time) AS max_wait_time, -- 被阻塞会话的最大等待时间(毫秒) DB_NAME(der.database_id) AS database_name, SUBSTRING(dest.text, (der.statement_start_offset/2)+1, ((CASE der.statement_end_offset WHEN -1 THEN DATALENGTH(dest.text) ELSE der.statement_end_offset END - der.statement_start_offset)/2)+1) AS blocked_query_text, -- 被阻塞的SQL -- 阻塞者执行的SQL(需要关联阻塞者的sql_handle) (SELECT SUBSTRING(dest_block.text, (der_block.statement_start_offset/2)+1, ((CASE der_block.statement_end_offset WHEN -1 THEN DATALENGTH(dest_block.text) ELSE der_block.statement_end_offset END - der_block.statement_start_offset)/2)+1) FROM sys.dm_exec_requests der_block CROSS APPLY sys.dm_exec_sql_text(der_block.sql_handle) dest_block WHERE der_block.session_id = der.blocking_session_id) AS blocking_query_text FROM sys.dm_exec_requests der CROSS APPLY sys.dm_exec_sql_text(der.sql_handle) dest WHERE blocking_session_id <> 0 -- 只筛选被阻塞的会话 GROUP BY blocking_session_id, der.database_id, dest.text, der.statement_start_offset, der.statement_end_offset ORDER BY block_count DESC; -- 按阻塞次数排序
1.2.1 阻塞与被阻塞明细
SELECT -- 阻塞者核心信息(解决列名错误,改用sys.dm_exec_connections的most_recent_sql_handle) der.blocking_session_id AS blocking_session_id, -- 阻塞者会话ID bs.login_name AS blocking_login, -- 阻塞者登录名 bc.client_net_address AS blocking_client_ip, -- 阻塞者客户端IP bc.local_net_address AS blocking_local_ip, -- 阻塞者本地IP(服务器端) -- 阻塞者的活跃请求信息(若存在) b.start_time AS blocking_request_start_time, -- 阻塞者请求开始时间 DATEDIFF(second, b.start_time, GETDATE()) AS blocking_request_duration_sec, -- 阻塞者请求已运行秒数 b.status AS blocking_request_status, -- 阻塞者请求状态(如running/suspended) -- 阻塞者执行的SQL(优先取活跃请求的SQL,无则取最近执行的SQL,同时清洗换行符) REPLACE(REPLACE( SUBSTRING(bt.text, (ISNULL(b.statement_start_offset, 0)/2)+1, ((CASE WHEN ISNULL(b.statement_end_offset, -1) = -1 THEN DATALENGTH(bt.text) ELSE b.statement_end_offset END - ISNULL(b.statement_start_offset, 0))/2)+1 ), CHAR(13), ' '), CHAR(10), ' ') AS blocking_query_text, -- 被阻塞者核心信息 der.session_id AS blocked_session_id, -- 被阻塞者会话ID des.login_name AS blocked_login, -- 被阻塞者登录名 con.client_net_address AS blocked_client_ip, -- 被阻塞者客户端IP der.start_time AS blocked_request_start_time, -- 被阻塞者请求开始时间 der.wait_time AS blocked_wait_time_ms, -- 被阻塞者已等待毫秒数 der.wait_type AS blocked_wait_type, -- 等待类型(如LCK_M_X排它锁、LCK_M_S共享锁) der.wait_resource AS blocked_wait_resource, -- 等待的具体资源(如:OBJECT: 123575513:1 [BULK_OPERATION]) der.command AS blocked_command_type, -- 被阻塞者命令类型(如SELECT/UPDATE/DELETE) -- 被阻塞者执行的SQL(清洗换行符) REPLACE(REPLACE( SUBSTRING(dest.text, (der.statement_start_offset/2)+1, ((CASE WHEN der.statement_end_offset = -1 THEN DATALENGTH(dest.text) ELSE der.statement_end_offset END - der.statement_start_offset)/2)+1 ), CHAR(13), ' '), CHAR(10), ' ') AS blocked_query_text, -- 公共信息 DB_NAME(der.database_id) AS database_name -- 涉及的数据库名 FROM -- 主表:被阻塞的请求(仅筛选有阻塞者的请求) sys.dm_exec_requests der -- 关联被阻塞者的会话信息(排除系统进程) INNER JOIN sys.dm_exec_sessions des ON der.session_id = des.session_id AND des.is_user_process = 1 -- 只看用户进程,排除SQL Server系统进程 -- 关联被阻塞者的连接信息(获取客户端IP) LEFT JOIN sys.dm_exec_connections con ON der.session_id = con.session_id -- 关联阻塞者的会话信息(获取登录名等基础信息) LEFT JOIN sys.dm_exec_sessions bs ON der.blocking_session_id = bs.session_id -- 关联阻塞者的连接信息(关键:获取most_recent_sql_handle,解决原报错) LEFT JOIN sys.dm_exec_connections bc ON der.blocking_session_id = bc.session_id -- 关联阻塞者的活跃请求信息(若阻塞者当前有正在执行的请求) LEFT JOIN sys.dm_exec_requests b ON der.blocking_session_id = b.session_id -- 获取阻塞者的SQL文本:优先用活跃请求的sql_handle,无则用连接的most_recent_sql_handle OUTER APPLY sys.dm_exec_sql_text(ISNULL(b.sql_handle, bc.most_recent_sql_handle)) bt -- 获取被阻塞者的SQL文本(必关联,因der是活跃请求) CROSS APPLY sys.dm_exec_sql_text(der.sql_handle) dest -- 筛选条件:仅保留有阻塞者的请求 WHERE der.blocking_session_id <> 0 -- 排序:先按阻塞者ID分组(同一会话的阻塞关系集中展示),再按被阻塞者等待时间降序(优先看严重的) ORDER BY blocking_session_id ASC, blocked_wait_time_ms DESC;
2. TOP CPU:“为什么 CPU 突然飙升?”(当前高 CPU)
SELECT TOP 10 [cpu_time], [session_id], [request_id], [start_time] AS '开始时间', [status] AS '状态', [command] AS '命令', dest.[text] AS 'sql语句', DB_NAME([database_id]) AS '数据库名', [blocking_session_id] AS '正在阻塞其他会话的会话ID', [wait_type] AS '等待资源类型', [wait_time] AS '等待时间', [wait_resource] AS '等待的资源', [reads] AS '物理读次数', [writes] AS '写次数', [logical_reads] AS '逻辑读次数', [row_count] AS '返回结果行数' FROM sys.[dm_exec_requests] AS der CROSS APPLY sys.[dm_exec_sql_text](der.[sql_handle]) AS dest WHERE [session_id]>50 --AND DB_NAME(der.[database_id])='UserSystemBusiness' ORDER BY [cpu_time] DESC
3. TOP MEM:持续累积(直到缓存清理 / 重启)
SELECT TOP 10 DB_NAME(CAST(pa.value AS INT)) AS database_name, -- 数据库名称 SUBSTRING(qt.text, (qs.statement_start_offset/2)+1, ((CASE qs.statement_end_offset WHEN -1 THEN DATALENGTH(qt.text) ELSE qs.statement_end_offset END - qs.statement_start_offset)/2)+1) AS query_text, -- 内存和IO相关指标 qs.total_physical_reads AS total_physical_reads, qs.total_logical_reads AS total_logical_reads, qs.total_logical_writes AS total_logical_writes, -- 时间相关信息(适配SQL Server 2012) qs.last_execution_time AS last_execution_time, -- 最后一次执行结束时间 DATEADD(millisecond, -qs.last_elapsed_time/1000, qs.last_execution_time) AS last_start_time, qs.last_elapsed_time AS last_execution_duration_microseconds, -- 最后一次执行耗时(微秒) qs.total_elapsed_time AS total_elapsed_time, -- 总耗时(微秒) -- 其他执行信息 qs.execution_count AS execution_count, qs.total_worker_time AS total_worker_time FROM sys.dm_exec_query_stats qs CROSS APPLY sys.dm_exec_sql_text(qs.sql_handle) qt CROSS APPLY sys.dm_exec_plan_attributes(qs.plan_handle) pa WHERE pa.attribute = 'dbid' ORDER BY qs.total_logical_reads DESC
4. TOP 磁盘 I/O(物理读写频繁的查询)
5. TOP 执行次数(高频执行的查询)
SELECT TOP 10 DB_NAME(CAST(pa.value AS INT)) AS database_name, SUBSTRING(qt.text, (qs.statement_start_offset/2)+1, ((CASE qs.statement_end_offset WHEN -1 THEN DATALENGTH(qt.text) ELSE qs.statement_end_offset END - qs.statement_start_offset)/2)+1) AS query_text, qs.execution_count AS execution_count, -- 总执行次数 qs.total_worker_time AS total_cpu_time, -- 总CPU时间 qs.total_elapsed_time AS total_elapsed_time, -- 总耗时(微秒) qs.total_logical_reads AS total_logical_reads -- 总逻辑读 FROM sys.dm_exec_query_stats qs CROSS APPLY sys.dm_exec_sql_text(qs.sql_handle) qt CROSS APPLY sys.dm_exec_plan_attributes(qs.plan_handle) pa WHERE pa.attribute = 'dbid' ORDER BY execution_count DESC; -- 按执行次数排序
6. TOP 平均耗时(单次执行耗时高的查询)
6.1 正在执行且运行时间超过指定阈值的查询(明细查询)
SELECT session_id, DB_NAME(database_id) AS database_name, command, -- 命令类型(如SELECT、UPDATE) status, -- 状态(如running、suspended) start_time, -- 开始时间 DATEDIFF(second, start_time, GETDATE()) AS run_seconds, -- 已运行秒数 SUBSTRING(dest.text, (der.statement_start_offset/2)+1, ((CASE der.statement_end_offset WHEN -1 THEN DATALENGTH(dest.text) ELSE der.statement_end_offset END - der.statement_start_offset)/2)+1) AS query_text FROM sys.dm_exec_requests der CROSS APPLY sys.dm_exec_sql_text(der.sql_handle) dest WHERE DATEDIFF(second, start_time, GETDATE()) > 30 -- 运行超过30秒 AND session_id > 50 -- 排除系统会话 ORDER BY run_seconds DESC;
6.2 平均执行时间长的查询(汇总分析)
SELECT TOP 10 DB_NAME(CAST(pa.value AS INT)) AS database_name, SUBSTRING(qt.text, (qs.statement_start_offset/2)+1, ((CASE qs.statement_end_offset WHEN -1 THEN DATALENGTH(qt.text) ELSE qs.statement_end_offset END - qs.statement_start_offset)/2)+1) AS query_text, qs.execution_count AS execution_count, qs.total_elapsed_time/ 1000000 AS total_elapsed_time, -- 总耗时(秒) (qs.total_elapsed_time / qs.execution_count) / 1000000 AS avg_elapsed_seconds, -- 平均耗时(秒) qs.last_elapsed_time / 1000000 AS last_elapsed_seconds -- 最后一次执行耗时(秒) FROM sys.dm_exec_query_stats qs CROSS APPLY sys.dm_exec_sql_text(qs.sql_handle) qt CROSS APPLY sys.dm_exec_plan_attributes(qs.plan_handle) pa WHERE pa.attribute = 'dbid' AND qs.execution_count > 0 ORDER BY avg_elapsed_seconds DESC; -- 按平均耗时排序
6.3 历史慢查:输出的SQL中去掉换行等(汇总分析)
同样依赖 sys.dm_exec_query_stats 视图,与 6.1 共享同一数据源,反映的是历史累计的查询执行统计。
时间范围:覆盖查询的全部历史执行记录(非实时)。
SELECT TOP 10 -- 数据库名称 DB_NAME(CAST(pa.value AS INT)) AS database_name, -- 提取SQL片段并转换为单行(去除换行符和回车符) REPLACE( REPLACE( SUBSTRING(qt.text, (qs.statement_start_offset/2)+1, ((CASE qs.statement_end_offset WHEN -1 THEN DATALENGTH(qt.text) ELSE qs.statement_end_offset END - qs.statement_start_offset)/2)+1 ), CHAR(13), '' -- 去除回车符 ), CHAR(10), '' -- 去除换行符 ) AS query_text, -- 单行显示的SQL语句 -- 执行次数 qs.execution_count AS execution_count, -- 总耗时(转换为秒) CAST(qs.total_elapsed_time / 1000000.0 AS DECIMAL(18,2)) AS total_elapsed_seconds, -- 平均耗时(秒) CAST((qs.total_elapsed_time / qs.execution_count) / 1000000.0 AS DECIMAL(18,2)) AS avg_elapsed_seconds, -- 最长一次执行耗时(秒) CAST(qs.max_elapsed_time / 1000000.0 AS DECIMAL(18,2)) AS max_elapsed_seconds, -- 最近一次执行时间 qs.last_execution_time AS last_execution_time FROM sys.dm_exec_query_stats qs CROSS APPLY sys.dm_exec_sql_text(qs.sql_handle) qt CROSS APPLY sys.dm_exec_plan_attributes(qs.plan_handle) pa WHERE pa.attribute = 'dbid' -- 关联数据库ID AND qs.execution_count > 0 -- 排除未执行的查询 ORDER BY total_elapsed_seconds DESC; -- 按总耗时排序(累计影响最大)
7. TOP 阻塞源头(频繁阻塞其他会话的会话)
SELECT TOP 10 blocking_session_id AS blocking_session, -- 阻塞者会话ID COUNT(*) AS block_count, -- 阻塞次数 MAX(der.wait_time) AS max_wait_time, -- 被阻塞会话的最大等待时间(毫秒) DB_NAME(der.database_id) AS database_name, SUBSTRING(dest.text, (der.statement_start_offset/2)+1, ((CASE der.statement_end_offset WHEN -1 THEN DATALENGTH(dest.text) ELSE der.statement_end_offset END - der.statement_start_offset)/2)+1) AS blocked_query_text, -- 被阻塞的SQL -- 阻塞者执行的SQL(需要关联阻塞者的sql_handle) (SELECT SUBSTRING(dest_block.text, (der_block.statement_start_offset/2)+1, ((CASE der_block.statement_end_offset WHEN -1 THEN DATALENGTH(dest_block.text) ELSE der_block.statement_end_offset END - der_block.statement_start_offset)/2)+1) FROM sys.dm_exec_requests der_block CROSS APPLY sys.dm_exec_sql_text(der_block.sql_handle) dest_block WHERE der_block.session_id = der.blocking_session_id) AS blocking_query_text FROM sys.dm_exec_requests der CROSS APPLY sys.dm_exec_sql_text(der.sql_handle) dest WHERE blocking_session_id <> 0 -- 只筛选被阻塞的会话 GROUP BY blocking_session_id, der.database_id, dest.text, der.statement_start_offset, der.statement_end_offset ORDER BY block_count DESC; -- 按阻塞次数排序
8. 缺失索引(性能提升潜力大的缺失索引)
SELECT TOP 10 CONVERT(decimal(18,2), user_seeks * avg_total_user_cost * (avg_user_impact / 100.0)) AS improvement_measure, -- 性能提升预估 DB_NAME(mid.database_id) AS database_name, OBJECT_NAME(mid.object_id, mid.database_id) AS table_name, -- 表名 mid.equality_columns, -- 等值条件列(如WHERE a = 1) mid.inequality_columns, -- 不等值条件列(如WHERE a > 1) mid.included_columns, -- 包含列(INCLUDE子句) migs.user_seeks, -- 用户查询寻求次数 migs.user_scans, -- 用户查询扫描次数 migs.avg_user_impact -- 平均性能提升百分比(%) FROM sys.dm_db_missing_index_details mid INNER JOIN sys.dm_db_missing_index_groups mig ON mid.index_handle = mig.index_handle INNER JOIN sys.dm_db_missing_index_group_stats migs ON mig.index_group_handle = migs.group_handle WHERE mid.database_id = DB_ID() -- 当前数据库 ORDER BY improvement_measure DESC; -- 按预估提升排序
                    
                
 posted on 
                
            
        
浙公网安备 33010602011771号