快照读在mysql中并非bug,而是rr级别下mvcc的正常行为:快照于事务内首次select时生成,后续快照读复用该版本;当前读(如select...for update)则绕过快照读取最新已提交数据。

快照读看不到最新提交的数据,不是 bug,是 MySQL 在 REPEATABLE READ 隔离级别下 MVCC 的正常行为——它读的是事务启动时(或首次 SELECT 时)生成的快照,不是“当前最新”。
快照读什么时候建立?
关键点在于:快照不是在 START TRANSACTION 就立刻建好,而是在事务内**第一次执行 SELECT(快照读)时才生成**。
- 如果事务开启后先做
UPDATE、INSERT,再做SELECT,那这次SELECT看到的仍是旧快照,哪怕中间其他事务已提交 - 如果用
START TRANSACTION WITH CONSISTENT SNAPSHOT,快照会在START TRANSACTION时立即建立 - 事务内后续所有快照读都复用这个初始快照,不管外部数据怎么变
为什么 SELECT ... FOR UPDATE 能看到新数据?
因为它不是快照读,而是**当前读**——绕过 MVCC 快照,直接查最新已提交版本,并加锁。
-
SELECT ... FOR UPDATE、SELECT ... LOCK IN SHARE MODE、UPDATE、DELETE全部属于当前读 - 当前读会忽略事务自己的 Read View,去读
DB_TRX_ID ≤ 当前最大已提交事务 ID的最新行版本 - 但要注意:无索引条件会导致全表扫描+表级锁,性能和并发风险陡增
Python 里用 PyMySQL 查不到最新数据怎么办?
常见于长连接复用、未显式控制事务边界,导致一个连接里多个查询共享同一个旧快照。
- 每次查询前手动
conn.begin()或cursor.execute("START TRANSACTION"),再SELECT,可强制新建快照(但不推荐高频开事务) - 更稳妥的做法:把隔离级别临时设为
READ COMMITTED,让每次SELECT都生成新快照:cursor.execute("SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED") - 避免长连接复用——用完即
conn.close(),或用连接池配置max_idle_time控制空闲超时
最容易被忽略的陷阱
你以为自己在查主库,其实连的是从库;你以为事务刚开,其实连接早被复用、快照早已固化;你以为触发器能帮你实时聚合,结果它根本没跑成功——这些都不是快照机制的问题,而是环境配置和代码路径没对齐。











