共计 1095 个字符,预计需要花费 3 分钟才能阅读完成。
场景
业务中有一个日志表,插入数据与同步数据查询强依赖于主键的有序性
数据同步时携带上次同步更新的主键,查询到 Max(id)
之间的数据,只同步增量部分
意味数据只有一次同步机会
表结构如下
问题
Mysql 数据隔离级别是 RR
讲道理,两个不同的事务来读取数据后一个事务 B 是无法读取的到先一个事务 A 数据的数据
但是我们这里有个逻辑很特殊Max(id)
这里有两个场景:
A 先读,B 后写
假设目前数据最后一条记录是 3
A 事务携带上次同步主键 2 来请求,Max(id) = 3
这时 B 事务前来写数据
A 事务先于 B 事务,根据 Mysql MVCC 视图,查询到了 id=2,id= 3 的数据
同时 B 事务,写入数据 id=4,id=5
下次数据同步携带 id=3,继续走,业务无误
图示:
A 先写,B 后读
假设目前数据最后一条记录是 3
A 事务开始写入 4,5
事务还没有结束时,B 携带 2 来读
期望同步 2~3 之间的数据,实际上却读到了 2~5,但是时间上可读的数据还是 1 2 3
这是为什么呢?
这就牵扯到了 InnoDB 主键自增的规则了
InnoDB 的每个表的自增主键都,保存在内存中
偏移量:auto_increment_offset
步长:auto_increment_increment
默认双 1
插入数据时获取该值,+ 后写入 1
这个值不受 MVCC 事务一致性保护!!
测试方法
事务 1:
BEGIN;
INSERT INTO t1 VALUES(null,1);
事务 2:SELECT max(id) from t1
事务 1 先通过 Begin 开始事务,后续每执行一次 insert 插入,事务 2 都能获取到最新的 ID
就种情况就是对业务有损了!!
4 5 两条数据就被永远的丢失了,阿西吧!!!
天知道我查了多久啊!!!
图示:
解决方案
1. 间隙锁
在数据同步查询前加上 select max(1) from table for update;
增加间隙锁
阻塞读写,但是会有性能瓶颈
注意:间隙锁的范围要控制好
BadCase
1.select * from table limit 1 for update;
本意是只想锁住(max-1,max](max,sumpernum],减少锁间隙
但是忘记 主键索引默认正向有序
,这样其实是锁住了(-supernum,1](1,2]
正确的写法是:select * from table order by desc limit 1 for update;
2.select max(1) from table for update;
这种写法虽然满足了锁住表的要求,但是 锁的是主键索引,也就是全表
当时考虑 1 会增加一次 Mysql 运算,希望节省性能,但是这样并发就下去了
2. 分布式读写锁
以上的问题经过分析后都不是最好的方案,最后修改为分布式读写锁,具体方案见下一篇文章