核心结论:DELETE是DML操作,全程走行级锁+事务机制;TRUNCATE是DDL操作,只瞬时持表级锁且隐式提交。这是两者锁等待表现差异的根本原因。
DELETE为什么会长时间持有锁
DELETE不是简单的“删完就放锁”,而是边删边锁、边写日志、边维护MVCC版本链。整个过程远比想象中复杂:
DELETE FROM t会启动一个事务,对每一行加X锁(或间隙锁),直到事务结束才释放。这意味着,如果没有合适的索引,它可能需要对全表进行逐行锁定。- 当没有WHERE条件时,InnoDB仍要全表扫描聚簇索引,逐行加锁、打删除标记、写
undo log。这个过程会持续消耗锁资源。 - 如果表没有合适的索引,锁可能会升级为表锁。在高并发场景下,这很容易导致
LOCK_WAIT或死锁。 - 在
SHOW ENGINE INNODB STATUS里,经常能看到大量RECORD LOCKS和长时间Trx has been waiting的记录,这正是DELETE锁等待的直接体现。
TRUNCATE锁等待几乎为零的底层原因
TRUNCATE根本不参与事务锁队列,它压根不“等”锁,只做两件事:抢锁,要么失败要么秒过:
- 它申请的是
SCH_M(schema modification)锁,粒度是表级,但持有时间以毫秒计,几乎可以忽略不计。 - 一旦发现表被
LOCK TABLES t READ或外键引用,立刻报错退出:ERROR 1099或ERROR 1701,绝不排队等待。这是它“快”的核心原因。 - 执行即隐式提交,不进事务上下文,所以
SELECT FOR UPDATE或其他DML不会因它进入锁等待队列。 - 在主从复制中,从库收到的是轻量DDL事件,不会像
DELETE那样堆积上万条row event导致应用线程卡住。
线上误用TRUNCATE反而引发更隐蔽的问题
虽然TRUNCATE看起来“不卡锁”很安全,但实际容易踩进事务截断和数据一致性陷阱。以下几个问题值得警惕:
- 在长事务里执行
TRUNCATE TABLE t,MySQL会自动COMMIT当前事务——你后面跟的ROLLBACK完全无效,下游可能读到中间态。这会导致数据一致性问题。 - TRUNCATE重置
AUTO_INCREMENT,如果业务依赖ID单调递增(比如对外暴露的订单号),首次插入可能撞上旧ID,引发业务异常。 - 它不触发
ON DELETE触发器,也不检查外键约束(除非被引用),这些“省略”在某些场景下就是逻辑漏洞。
真正决定是否用TRUNCATE的,从来不是“快不快”,而是“能不能接受不可回滚、自增重置、触发器失效、外键拒绝”这四点。锁只是表象,语义差异才是根本。
