核心是减少锁持有时间与竞争,而非调高超时阈值;需检查索引命中、避免隐式转换、防范行锁升级为表锁,并定位MDL或事务阻塞源。

删除操作因锁超时失败,核心不是调高超时阈值,而是减少锁持有时间 + 避免锁竞争。 盲目改 innodb_lock_wait_timeout 或 DDL_LOCK_TIMEOUT 只是掩盖问题,下次还会爆。真正要动的是事务结构、索引设计和执行方式。
MySQL DELETE 超时:先确认是不是行锁升级成了表锁
DELETE 不一定只锁匹配的行。如果 WHERE 条件没走索引,InnoDB 会扫描全表并为每行加锁,极易触发锁等待甚至死锁。
- 用
EXPLAIN检查你的DELETE FROM t WHERE ...是否命中索引;没命中就别删,先建索引 - 避免在 WHERE 中对字段做函数操作,比如
WHERE DATE(created_at) = '2024-01-01'——这会让索引失效 - 注意隐式类型转换:
WHERE user_id = '123'(user_id是 INT)也会跳过索引,导致全表扫描加锁 - 如果必须删大量数据,且无法加索引,优先考虑
SELECT ... INTO OUTFILE导出保留数据,再TRUNCATE或重建表
SQL Server 删除索引失败:锁超时本质是有人正用着这张表
报错 “已超过了锁清除超时” 或 “执行超时已过期”,不是索引本身的问题,而是当前有活跃会话正在读/写该表,SQL Server 删除索引需获取 SCH-M(架构修改)锁,它和几乎所有其他锁互斥。
- 别急着
KILL,先跑SELECT blocking_session_id, session_id, wait_type FROM sys.dm_exec_requests WHERE blocking_session_id 0看谁在挡路 - 重点查
wait_type = 'LCK_M_SCH_M'的请求,它直接指向索引 DDL 阻塞源 - 如果是应用连接长期空闲但未关闭(常见于连接池 misconfig),清理比杀更稳妥;可设
ALTER DATABASE ... SET AUTO_CLOSE OFF防止自动关库引发锁残留 - 生产环境删索引,务必选低峰期,并提前用
SET LOCK_TIMEOUT 5000控制自身等待上限,避免无限挂起
隔离级别调低真能缓解 DELETE 锁超时?看场景
把 MySQL 从 REPEATABLE READ 改成 READ COMMITTED,对某些 DELETE 场景确实有效,但不是万能解药,且有副作用。
- 仅当 DELETE 前有
SELECT ... FOR UPDATE或其它显式加锁读时,降级才有意义——READ COMMITTED下间隙锁(gap lock)范围更小,冲突概率下降 - 但如果 DELETE 本身带 WHERE 且无索引,降级完全没用:行锁照加,只是不加间隙锁而已
- PostgreSQL 的
READ COMMITTED是默认且唯一推荐级别,无需调整;但要注意它不解决长事务占锁问题 - SQL Server 的
READ_COMMITTED_SNAPSHOT(RCSI)才是关键:开启后,普通 SELECT 不再阻塞写,大幅降低 DML 间的锁等待,比调隔离级别本身更治本
为什么加了索引还锁超时?可能你锁的不是数据,是元数据
某些情况下,DELETE 操作卡住,SHOW ENGINE INNODB STATUS 显示等的是 metadata lock(MDL),而不是行锁或表锁。这说明有其它会话正在对该表做 DDL(如 ALTER TABLE、CREATE INDEX),而 MDL 是串行化的。
- MySQL 5.7+ 中,
SELECT * FROM performance_schema.metadata_locks可查谁持有了 MDL;注意该表默认关闭,需先UPDATE performance_schema.setup_instruments SET ENABLED = 'YES' WHERE NAME = 'wait/lock/metadata/sql/mdl' - DDL 操作本身也受
lock_wait_timeout限制,但它的默认值(通常 31536000 秒)远高于普通语句,容易让人误以为“永远不超时” - 线上禁止在业务高峰期执行任何 DDL;如必须做,用
pt-online-schema-change或gh-ost这类无锁工具 - 触发器里执行
INSERT INTO log_table后又去UPDATE config,这种跨表操作若没统一锁序,哪怕每张表都有索引,照样引发锁等待链
最常被忽略的一点:锁超时错误日志里那个 “wait time 0 milliseconds”,不是系统没等,而是数据库压根没启动等待——比如 YashanDB 默认 DDL_LOCK_TIMEOUT=0,或者 MySQL 的 lock_wait_timeout 被设成 0。这种配置下,连“争抢机会”都没有,直接失败。查清底层数据库的实际默认值,比盲目优化 SQL 更紧迫。










