read committed 查不到刚插入记录是因为事务未提交,其他事务默认不可见;repeatable read 下仍可能幻读,因间隙锁无法覆盖所有场景;高并发扣库存应选 repeatable read 配合原子 update 或 select for update。

READ COMMITTED 为什么查不到刚插入的记录?
因为事务还没提交,其他事务默认看不到未提交的变更。这是隔离级别的基本约束,不是 bug。
- PostgreSQL 默认是
READ COMMITTED,MySQL InnoDB 在 RR 模式下也常被误以为“实时可见”,其实不然 - 执行
INSERT后立刻在另一个会话查不到,大概率是没提交,而不是隔离级别设错了 - 如果真需要“刚写就可见”,得用
SELECT ... FOR UPDATE或显式加锁,但会阻塞并发,别盲目上
REPEATABLE READ 下为什么还会出现幻读?
MySQL InnoDB 的 REPEATABLE READ 通过间隙锁(gap lock)抑制大部分幻读,但不等于完全消除——尤其涉及 INSERT ... SELECT 或无索引字段查询时。
- 幻读典型场景:
SELECT COUNT(*) WHERE status = 'pending'返回 5,接着另一事务插入一条status = 'pending'并提交,当前事务再查还是 5;但如果执行UPDATE ... WHERE status = 'pending',InnoDB 可能只锁已有行,新插入的行不受影响 - 真正防幻读得靠
SERIALIZABLE,但代价是自动将所有SELECT转成SELECT ... LOCK IN SHARE MODE,并发性能断崖下跌 - 多数业务用
REPEATABLE READ+ 应用层重试或唯一约束更实际,比如插入前先SELECT FOR UPDATE检查是否存在
如何临时切换会话隔离级别?
用 SET TRANSACTION ISOLATION LEVEL,但要注意作用域和生效时机。
- 必须在事务开始前设置,否则报错:
ERROR 1568 (25001): Transaction characteristics can't be changed while a transaction is in progress - MySQL 支持:
READ UNCOMMITTED、READ COMMITTED、REPEATABLE READ、SERIALIZABLE;PostgreSQL 不支持READ UNCOMMITTED(等价于READ COMMITTED) - 示例:
SET TRANSACTION ISOLATION LEVEL READ COMMITTED; BEGIN; SELECT * FROM orders;—— 这个SELECT就运行在 RC 级别下 - 注意:某些 ORM(如 Django)会在连接池初始化时固定隔离级别,手动
SET可能被覆盖,得看框架文档是否允许 per-transaction override
高并发下单扣库存,该选哪个隔离级别?
别迷信“越高级越安全”,SERIALIZABLE 在订单场景往往引发大量锁等待甚至死锁,REPEATABLE READ 配合行锁+唯一约束才是更稳的选择。
- 典型错误:用
SELECT ... WHERE stock > 0判断后UPDATE SET stock = stock - 1,中间可能被其他事务插队扣成负数 —— 这不是隔离级别问题,是逻辑漏洞 - 正确做法:一条
UPDATE products SET stock = stock - 1 WHERE id = 123 AND stock >= 1,检查ROW_COUNT()是否为 1 - 如果业务要求强一致性(比如秒杀),加
SELECT ... FOR UPDATE是明确意图,但要确保 WHERE 条件走索引,否则升级为表锁 - PostgreSQL 的
SELECT ... FOR UPDATE SKIP LOCKED能跳过已被锁的行,适合分页派单类场景,MySQL 8.0+ 才支持类似语法
实际应用中,隔离级别不是配置开关,而是和 SQL 写法、索引设计、事务粒度捆在一起的。改了级别却没调查询逻辑,反而更容易出隐蔽问题。










