背景:
mysql集群,读写分离,一主两从。
原本的业务(代码)逻辑如下:
问题:但是同一个孩子对同一个题目出现了两条答题结果
5 定位问题
scratch答题数据初始化逻辑: 1)判断是否有该用户的答题数据 2)没有则新插入初始化数据 3)
但是插入的几条记录时间间隔都比较大,所以猜测是记录插入时 mysql和redis等数据存储存在一定的问题。
先看了下redis发现所有数据均正常。
然后查看mysql发现其iops明显增加,如下图:
几乎可以确认是mysql的问题,怎么确认呢?按照代码逻辑一定是先查询后插入,所以想通过mysql的binlog获取信息。
所以到这里问题解决了,插入时间分别是4.11 4.20 4.21 但是都是被阻塞了,执行时间都在几分钟之后。
简单插入一条记录,为什么执行了分钟级别的时间?
这条alter语句,执行了近20分钟,且这个该sql没有阻塞select 但阻塞了inset,所以最终导致insert多次,产生重复答题结果
流程如下:
别的同事新写了一个wiki,说明两条数据是因为读写分离导致的主从延迟,并发现事务不生效,提出的解决方案是增加事务。
总体结论
说明:
1 代码如果实际没有启用事务,读写分离如果有大的延迟的确会导致重复数据。即可能出现主库已经插入,未同步到从库,所以第二个线程查询发现依旧没有数据存在,此时依旧会执行插入
2 如果代码中启用了事务,不存在读写分离的问题,所有的读写都会走主库。所以即使有很大的主从延迟,也不会导致数据的重复。事务中的读写sql都走主库原因如下:
3 启用了事务也不能完全避免重复数据,因为数据库的隔离级别是读已提交,所以仍有可能出现不可重复读 和 幻读的问题,场景如下:
事务1拿到分布式锁,中insert,但是还没提交
alter、大事务等导致事务1被阻塞,事务1的redis分布式锁释放
事务2拿到分布式锁,虽然查的也是主库,因为事务1中还没提交,查询依旧查不到,以为还没有孩子的答题结果,所以会继续insert
事务1中提交
事务2中提交
所以结论:事务只能解决读写分离导致的问题;不能解决分布式锁超时导致的问题。
解决:大SQL业务低峰期执行 + 从硬件、配置、业务层面减小MySQL主从延迟 + 业务唯一索引 +
启用事务,避免多次快速保存导致的重复数据