前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >记一次幻读排查

记一次幻读排查

原创
作者头像
榴莲其实还可以
发布2020-06-03 15:43:38
7920
发布2020-06-03 15:43:38
举报

前言

mysql支持4种级别的事务隔离:未提交读(read uncommitted),已提交读(read committed),可重复读(repeatable read),串行化(serializable)。一般mysql默认的事务隔离级别是可重复读,在这种隔离级别下当遇到幻读的情况,该怎么处理呢。

背景

腾讯课堂双十一活动中,对于满足条件(花钱购买了某些课程)的用户,平台会给他们返现,给他们发QQ/微信红包。举个例子某个用户购买了某门课程,然后平台需要给他返现500块,对于微信红包来说会分成3比,200,200,100这样发给用户。另外在发送红包的整个流程中,会经过一个风控服务(order_risk_manage), 风控服务会判断这笔奖励该不该发,正常情况下,以200,200,100的顺序发红包给用户,风控服务会返回3个通过。异常情况下如发送 200,200,200对于第三笔 200,风控服务会拒绝,因为发送的奖励超过了限制(500)。 风控判断是否可以通过的主要流程如下:

1,查询bonus表 目前已经发送了多少钱

2,当前已经发放的和即将发放的这笔之和是否超过限制

3,如果没超过限制,通过,并插入该条发放记录到bonus表,否则不通过

整个查询以事务的方式进行。

问题发现以及排查经过

首先我们再查表的过程中,发现该笔订单成功发放了3笔200,显然发多了。

为什么会发多呢,开始根据日志排查

通过日志发现,对于同一笔订单,实际上是有6笔200的奖励经过风控,我们看到其中有一笔经过风控返回的错误是

paid too much,paidAmount:600,amount:200,totalAmount:500

意思就是已经奖励了600,总共最多奖励500,这一笔200的奖励失败,不能发放。已经发了600,显然已经多发了。为什么会多发呢?继续看日志。

我们发现在读bonus表的过程中,有两次读到paidAmount=200。两次读到已发放200,然后准备奖励200,总共400,小于 500,所以风控通过了。

虽然我们查表插入是以事务的方式进行,但是rr的隔离级别,解决不了读表,然后插入导致的一致性问题。这里显然出现了幻读。

解决方案

1,将mysql的隔离级别提升到串行化,虽然能解决问题,但是会降低整体的效率,代价太大。 2,引入redis分布式锁,在事务函数调用中,先获取redis锁,事务完成后再释放。缺点:单独为这一个点,要引入redis套件,麻烦。 3,读表的时候加行的排它锁(x锁),语句大概是 select * from table_name where xx for update

此处我们选择了第3种方式,主要是影响小,对原先代码上的改动更小。

简单的方案测试

可以看到事务A在执行select的时候,事务B阻塞了,然后我们再事务A中插入一条记录,并提交再看。

当事务A commit后,事务B自动从阻塞状态恢复,并且可以看到事务B能够查询到事务A刚插入的一条记录。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • 背景
  • 问题发现以及排查经过
  • 解决方案
  • 简单的方案测试
相关产品与服务
云数据库 Redis
腾讯云数据库 Redis(TencentDB for Redis)是腾讯云打造的兼容 Redis 协议的缓存和存储服务。丰富的数据结构能帮助您完成不同类型的业务场景开发。支持主从热备,提供自动容灾切换、数据备份、故障迁移、实例监控、在线扩容、数据回档等全套的数据库服务。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档