ICode9

精准搜索请尝试: 精确搜索
首页 > 数据库> 文章详细

MYSQL 高并发下,记录变动后余额怎么统计才能更准确?

2023-12-05 23:06:36  阅读:304  来源: 互联网

标签:


-- 积分总表
CREATE TABLE `api_credits` (
`uid` bigint unsigned NOT NULL COMMENT '用户 ID',
`names` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '用户姓名',
`credits1` decimal(15,2) NOT NULL DEFAULT '0.00' COMMENT '积分 1 余额',
`credits2` decimal(15,2) NOT NULL DEFAULT '0.00' COMMENT '积分 2 余额',
`credits3` decimal(15,2) NOT NULL DEFAULT '0.00' COMMENT '积分 3 余额',
`time` int NOT NULL DEFAULT '0' COMMENT '更新时间',
PRIMARY KEY (`uid`)
) ENGINE=InnoDB COMMENT='积分总表';

-- 积分记录表
CREATE TABLE `api_credits_log` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT '记录 ID',
`uid` bigint unsigned NOT NULL DEFAULT '0' COMMENT '用户 ID',
`credits` decimal(15,2) NOT NULL DEFAULT '0.00' COMMENT '积分变动',
`balance` decimal(15,2) NOT NULL DEFAULT '0.00' COMMENT '变动后余额',
`cid` tinyint unsigned NOT NULL DEFAULT '0' COMMENT '操作类型',
`time` int unsigned NOT NULL DEFAULT '0' COMMENT '记录时间',
PRIMARY KEY (`id`),
KEY `uid_time` (`uid`,`time`)
) ENGINE=InnoDB COMMENT='积分记录表';

消费 100 积分,向 积分总表 api_credits 减去用户总积分,并向积分记录表 api_credits_log 写入一条记录,
balance 用户余额计算是 api_credits 表中 credits1 - 100 (即 80000 - 100 = 79900 )

消费事务会产生 SQL 数据:
SELECT * FROM `api_credits` WHERE `uid`='22' LIMIT 1
UPDATE `api_credits` SET `credits1`=`credits1`-'100' WHERE `uid`='22' AND `credits1`>='100'
INSERT INTO `api_credits_log` SET `uid`='22', `cid`='3', `credits`='100', `balance`='79900', `time`='1701001020'

但是在高并发场景下 SELECT 读出来的值不是最新,如,在同一时间段这个用户同时几条消费记录,这个变动后余额统计的就不是这么准确了,有没有什么好点的解决方案?

系统用了主从架构,读写分离,但是在这条事务中 SELECT 查的是主库

再说一些业务上的事。这篇要说的重点是:性能优化不是对业务透明的纯技术实现,好的性能优化往往判随着业务优化(即业务功能变更)。

先把那三个 SQL 转化成业务描述,这样更方便一些:
SELECT * FROM `api_credits` WHERE `uid`='22' LIMIT 1
——①、查询出指定 uid 的当前积分情况
UPDATE `api_credits` SET `credits1`=`credits1`-'100' WHERE `uid`='22' AND `credits1`>='100'
——②、对①查出来的积分,做积分扣减操作(原本的逻辑应该是「如果当前余额大于阈值,则计算最新余额后,更新为最新值」这种代码)
INSERT INTO `api_credits_log` SET `uid`='22', `cid`='3', `credits`='100', `balance`='79900', `time`='1701001020'
——③、对②所做的积分扣减做记录,需要记下变化后的余额

首先来说,在上面的场景中,第②步骤应该使用原本的代码逻辑,不该使用优化 SQL ,因为你已经做了第①步的查询,导致这种优化是无效的。② 这种优化方式,主要就是为了避开查询 SQL 上应用跟数据库之间的网络交互时间,那么你如果要用这种优化,就必须避开 ① 这一步。当你使用 update ... set col = col - num 这种 SQL 的时候,你需要避开任何相关查询 SQL ,通常你更应该用「一句」 SQL 完成整个业务操作。

然后,你之所以要做①,是因为③当中要记录余额。这时候你会发现,使用 「 update ... set col = col - num 」来做优化的性能要求, 记录余额的功能要求,是冲突的。如果你要就地修改,那么就无法同时获取余额值,包括修改前和修改后;如果你要获取修改后的余额值,那么就必须先将当前余额值或者修改后的余额值查询出来,不能单纯的就地修改。

最后就是要做选择的时候了,既然高并发性能要求跟记录余额的功能要求冲突,那就要做 2 选 1 。通常都会选择不记录余额,即余额变更记录,只记录变更事件、变更金额,不记录变更后以及变更前的余额。相比与高并发/快速扣减、不能超扣、事后可查每次的扣减记录这些核心业务,扣减记录上的余额展示,就只能算作边缘业务被抛弃了。这是有现实示例的:信用卡账单基本都这样;对于套餐类型的移动通话,你要去查通话详单,它的详单条目上也只会有通话时间,没有通话后的套餐剩余时间——如果你要精确对比,还得自己算;有些银行的借记卡消费提醒是只提醒消费多少不提醒消费后余额的。

标签:
来源:

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有