用一个连接FOR UPDATE锁住一行,另外两个连接分别用普通SELECT和UPDATE去碰它,看一致性读和锁定读的分界线在哪里。
A用SELECT ... FOR UPDATE锁住一行不提交,B用普通SELECT读它,C用UPDATE改它。
操作A: START TRANSACTION A: SELECT v FROM t4 WHERE id=1 FOR UPDATE → 锁住id=1这一行,v=100 B: SELECT v FROM t4 WHERE id=1 (普通一致性读,不加锁) C: UPDATE t4 SET v=999 WHERE id=1 (需要获取写锁) 1.5秒后 A: COMMIT
同样是访问A锁住的那一行,B和C的遭遇完全不同。
结果B的普通SELECT:耗时 0.0001 秒,立刻拿到 v=100 C的UPDATE:卡住,在A提交前一直等待 1.5秒后Acommit C的UPDATE完成,总耗时 1.5029 秒(几乎正好等于A的提交延迟)
B的普通SELECT完全没有被A的 FOR UPDATE 锁影响,0.0001秒瞬间返回——这就是"一致性读":普通SELECT走的是MVCC快照读,不需要获取任何锁,也就不会被别的事务持有的锁挡住。而C的UPDATE被阻塞了整整1.5秒,直到A提交才完成——因为UPDATE要获取这一行的排他锁,而A的 FOR UPDATE 已经持有了这把锁。
FOR UPDATE、LOCK IN SHARE MODE 这类语法,或者不是UPDATE/DELETE这种写操作,SELECT走的都是不加锁的快照读,跟其他事务的锁完全绝缘;一旦涉及修改数据(或显式要求锁定),才会去排队等锁。
每一步都是本文上面用三个MySQL连接跑出来的原始结果。
SELECT ... FOR UPDATE:获取排他锁的锁定读