
行锁为什么会“变成”表锁?根本就不是升级,而是退化
InnoDB 从不主动把行锁“升级”为表锁——它只是在无法精准加锁时,被迫对成百上千行逐个加行锁,结果等效于锁了整张表。真正触发这种退化的,是 WHERE 条件没走索引。
- 常见错误现象:
UPDATE t SET status = 1 WHERE name LIKE '%admin%'执行极慢、阻塞其他事务,EXPLAIN显示type: ALL - 本质原因:没索引 → 全表扫描 → 每扫描一行就加一个行锁 → 锁住 5000+ 行后,InnoDB 可能直接拒绝执行或报
Lock wait timeout exceeded - 别信“阈值 5000 行就升级”的说法——这不是策略切换,是优化器放弃索引后的自然结果
- 联合索引失效也一样:用
WHERE city = 'SZ'却建了(name, city)索引,照样全扫
哪些操作会直接拿到表锁?不是隐式退化,而是明示加锁
只有显式调用或引擎强制行为才会真正获得表级锁,和行锁机制完全无关。
-
LOCK TABLES t WRITE:会话级独占锁,之后所有 DML 都被拦住;再执行SELECT ... FOR UPDATE直接报错ERROR 1100 (HY000): Table 't' was not locked with LOCK TABLES -
ALTER TABLE、TRUNCATE TABLE:DDL 本身不走 InnoDB 行锁逻辑,由 Server 层的元数据锁(MDL)控制,效果等同表锁 - MyISAM 表任何写操作:该引擎压根不支持行锁,
UPDATE或INSERT自动上表写锁 -
AUTO_INCREMENT插入未命中主键路径时:InnoDB 会对整个自增索引段加锁,高并发下表现接近表锁
怎么确认当前锁是行锁还是表锁?别靠猜,查视图
MySQL 8.0+ 必须看 performance_schema.data_locks,这是唯一权威来源。
- 查锁类型:
SELECT LOCK_TYPE, LOCK_MODE, INDEX_NAME FROM performance_schema.data_locks WHERE OBJECT_SCHEMA = 'db' AND OBJECT_NAME = 't'; -
LOCK_TYPE = 'TABLE'是真表锁;LOCK_TYPE = 'RECORD'是行锁(含间隙锁) - 配合
performance_schema.data_lock_waits查谁在等谁:SELECT * FROM performance_schema.data_lock_waits; - 别依赖
SHOW ENGINE INNODB STATUS\G的 “HOLDS THE LOCK(S)” —— 它只显示最近事务,漏掉静默持有者
如何避免“像表锁一样卡住”?核心就一条:让每条语句都走索引
不是加更多锁,而是让锁尽可能少、尽可能窄。
- 模糊查询必须前置通配符?改用
FULLTEXT或引入 Elasticsearch,别硬扛LIKE '%x' - 批量删/改超万行?分批加
LIMIT:DELETE FROM log WHERE ts ,循环执行 - 字段上有索引但没生效?检查隐式转换:
WHERE phone = 138(phone是VARCHAR)会丢索引,必须写成WHERE phone = '138' - 事务里混用 DML 和 DDL?绝对禁止。DDL 会触发隐式提交,导致前面的行锁提前释放,后续逻辑出错
最容易被忽略的一点:autocommit = 1 下写 SELECT ... FOR UPDATE 等于没锁——语句一结束锁就没了,必须先 START TRANSACTION 或设 SET autocommit = 0。










