优先查执行计划和锁状态:若wait_type为LCK_M_U等则为锁等待,blocking_session_id非零可确认;若cpu_time远小于total_elapsed_time则在等资源;若接近则真慢查询,需查执行计划。

存储过程执行超时,优先查执行计划和锁状态,而不是调超时参数——因为90%的“超时”根本不是时间不够,而是某条语句卡在锁、扫描或参数嗅探上。
怎么一眼看出是锁卡住还是真慢查询
执行 SELECT * FROM sys.dm_exec_requests WHERE session_id > 50 AND total_elapsed_time > 30000,重点看三列:
-
wait_type是LCK_M_U、LCK_M_S或KEY_LOCK→ 锁等待,立刻查blocking_session_id和last_wait_type -
status是running但cpu_time远小于total_elapsed_time→ 大概率在等资源(锁/I/O) -
status是running且cpu_time接近total_elapsed_time→ 真正在 CPU 上跑,用sys.dm_exec_query_plan拆执行计划,找“表扫描”或“警告图标”
为什么 SET LOCK_TIMEOUT 在超时场景下常被误用
SET LOCK_TIMEOUT 5000 只对单条语句的锁等待生效,但它不解决以下问题:
- 对死锁(错误号 1205)完全无效——SQL Server 在毫秒级检测并终止,根本没机会触发 LOCK_TIMEOUT
- 对 CPU 密集型操作(如大排序、递归 CTE)或 I/O 瓶颈无影响
- 设在存储过程开头,只影响后续新发的语句,不影响当前正在执行的那条卡住的 UPDATE/SELECT
- 若过程里有多个 DML,必须每条前都显式重设,否则失效
索引提示(INDEX HINT)写在哪才真正起作用
索引提示不是“加个注释就生效”,它必须嵌入具体语句中,且位置敏感:
- SQL Server:
FROM orders o WITH (INDEX(ix_orders_user_id))✅ 紧贴别名后;FROM orders WITH (INDEX(...)) WHERE ...❌ 报错 - MySQL:
SELECT * FROM orders FORCE INDEX (idx_orders_status) WHERE status = 'shipped'✅ 在FROM表名后、WHERE前;WHERE ... FORCE INDEX❌ 直接语法错误 - 视图内或存储过程定义头写提示 → 完全无效,提示无法穿透视图展开逻辑
- 多表 JOIN 必须每个表单独加,漏一个就可能让优化器选错驱动表
参数嗅探导致的“假性超时”怎么快速验证
同一存储过程,不同参数值执行时间差几十倍?大概率是参数嗅探固化了低效计划:
- 执行
DBCC FREEPROCCACHE清空缓存,再用“慢参数”重新执行一次,看是否变快——如果快了,基本锁定是参数嗅探 - 临时改写过程,在关键 SELECT 前加
OPTION (RECOMPILE),仅对该语句禁用计划复用 - 更稳妥的是用局部变量“断开”参数传递:
DECLARE @local_id INT = @input_id; SELECT ... WHERE id = @local_id,让优化器无法基于 @input_id 预估行数 - 避免在 WHERE 中对字段做函数操作:
WHERE UPPER(name) = 'ABC'会强制全表扫描,哪怕 name 有索引
真正卡住的过程,往往不是“太慢”,而是“不动”——锁住了、等元数据了、被另一个未提交事务挂起了。盯着 sys.dm_exec_requests 的 wait_type 和 blocking_session_id 看两眼,比调十次 CommandTimeout 有用得多。

















