博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
一个UPDATE语句引发的血案
阅读量:6513 次
发布时间:2019-06-24

本文共 3458 字,大约阅读时间需要 11 分钟。

原文在我的博客:

今天对数据库的数据进行清查,发现其中有两个用户的数据与交易记录不一致,其中user0少了200虚拟币,user多了200虚拟币。幸好用户还没有投诉,赶紧把数据改正确,但是也要研究一下发生的原因。

虚拟币是可以在网站内部流通的一种产品,允许用户间买卖和赠送。

和用户交易相关的有三张表,分别是usersell_ordertransfer_ordertransfer_order记录了所有虚拟币转移记录,sell_order记录的是买卖虚拟币。

查一下sell_order

SELECT order_num, seller, buyer, count, complete_time \    FROM sell_order \    WHERE seller=user0 AND buyer=user1 \        AND complete_time > DATE_ADD(NOW, INTERVAL -1 DAY);复制代码
+-----------+--------+-------+-------+--------------------+| order_num | seller | buyer | count |complete_time       |+-----------+--------+-------+----------------------------+| 233333333 | user0  | user1 | 200   |2018-01-01 12:00:00 |+-----------+--------+-------+-------+--------------------+复制代码

只有一条记录,没有异常发生。而清查数据库的脚本也依赖的就是这个表。那么问题可能出在了transfer_order里。

SELECT order_num, from, to, count, type, complete_time \    FROM transfer_order \    WHERE from=user0 AND to=user1 \        AND complete_time > DATE_ADD(NOW, INTERVAL -1 DAY);复制代码
+-----------+-------+-------+-------+----------------------------+| order_num | from  | to    | count | type | complete_time       |+-----------+-------+-------+-------+----------------------------+| 666666666 | user0 | user1 | 200   | sell | 2018-01-01 12:00:00 |+-----------+-------+-------+-------+----------------------------+| 666666667 | user0 | user1 | 200   | sell | 2018-01-01 12:00:00 |+-----------+-------+-------+-------+----------------------------+复制代码

这就是说虽然只有一笔卖出订单,但却生成了两笔转账订单,初步判断是用户疯狂点击转账按钮所致,那么这个BUG就应该隐藏在代码里,还原车祸现场:

def sell_order_complete(order_num, seller, buyer, count):    try:        session = DBSession()                session.query(TableSellOder) \            .filter(TableSellOder.order_num == order_num) \            .update({TableSellOder.complete_time == now(), TableSellOder.status: 'Success'})                    session.query(TableUser) \            .filter(TableUser.id = seller) \            .update({TableUser.count: TableUser.count - count})                    session.query(TableUser) \            .filter(TableUser.id = buyer) \            .update({TableUser.count: TableUser.count + count})                    session.add(TableTrandferOrder(order_num, seller, buyer, count, "sell"))            except Exception:        session.rollback()        rasie_http_error(500)复制代码

在示例代码里使用了Python和SQLAlchemy,此代码并非真实代码

看上去还算正常,把所有数据库操作放到了一个事务里,一旦出现异常回滚。但是SQL UPDATE语句不管有没有真的修改了语句,都不会报异常。当用户疯狂执行这个函数时,虽然表sell_order中的数据已经修改,但是使用相同的参数,这个函数可以无限执行,直到卖家的余额不足为止。

所以在执行SQL UPDATE语句时,需要检测SQL UPDATE语句执行的时影响的行数是否为预期的行数,如果不是预期的行数,需要手动出发异常,不再执行。

这是一场深刻的教训,修改后的代码如下:

def sell_order_complete(order_num, seller, buyer, count):    try:        session = DBSession()                assert session.query(TableSellOder) \            .filter(TableSellOder.order_num == order_num) \            .update({TableSellOder.complete_time == now(), TableSellOder.status: 'Success'})   \            == 1                    assert session.query(TableUser) \            .filter(TableUser.id = seller) \            .update({TableUser.count: TableUser.count - count}) \            == 1                    assert session.query(TableUser) \            .filter(TableUser.id = buyer) \            .update({TableUser.count: TableUser.count + count}) \            == 1                    session.add(TableTrandferOrder(order_num, seller, buyer, count, "sell"))            except Exception:        session.rollback()        rasie_http_error(500)复制代码

转载于:https://juejin.im/post/5b21dd8b6fb9a01e345bab95

你可能感兴趣的文章
关于extmail邮件服务器安装extsuite-webman出现的报错
查看>>
NoSQL--Redis 2.4--Set
查看>>
SVN使用教程总结
查看>>
linux和window是服务器时间同步
查看>>
Scenario 1 – HP C7000 Virtual Connect FlexFabric以太网网络设计Active/Standby Uplinks
查看>>
Linux RPM 命令详解及源码编译安装
查看>>
智能机时代,安居客让寻找房源更有效率
查看>>
Discuz建站教程:]本地安装discuz网站
查看>>
Apache安装
查看>>
修改centos系统默认编辑器
查看>>
gvim安装后,英文版与中文版如何互相切换
查看>>
读《Go并发编程实战》第4章 流程控制方式
查看>>
rpm安装、升级、查询与安全验证
查看>>
CUDA学习(一百零二)
查看>>
CUDA学习(一百零一)
查看>>
easyUi datagrid 返回时间格式化操作
查看>>
配置语法
查看>>
LAMP与LNMP加速与缓存优化(二)
查看>>
计算连续的IP地址问题
查看>>
关于CentOS中tomcat的8005端口启动不起来的解决办法
查看>>