select ... for update 未锁住行,主要因未在事务中执行、表引擎非innodb、where条件未命中索引;update默认加x锁但依赖索引精确定位,否则可能锁表;验证锁可用阻塞式select或查询innodb_trx等系统表。

SELECT ... FOR UPDATE 为什么没锁住行
执行 SELECT ... FOR UPDATE 没生效,大概率是因为没在事务里运行。MySQL 的行锁只在事务中起作用,单独执行这条语句(自动提交模式下)会立即提交,锁也随即释放。
- 确保已执行
SET autocommit = 0或显式用BEGIN/START TRANSACTION开启事务 - 检查表引擎是否为
InnoDB——MyISAM只支持表级锁,SHOW CREATE TABLE tbl_name可确认 - WHERE 条件必须命中索引,否则会升级为表锁;比如对无索引字段加
FOR UPDATE,实际可能锁全表
UPDATE 语句自动加锁的条件是什么
UPDATE 在事务中执行时,默认加当前读(current read)下的行级排他锁(X锁),但前提是能通过索引精确定位到行。
- 若 WHERE 使用主键或唯一索引(如
WHERE id = 100),只锁匹配的单行 - 若 WHERE 使用非唯一索引(如
WHERE status = 'pending'),会锁所有扫描到的索引记录,还可能包含间隙锁(gap lock),防止幻读 - 若 WHERE 不走索引(全表扫描),InnoDB 会为每条聚簇索引记录加 X 锁,等效于锁表,性能极差
如何验证某行是否已被锁定
直接尝试对同一行执行带锁操作,是最简单可靠的验证方式。比如另一个事务执行:
SELECT * FROM orders WHERE id = 123 FOR UPDATE;
如果卡住不返回,说明该行正被其他事务持有 X 锁。也可查系统表辅助判断:
-
SELECT * FROM information_schema.INNODB_TRX查看当前活跃事务 -
SELECT * FROM information_schema.INNODB_LOCK_WAITS查等待关系(有数据即存在阻塞) -
SELECT * FROM information_schema.INNODB_LOCKS(MySQL 5.7+ 已弃用,8.0 中移除,勿依赖)
事务中混用 LOCK IN SHARE MODE 和 FOR UPDATE 的风险
共享锁(S锁)和排他锁(X锁)互斥,但 S 锁之间不互斥。容易踩的坑是:一个事务用 SELECT ... LOCK IN SHARE MODE 读了某行,另一个事务用 FOR UPDATE 尝试修改,会被阻塞;但如果第二个事务也用 LOCK IN SHARE MODE,就能并发读,看似没问题,但后续若都试图升级为写,就会出现死锁。
- 避免在同一个事务里先
LOCK IN SHARE MODE再UPDATE同一行 —— 这会触发锁升级,可能引发死锁 - 高并发更新场景,优先用
UPDATE ... WHERE直接操作,减少“读-改-写”分步流程 - 注意隔离级别:
READ COMMITTED下间隙锁禁用,REPEATABLE READ下默认启用,影响锁范围
行锁不是万能的,它高度依赖索引结构和事务边界。很多“锁不住”或“锁太多”的问题,根源不在锁机制本身,而在 SQL 是否走索引、事务是否及时提交、隔离级别是否合理。










