SQL Server优化入门系列(一)——快速定位阻塞SQL
引言
我们在运维数据库的时候,经常会面对这样的问题:
数据库现在运行得怎么样
有哪些会话在执行,当前状态是什么,在执行什么SQL
哪些会话被阻塞,阻塞原因是什么
会话是从哪些IP连接过来的,使用了什么账号
通过这篇文章介绍的方法,我们可以快速找到SQL Server中正在运行的SQL,以及被阻塞的会话。
连接和请求
我们可以通过SQL Server提供的一系列DMV来解答上面这些问题。
要用好这些DMV,需要我们对SQL Server的基本概念和运行机制有一些基本的了解。
客户端执行一个SQL,大致可分为几个步骤:
1、建立连接
2、将请求(SQL)发送到服务端
服务端收到客户端发送的请求后,需要:
1、解析和优化SQL
2、请任务(Task)分配到Worker执行
3、将结果返回给客户端
下面的表格介绍了一个请求执行过程中,涉及到的核心的概念和相关的DMV:
概念 | 描述 | 相关DMV |
连接 (Connection) | 客户端到服务端的物理通道。 一般是TCP连接。 | sys.dm_exec_connections
|
会话 (Session) | 用户请求都会通过一个会话发送。 会话会绑定到一个连接上。 | sys.dm_exec_sessions
|
请求 (Request) | 一个请求可以简单理解为发送给服务器的任务(一个SQL语句、一个Batch)。 SQL Server接收到用户请求后,会生成对应的Task,分配给worker执行。 | sys.dm_exec_requests 当前系统中运行的requests。 请求执行完成后,从该dmv中就查不到相关信息了。
sys.dm_exec_sessions获取阻塞当前会话的session。如果引起阻塞的SQL还在执行,可以关联request、task表获取SQL语句。如果引起引起阻塞的SQL已经执行完成,则只能关联到connection表,获取会话最后执行的SQL。 |
任务 (task) | 用户发起的请求,服务端以task的方式运行。task会分配到某个worker执行,在task执行完成之前,worker不会执行其他task。 并行查询中,一个父task会产生多个子task。 task状态:
| sys.dm_os_tasks,
sys.dm_os_waiting_tasks
|
Worker | worker线程(thread)或fiber,用于执行task | sys.dm_os_workers
|
Scheduler | woker会分配到某个scheduler执行。scheduler相当于逻辑CPU。 | sys.dm_os_schedulers
|
会话相关查询
查询所有会话连接和执行的SQL
通过如下SQL可以查询SQL Server实例当前正在运行的SQL
select a.client_net_address, a.client_tcp_port, a.local_net_address, a.local_tcp_port, b.login_time, b.login_name, b.host_name, b.program_name, b.status, b.total_scheduled_time, b.total_elapsed_time, b.reads, b.writes, b.logical_reads, substring(d.text, c.statement_start_offset/2 + 1, ((case when c.statement_end_offset = -1 then len(d.text) * 2 else c.statement_end_offset end) - c.statement_start_offset)/2 + 1) as current_sql, d.text as full_sql from sys.dm_exec_connections a, sys.dm_exec_sessions b, sys.dm_exec_requests c cross apply sys.dm_exec_sql_text(c.sql_handle) d where a.session_id = b.session_id and b.session_id = c.session_id and b.session_id > 50;
查询返回的主要信息:
client_net_address: 客户端IP
status
login_name: 登录名
current_sql: 当前执行的SQL
full_sql: 完整的sql(存储过程或batch的完整sql)
查询阻塞的会话信息
通过如下SQL可以查询当前被阻塞的会话、SQL
SELECT Blocking.session_id as BlockingSessionId, Sess.login_name AS BlockingUser, BlockingSQL.text AS BlockingSQL, Waits.wait_type WhyBlocked, Waits.wait_duration_ms, Blocked.session_id AS BlockedSessionId, USER_NAME(Blocked.user_id) AS BlockedUser, BlockedSQL.text AS BlockedSQL, DB_NAME(Blocked.database_id) AS DatabaseName FROM sys.dm_exec_connections AS Blocking INNER JOIN sys.dm_exec_requests AS Blocked ON Blocking.session_id = Blocked.blocking_session_id INNER JOIN sys.dm_os_waiting_tasks AS Waits ON Blocked.session_id = Waits.session_id RIGHT OUTER JOIN sys.dm_exec_sessions Sess ON Blocking.session_id = sess.session_id CROSS APPLY sys.dm_exec_sql_text(Blocking.most_recent_sql_handle) AS BlockingSQL CROSS APPLY sys.dm_exec_sql_text(Blocked.sql_handle) AS BlockedSQL ORDER BY BlockingSessionId, BlockedSessionId
返回的信息:
BlockingSessionId
BlockingSQL:blocking sql并不一定是真正引起阻塞的SQL,而是阻塞session执行的最后一个SQL。
WhyBlocked:阻塞原因
wait_duration_ms: 阻塞时间(毫秒)
BlockedSessionId:被阻塞的session id
Blocked SQL:被阻塞的session执行的SQL