用两个真实并发会话演示MVCC一致性读的效果,再用History list length这个真实指标验证undo log到底是按什么单位在积累。
session A开一个带一致性快照的事务,中途sleep;session B在A的事务还开着的时候,修改同一行数据并提交。
session ASTART TRANSACTION WITH CONSISTENT SNAPSHOT; SELECT amount FROM orders WHERE id=1; -- 第一次读 SELECT SLEEP(4); SELECT amount FROM orders WHERE id=1; -- 第二次读,此时B已经改完并提交 COMMIT; SELECT amount FROM orders WHERE id=1; -- 提交之后,新开一次读 -- session B(在A的SLEEP期间执行) UPDATE orders SET amount = 9999.99 WHERE id=1;
A 第一次读: amount = 148.08 (B 执行 UPDATE ... = 9999.99 并提交) A 第二次读(同一事务内): amount = 148.08 ← 真实没变,B的修改对A不可见 A COMMIT 之后,新读一次: amount = 9999.99 ← 事务结束后,才真实看到新值
A的事务用START TRANSACTION WITH CONSISTENT SNAPSHOT开启时,真实建立了一个Read View,之后A在这个事务内的每一次读,都通过undo log真实还原出Read View建立那一刻的数据版本,B之后的修改和提交对A来说是"看不见"的。A提交之后,旧事务结束,新的一次读会重新建立Read View,这时候B的修改才真实变得可见。
基线: History list length = 0
单条UPDATE一次性改3000行(一个事务): History list length = 1 ← 只涨了1,不是3000
A开一个长事务快照后,B执行40次独立的单行UPDATE(各自单独提交,共40个事务):
History list length = 41 ← 真实涨了40第一次真实测试容易踩的一个误区:以为History list length是按"被修改的行数"涨的。真实测下来完全不是——一条UPDATE语句哪怕真实改了3000行,只要它是同一个事务(自动提交模式下,一条语句默认就是一个事务),History list length也只真实涨1;而40条各自独立提交的单行UPDATE,因为是40个不同的事务,真实让这个数字涨了40。History list length统计的是"有多少个已提交事务的undo log,还没有被purge线程真实清理掉",单位是事务,不是行。
A COMMIT,长事务结束: History list length = 41 之后等待3秒: History list length = 41 再补充3次UPDATE+各等5秒,共约15秒:History list length = 41 ← 真实没有变化 再补充100次UPDATE,之后再等5秒: History list length = 41 ← 依然没变
每一步都是本文上面用两个真实并发会话跑出来的原始结果。
START TRANSACTION WITH CONSISTENT SNAPSHOT + SHOW ENGINE INNODB STATUS里的History list length