DELETE ... LIMIT的加锁范围由执行计划是否走索引决定:无索引时全表扫描并锁定所有扫描行,等效锁表;有索引时按Next-Key Lock规则锁定匹配行及间隙,LIMIT仅限制删除行数,不减少加锁范围。

DELETE ... LIMIT 的加锁范围不是由 LIMIT 决定的,而是由执行计划是否能走索引、是否触发全表扫描决定的。 没有索引时,InnoDB 会升级为表级锁;有唯一/普通索引且能精准定位时,才真正只锁匹配的行(含间隙锁或临键锁)。很多人误以为 LIMIT=1 就只锁 1 行,这是线上死锁和长事务等待的常见根源。
DELETE ... LIMIT 没走索引时锁整张表
当 WHERE 条件列没有索引,或索引失效(如对字段做函数操作、隐式类型转换),InnoDB 无法快速定位目标行,只能全表扫描。此时即使加了 LIMIT 1,也会对所有扫描过的行逐个加 X 锁,并在扫描结束后不释放——相当于给整张表上了行级排他锁,其他事务读写该表任何行都会被阻塞。
- 典型场景:
DELETE FROM orders WHERE DATE(create_time) = '2026-09-28' LIMIT 1(create_time有索引但被函数包裹) - 验证方式:执行后查
performance_schema.data_locks,会看到大量RECORD类型的X锁,甚至出现TABLE级锁(若引擎回退到表锁逻辑) - 后果:阻塞后续所有 DML,可能引发主从延迟、连接堆积
DELETE ... LIMIT 走索引时也未必只锁一行
即使 WHERE 条件命中索引,InnoDB 在 REPEATABLE READ 隔离级别下默认使用 Next-Key Lock(临键锁 = 行锁 + 间隙锁),目的是防止幻读。所以实际锁定的是「目标值所在区间」,而不仅是命中的那条记录。
面向 CRM 系统的 MySQL 8 数据库模式设计。当用户需要为客户关系管理(CRM)系统设计、评审、优化或生成数据库模式时,应使用此项技能。
- 例如:
DELETE FROM users WHERE id = 100 LIMIT 1,若id是主键,只锁id=100这一行(REC_NOT_GAP) - 但:
DELETE FROM users WHERE name = 'alice' LIMIT 1,若name是普通索引,InnoDB 会锁住'alice'对应的索引记录 + 该记录前后的间隙(比如锁住('bob', 'carol')这个范围),防止其他事务插入新alice - 关键点:
LIMIT不影响加锁逻辑,只控制最终删除几行;优化器仍需预判所有可能匹配的行并加锁
如何确认实际加了哪些锁
别猜,直接查 performance_schema.data_locks。这是唯一可信的运行时锁视图,比 SHOW ENGINE INNODB STATUS 更细粒度、更实时。
- 必须开启:确保
performance_schema已启用,且data_locks表的消费者已打开(UPDATE performance_schema.setup_consumers SET ENABLED = 'YES' WHERE NAME = 'events_transactions_current';) - 关键字段:
LOCK_TYPE(TABLE/RECORD)、LOCK_MODE(X,S,X,GAP,X,REC_NOT_GAP)、LOCK_DATA(具体锁住的值或间隙) - 注意:同一事务中多次
DELETE ... LIMIT可能累积锁,不会因为 LIMIT 小就自动释放前面的锁
最常被忽略的一点是:LIMIT 是语句执行层面的限制,而加锁是存储引擎在执行前根据隔离级别和索引结构做的预判行为。想控制锁范围,优先保证 WHERE 条件走索引,再考虑用主键精确删除;把 LIMIT 当“安全阀”用,反而容易掩盖锁膨胀问题。

















