上账务系统余额并发更新问题记录

某电商平台,某天线上用户报bug说账户余额信息与交易流水对不上。可以认为是数据库并发更新问题,由此定位出具体原因,并给出解决方案。

问题现象

场景描述

线上账务系统,在定时结算给卖家钱时,且高并发量的情况下,出现提现x元(假设当前用户余额为x元)余额为0后,再转入该账户一笔钱(假设为y元),结果账户余额变为了x+y 元,导致用户余额错误。 ps:账户余额的变更都是在事务中update的

环境说明

mysql5.7 + innodb,事务隔离级别是REPEATABLE-READ

场景模拟

我们简化下线上的数据结构,进行场景模拟。 数据表如下:

‘账户主表’

CREATE TABLE user (

uid int(11) NOT NULL COMMENT ‘类型id+自增序列‘,

name varchar(32) DEFAULT NULL,

PRIMARY KEY (uid)

) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT=‘账户主表‘

‘账户余额明细表’

CREATE TABLE user_account (

uid int(11) NOT NULL,

amount decimal(19,4) DEFAULT 0 COMMENT ‘账户余额‘,

PRIMARY KEY (uid)

) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT=‘账户余额明细表‘

账户类型配置

CREATE TABLE user_conf (

type_id int(11) NOT NULL, description varchar(32) DEFAULT NULL COMMENT ‘类型描述‘, PRIMARY KEY (type_id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT=‘账户类型配置‘

具体数据为:

select * from user;

+-------+------+ | uid | name | +-------+------+

| 10001 | a |

| 10002 | b |

select * from user_account;

+-------+----------+ | uid | amount | +-------+----------+

| 10001 | 10.0000 |

| 10002 | 108.9900 |

select * from user_conf;

+---------+--------------+ | type_id | description | +---------+--------------+

| 100 | 外部账户 |

| 200 | 内部账户 |

模拟提现(即余额减)和入账(即余额加)并发操作的事务如下:

session1-提现10元session2-入账20元begin;select description from user_conf where type_id = 100;select from user where uid = 10001 for update; // user表用来做互斥select amount from user_account where uid = 10001; // 10.00begin;select description from user_conf where type_id = 100;select from user where uid = 10001 for update; // wating//watingupdate user_account set amount = 0.00 where uid = 10001;commit;拿到锁select amount from user_account where uid = 10001; //10.00入账20元,代码中计算后应该为30元update user_account set amount = 30.00 where uid = 10001;commit;

问题出现了,后面再查询该用户余额为30元,即用户提现的10元未反映在余额中

原因定位

熟悉mysql的同学或许已经知道问题是由REPEATABLE-READ隔离级别下快照读导致。

具体解释:

RR级别下,第一次读操作会生成快照,对于可见性来说,只有当第一次读之前其他事务提交的修改和自己的修改可见,其他的均不可见。

官网文档:https://dev.mysql.com/doc/refman/5.7/en/glossary.html snapshot A representation of data at a particular time, which remains the same even as changes are committed by other transactions.

With REPEATABLE READ isolation level, the snapshot is based on the time when the first read operation is performed.

可见性原理

回到上述模拟场景中,session2在sql语句select description from user_conf where type_id = 100; 时已生成快照,虽然session1提交了,但仍然不可见,导致并发更新问题。

另外,开启事务后,SELECT … FOR UPDATE 是不会生成快照的,大家可自行实验

解决方案

方案一

将REPEATABLE-READ隔离级别改为READ-COMMITTED,这样即能看到最新提交的数据。

方案二

在读’账户余额明细表’user_account 的时候加 for update,这样会 1.强制读该行记录的最新版本数据,2.且若其他事务未commit,本事务将阻塞,保证串行更新

方案三

延时生成快照。开启事务后,首先就通过user表做互斥,直接for update加锁,针对多个事务并发更新即变为串行。

附:定位过程

针对上报bug用户,查询其交易流水明细与余额变更明细,确认账务存在问题
查询账务系统近几天是否有上线变更,检查无
拉取账务数据库mysql general log,找到并发更新的两个事务session
查询数据库设置的隔离级别为RR,查询应用数据库连接池配置即session的隔离级别未配置,采用数据库配置
确认由RR级别导致(当然也可以认为是代码问题导致)
确认是一个月前账务系统分库分表上线,改用其他连接池且未设置session隔离级别。而之前是有配置session的隔离级别为READ-COMMITTED。
延伸思考

mysql RR级别适用的业务场景是什么,应该怎么选择? 有兴趣或有见解的同学可以留言回复或私信~~

原文地址:http://blog.51cto.com/13963248/2308377

时间: 2025-01-14 01:38:15

上账务系统余额并发更新问题记录的相关文章

多线程并发更新同一条记录

有一个任务表,每个任务有n个事项要并发处理, 每个事项完成时需要更新已完成数,即finished+1,当n个事项全部处理完成即整个任务完成时需要做任务的统计工作. 数据库通过行级锁来保证并发更新准确性,测试代码如下: public class Concurrency {          /**执行更新语句,根据commitImmediately决定是否立即提交*/     public static int executeUpdate(String sql, boolean commitImm

徐汉彬:Web系统大规模并发——电商秒杀与抢购(转)

[导读]徐汉彬曾在阿里巴巴和腾讯从事4年多的技术研发工作,负责过日请求量过亿的Web系统升级与重构,目前在小满科技创业,从事SaaS服务技术建设. 电商的秒杀和抢购,对我们来说,都不是一个陌生的东西.然而,从技术的角度来说,这对于Web系统是一个巨大的考验.当一个Web系统,在一秒钟内收到数以万计甚至更多请求时,系统的优化和稳定至关重要.这次我们会关注秒杀和抢购的技术实现和优化,同时,从技术层面揭开,为什么我们总是不容易抢到火车票的原因? 一.大规模并发带来的挑战 在过去的工作中,我曾经面对过5

从电商秒杀与抢购谈Web系统大规模并发

从电商秒杀与抢购谈Web系统大规模并发 http://www.iamlintao.com/4242.html 一.大规模并发带来的挑战 在过去的工作中,我曾经面对过5w每秒的高并发秒杀功能,在这个过程中,整个Web系统遇到了很多的问题和挑战.如果Web系统不做针对性的优化,会轻而易举地陷入到异常状态.我们现在一起来讨论下,优化的思路和方法哈. 1. 请求接口的合理设计 一个秒杀或者抢购页面,通常分为2个部分,一个是静态的HTML等内容,另一个就是参与秒杀的Web后台请求接口. 通常静态HTML等

【问底】徐汉彬:Web系统大规模并发——电商秒杀与抢购

摘要:电商的秒杀和抢购,从技术的角度来说,会对Web系统产生巨大的考验.本期<问底>,徐汉彬将带大家关注秒杀和抢购的技术实现和优化,同时,从技术层面揭开,为什么我们总是不容易抢到火车票的原因. [导读]徐汉彬曾在阿里巴巴和腾讯从事4年多的技术研发工作,负责过日请求量过亿的Web系统升级与重构,目前在小满科技创业,从事SaaS服务技术建设. 电商的秒杀和抢购,对我们来说,都不是一个陌生的东西.然而,从技术的角度来说,这对于Web系统是一个巨大的考验.当一个Web系统,在一秒钟内收到数以万计甚至更

徐汉彬:Web系统大规模并发——电商秒杀与抢购

Web系统大规模并发——电商秒杀与抢购 电商的秒杀和抢购,对我们来说,都不是一个陌生的东西.然而,从技术的角度来说,这对于Web系统是一个巨大的考验.当一个Web系统,在一秒钟内收到数以万计甚至更多请求时,系统的优化和稳定至关重要.这次我们会关注秒杀和抢购的技术实现和优化,同时,从技术层面揭开,为什么我们总是不容易抢到火车票的原因? 一.大规模并发带来的挑战 在过去的工作中,我曾经面对过5w每秒的高并发秒杀功能,在这个过程中,整个Web系统遇到了很多的问题和挑战.如果Web系统不做针对性的优化,

Java开源生鲜电商平台-OMS订单系统中并发问题和锁机制的探讨与解决方案(源码可下载)

Java开源生鲜电商平台-OMS订单系统中并发问题和锁机制的探讨与解决方案(源码可下载) 说明:Java开源生鲜电商中OMS订单系统中并发问题和锁机制的探讨与解决方案: 问题由来     假设在一个订单系统中(以火车票订单系统为例),用户A,用户B都要预定从成都到北京的火车票,A.B在不同的售票窗口均同时查询到了某车厢卧铺中.下铺位有空位.用户A正在犹豫订中铺还是下铺,这时用户B果断订购了下铺.当用户A决定订下铺时,系统提示下铺已经被预订,请重新选择铺位.在这个系统场景中,我们来探讨一下,火车票

使用工作流更新子记录

??Ever wish you could use a workflow to update related/child records? For example, when I update the address on an account, update the address on all of the related contacts. You can, with the help of the very helpful CRM 2011 Distribute Workflow Act

艺萌TCP文件上传下载及自动更新系统介绍(TCP文件传输)(一)

艺萌TCP文件上传下载及自动更新系统介绍(TCP文件传输) 该系统基于开源的networkComms通讯框架,此通讯框架以前是收费的,目前已经免费并开元,作者是英国的,开发时间5年多,框架很稳定. 项目地址:http://www.51aspx.com/code/MSDCArtMengFileUpload 咨询qq:286275658 演示程序下载地址:http://pan.baidu.com/s/1geVfmcr 服务器端运行效果图: 服务器端配置文件: <?xml version="1.

web系统常见并发问题

说说考试系统中的一些并发问题吧,这几天着重解决了一下题目这边的并发问题. 其实并发问题不外乎就是: 1.当插入一道试题时,结果这个题目所属的课程被删除掉了: 2.当删除一个课程时,先查时这个课程下的试题没有被使用可以被删除,结果在将将要执行删除语句之前,有一道试题被使用了: 对于第一个问题,其实用外键就可以解决.但是公司似乎有规定,不能够使用外键.主要原因还是外键对于数据库性能的损耗比较严重,特别是当数据量较大时. 其实外键对于我们这个考试系统也是不好使的,因为系统中的删除都是逻辑删除,只是将i