select for update 未锁住行的根本原因是事务隔离级别低于可重复读或未命中索引;需设为repeatable read并确保where条件走索引(explain显示type为range/ref),避免函数导致索引失效。

SELECT FOR UPDATE 在 MySQL 中为什么没锁住行
根本原因通常是事务隔离级别太低,或者语句没走索引——SELECT FOR UPDATE 只在可重复读(RR)及以上且命中索引时才加行锁;读已提交(RC)下它可能升级为临键锁或退化成表锁,而全表扫描时直接锁整张表。
- 检查当前会话隔离级别:
SELECT @@transaction_isolation;,不是REPEATABLE-READ就先SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ; - 确认
WHERE条件字段有有效索引,用EXPLAIN看type是否为range/ref,避免ALL - 注意:如果
WHERE条件中用了函数(如WHERE YEAR(create_time) = 2024),索引失效,锁不住单行 - 在 RR 下,若查询条件是间隙(比如
id > 10 AND id 且无对应记录),会加间隙锁,但不会阻塞插入——这点常被误认为“没锁住”
UPDATE 语句执行完但事务没提交,其他事务还能读到旧值吗
取决于隔离级别和读方式:在 READ-COMMITTED 或更高隔离级别下,普通 SELECT 看不到未提交变更;但加了 SELECT ... LOCK IN SHARE MODE 或 SELECT FOR UPDATE 的读,会触发锁等待,而非读取旧快照。
-
READ-UNCOMMITTED下所有事务都能读到未提交的“脏数据”,但极少在生产使用 -
READ-COMMITTED每次SELECT都新建一致性读视图,所以读不到未提交的UPDATE - 显式加锁读(如
SELECT ... FOR UPDATE)会尝试获取锁,若被占用就阻塞,不会降级为读快照 - 注意:应用层 ORM(如 Django ORM)默认不开启显式事务,
save()后不commit,可能让锁一直挂着,拖慢并发
死锁日志里出现 “WAITING FOR THIS LOCK TO BE GRANTED” 却找不到持有者
大概率是那个持有锁的事务已经崩溃、连接断开,但 MySQL 还没及时清理其事务状态——这时候锁还在,但 INFORMATION_SCHEMA.INNODB_TRX 里查不到对应记录,因为事务元数据已丢失。
- 查活跃事务:
SELECT * FROM INFORMATION_SCHEMA.INNODB_TRX;,重点看TRX_STATE和TRX_MYSQL_THREAD_ID - 如果死锁日志里显示持有者线程 ID 不在
INNODB_TRX中,基本可判定是“僵尸事务”,需手动 kill 对应连接:KILL [thread_id]; - MySQL 8.0.14+ 支持
innodb_deadlock_detect=ON(默认),但无法避免这种因网络中断导致的悬空锁 - 预防方法:应用层设置合理的连接超时(
wait_timeout、interactive_timeout)和事务超时(innodb_lock_wait_timeout)
PostgreSQL 的 SELECT FOR UPDATE 和 MySQL 行为差异在哪
核心区别在锁粒度与默认行为:PostgreSQL 默认只锁命中的行,不加间隙锁;MySQL InnoDB 在 RR 下默认加临键锁(行锁 + 间隙锁),防幻读;而且 PostgreSQL 不支持 LOCK IN SHARE MODE 这种语法。
- PG 中
SELECT FOR UPDATE不会阻塞INSERT到间隙,除非明确用了FOR UPDATE OF table_name或加了SKIP LOCKED控制竞争 - MySQL 的
SELECT FOR UPDATE在唯一索引等值查询时只加行锁,但范围查询(如WHERE id > 10)会锁住间隙,PG 不会 - PG 没有“共享锁升级为排他锁”的概念,
SELECT FOR SHARE和FOR UPDATE是正交的两种锁,互不兼容 - 迁移时特别注意:原来 MySQL 用间隙锁防幻读的逻辑,在 PG 中得靠
SERIALIZABLE隔离级别或显式重试来兜底
事情说清了就结束










