嗯, 你说得对,读写冲突 指的应该是快照读。我又胡乱看了好一些文章,大概有一个猜想,不一定对:joy:
以下均为悲观事务:
-
DML阶段加的锁不会阻塞快照读,
因为此时还没有,原因暂时没想清楚。这个锁只是一个占位符,和Prewrite阶段的锁不一样,出处:commit_ts
,没必要阻塞TiDB 的悲观锁实现的原理确实如此,在一个事务执行 DML (UPDATE/DELETE) 的过程中,TiDB 不仅会将需要修改的行在本地缓存,同时还会对这些行直接上悲观锁,这里的悲观锁的格式和乐观事务中的锁几乎一致,但是锁的内容是空的,只是一个占位符,待到 Commit 的时候,直接将这些悲观锁改写成标准的 Percolator 模型的锁,后续流程和原来保持一致即可,唯一的改动是:
对于读请求,遇到这类悲观锁的时候,不用像乐观事务那样等待解锁,可以直接返回最新的数据即可(至于为什么,读者可以仔细想想)。
-
Prewrite 阶段,
2.1. 写事务会根据现有的max_ts
,计算min_commit_ts
2.2. 写事务会加个自己的start_ts
的锁
2.3. 快照读分两种情况
2.3.1. 快照读在 Prewrite 该 Key 前就读取了该 Key,会把该 Key 的max_ts
推高至快照读的start_ts
,如果下一次继续读这个 Key,判断min_commit_ts
是否大于快照读的start_ts
,若是,则锁 Bypass
2.3.2. 快照读在 Prewrite 该 Key 前没有读取该 Key,且start_ts
大于min_commit_ts
,会被锁住,这应该就是 读写冲突 里的 Backoff 重试
考虑以下三个事务:
T1 | T2 | T3 | Lock |
---|---|---|---|
Begin(Start TS = 1) | |||
Prewrite(x) | Begin(Start TS = 5) | Lock(x, Start TS = 1) | |
Read(y) => Max TS(y) = 5 | |||
Prewrite(y) => Min Commit TS(y) = 6 | Lock(y, Start TS = 1) | ||
Read(y) => Ok | Begin(Start TS = 7) | Min Commit TS(y) > Start TS(T2) => 锁Bypass | |
Read(y) => Block | Min Commit TS(y) < Start TS(T3) => 锁阻塞 | ||
COMMIT |