行锁和表锁由存储引擎、SQL类型及索引命中情况自动决定:InnoDB默认行锁但无索引时退化为表锁,MyISAM全程表锁;可通过EXPLAIN、SHOW ENGINE INNODB STATUS等验证实际锁行为。
MySQL 本身不直接提供“手动切换行锁/表锁”的开关。锁类型由存储引擎、SQL 类型、是否命中索引共同触发:InnoDB 默认走行锁,但 UPDATE 或 DELETE 没走索引时会退化为表锁;MyISAM 则全程只用表锁,连 SELECT 都会自动加读锁。
EXPLAIN SELECT ... FOR UPDATE 中 key 字段非 NULL 且 rows 较小,大概率是行锁;若 type 是 ALL 或 key 为 NULL,极可能升级为表锁
WHERE id = ?,如果 id 列没建索引,照样全表扫描 → 表锁SELECT ... FOR UPDATE,强行写会报错:ERROR 1036 (HY000): Table 'xxx' is read only
很多线上慢查询或阻塞,其实源于本想锁几行,结果锁了整张表——尤其在迁移 MyISAM 表到 InnoDB 后忽略索引设计时。
UPDATE/DELETE 语句中 WHERE 条件未命中任何索引(包括隐式类型转换,如 WHERE phone = 13800138000 而 phone 是 VARCHAR)TEXT、BLOB)做 ORDER BY 或 GROUP BY,触发 filesort + 临时表,InnoDB 可能放弃行锁优化LOCK TABLES t1 WRITE —— 这是强制表锁,哪怕你用的是 InnoDB,也会绕过 MVCC 直接上表级排他锁光靠理论容易误判。InnoDB 提供了几个关键视图,比“应该加什么锁”更可信的是“实际加了什么锁”。
SHOW ENGINE INNODB STATUS\G,重点关注 TRANSACTIONS 和 LOCK WAIT 部分,能看到谁在等哪一行、被谁堵住SHOW ENGINE INNODB STATUS\G 输出末尾的 LATEST DETECTED DEADLOCK 区域,包含完整 SQL 和锁模式(X 表示排他锁,S 表示共享锁)SHOW GLOBAL STATUS LIKE 'table_locks%';,若 table_locks_waited 持续增长,说明存在 MyISAM 表或 InnoDB 被迫升表锁的高频场景行锁提升并发能力,但代价是锁管理更复杂。InnoDB 的行锁基于索引记录,而两个事务若以不同顺序访问相同几行,就极易触发死锁。
T1 先更新 id=1 再更新 id=2;T2 反过来先更新 id=2 再更新 id=1 → 双方互相等待ORDER BY id ASC),让加锁顺序一致SELECT ... FOR UPDATE 做“乐观重试”:它本质是悲观锁,一旦加锁失败(超时或死锁回滚),必须重试整个事务,而不是仅重试某条语句UPDATE、那个忘记加 COMMIT 的事务、或是开发时以为“只是读一下”的 SELECT 却在 RR 隔离级别下触发了间隙锁。锁机制本身很稳,出问题的永远是人对它的假设。