select …… for update 在事务开启后执行完成时立即加锁,锁持续至事务提交或回滚;若未显式开启事务(autocommit=1),则语句执行完即提交并释放锁,实际无锁效果。

SELECT ... FOR UPDATE 什么时候真正加锁?
它不执行完就加锁,而是在事务提交或回滚时才释放——但加锁动作发生在语句执行完成的那一刻,前提是事务已开启。如果没开事务(autocommit = 1),SELECT ... FOR UPDATE 执行完立刻提交,锁也立刻消失,等于没锁。
必须显式执行:START TRANSACTION 或 BEGIN,再执行 SELECT ... FOR UPDATE,否则锁无效。
-
SELECT @@autocommit返回1?先SET autocommit = 0 - 不能在视图、临时表、MyISAM 表上用,会报错
ERROR 1288 - 隔离级别影响锁类型:默认
REPEATABLE READ下加 next-key lock(记录锁 + 间隙锁);READ COMMITTED下只加记录锁
WHERE 条件没走索引,为什么实际是锁全表?
InnoDB 的行锁是“锁索引”,不是锁数据行本身。没索引 → 查询要全表扫描 → InnoDB 必须保证可重复读语义,于是给所有聚簇索引记录(即所有主键行)都加上排他锁,并持有意向排他锁(IX),效果等同于锁表。
典型退化场景:
-
SELECT * FROM users WHERE name = 'Alice' FOR UPDATE,name字段无索引 -
WHERE status IN ('active', 'pending'),status有索引但区分度极低(如只有两个值)→ 优化器可能放弃索引,走全表扫描 -
WHERE JSON_EXTRACT(data, '$.type') = 'vip'→ 函数导致索引失效 - 字符集/排序规则不一致,比如字段是
utf8mb4_0900_as_cs,而参数是utf8mb4_general_ci→ 隐式转换使索引失效
怎么确认 FOR UPDATE 到底锁了哪些行?
别靠猜,查 INFORMATION_SCHEMA.INNODB_TRX 和锁视图。这是唯一可靠方式,尤其在线上排查阻塞时。
先看活跃事务:
SELECT trx_id, trx_state, trx_started, trx_query FROM INFORMATION_SCHEMA.INNODB_TRX;
再关联锁信息定位具体行:
SELECT r.trx_id waiting_trx_id,
r.trx_mysql_thread_id waiting_thread,
r.trx_query waiting_query,
b.trx_id blocking_trx_id,
b.trx_mysql_thread_id blocking_thread,
b.trx_query blocking_query
FROM INFORMATION_SCHEMA.INNODB_LOCK_WAITS w
INNER JOIN INFORMATION_SCHEMA.INNODB_TRX b ON b.trx_id = w.blocking_trx_id
INNER JOIN INFORMATION_SCHEMA.INNODB_TRX r ON r.trx_id = w.requesting_trx_id;
注意:EXPLAIN SELECT ... FOR UPDATE 只能看是否走索引、扫描多少行,不能反映锁范围;SHOW ENGINE INNODB STATUS\G 虽含锁详情,但只保留最近一次死锁,不可靠。
UPDATE 本身也加 X 锁,为什么还要写 FOR UPDATE?
因为 UPDATE 加的是“后置锁”:它先定位行(可能全表扫),再加锁、再修改。如果两个事务并发执行 UPDATE ... WHERE balance > 100,且都没索引,它们可能各自扫一遍表、各自加锁、各自更新——结果是幻读或覆盖写,根本没互斥。
SELECT ... FOR UPDATE 是“前置锁”:先精确锁定目标行(或范围),再做业务判断(比如余额是否足够),最后 UPDATE。这才是可控的悲观锁流程。
- 没
FOR UPDATE的UPDATE只保证自己改成功,不保证读取时的数据没被别人改过 -
SELECT ... FOR UPDATE+UPDATE组合才能构成原子性“读-判-改”逻辑 - 高并发下,
UPDATE自带的锁无法防止“先读后更新”的竞态,必须靠前置锁堵住窗口
最容易被忽略的点:锁的粒度完全取决于查询计划,而不是 SQL 表面上写了什么字段;同一句 FOR UPDATE,在不同数据分布、索引结构、甚至字符集设置下,可能锁一行,也可能锁全表。上线前务必用真实数据集跑 EXPLAIN + INNODB_TRX 验证。











