金融系统的数据更新问题

txlsy 发布于 2016/05/25 12:54
阅读 3K+
收藏 6

今天面试,面试官问“9块变8块,数据更新怎么操作”,当时就一脸萌比了。

本身水平就不高,还没相关经验,想到的就是“直接update啊”。。。。。

然后就被鄙视了。

各位说说都该考虑到些什么?

加载中
2
eechen
eechen
数据库并发控制(库存/余额)
事务SELECT FOR UPDATE悲观锁(排它锁)使用场景举例(以MySQL InnoDB为例):
假设商品表单products内有一个存放商品数量的字段quantity,在下单之前必须先确定商品数量quantity是否足够.
假设编号为100的商品的库存剩余5件,用户下单购买1件.
不安全的做法:
SELECT quantity FROM products WHERE id=100;
UPDATE products SET quantity=5-1 WHERE id=100;
为什么不安全呢?
少量并发时或许不会有问题,但是大量的数据存取则有可能会出问题,除非你只用一个PHP-FPM工作进程.
如果我们需要在quantity>0的情况下才能扣库存,假设程序在第一行SELECT读到的quantity是5,看起来数字没有错,
但是当MySQL正准备要UPDATE的时候,可能已经有人把库存扣成0了,但是程序却浑然不知,将错就错的UPDATE下去了.
因此有必要使用MySQL事务来确保读取及提交的数据都是正确的:
SET AUTOCOMMIT=0;
BEGIN WORK;
SELECT quantity FROM products WHERE id=100 FOR UPDATE; /* 库存数量不少于用户下单量 */
UPDATE products SET quantity=5-1 WHERE id=100;
COMMIT WORK;
SET AUTOCOMMIT=1;
其中SELECT FOR UPDATE语句的语法与SELECT语句相同,只是在SELECT语句的后面加了FOR UPDATE子句.
该语句用来锁定特定的行,当这些行被锁定后,其他会话可以读取这些行,但不能更改或删除这些行,
直到该语句的事务被commit语句或rollback语句结束为止.
值得一提的是,事务中的SELECT FOR UPDATE遇到被其他事务的SELECT FOR UPDATE锁定的数据时,会等待其它事务结束后才执行,而事务中的SELECT则不受此影响.
如果只开启事务,SELECT里不加FOR UPDATE子句的话,当前事务执行SELECT时并不能阻止其他事务进行SELECT同一条数据的操作.
可见,SELECT FOR UPDATE是一个写操作,在事务提交后,其他事务的SELECT FOR UPDATE才可以操作同一条记录,如果操作的记录互不相同,则彼此不会被卡住.

txlsy
txlsy
回复 @Will_awokE : 高并发呢,该咋办
Will_awokE
Will_awokE
这个只是解决低并发下的超买而已。
txlsy
txlsy
面试官是有提到要注意安全性,可是一下子想到的是数据的安全性,想着都要更新了数据还能有什么安全性,原来是指这个啊
西湖老司机
西湖老司机
eechen 大神 你写的太多 俺没耐心看完啊
1
leo108
leo108
  1. update table set money=money-1 where money=9 and xxx=xxx;
  2. 记录一条操作日志,何时、对何账户、因何原因变更了多少金额,变更前和变更后分别是多少
leo108
leo108
回复 @我叫头条 : 恩,有道理,应该在where里加上money=9
txlsy
txlsy
后来再想想的时候也想到了要做日志,但是就想不到别的了
蛋看江湖
蛋看江湖
不要用事务,高并发量撑不住,流水可以查问题的
MockMan
MockMan
where money=9
leo108
leo108
当然,这两个操作是需要用事务包含
1
码畜
码畜

9-1=8这种场景,事务是必须,接着是资金流水,而一旦复杂点的业务上还得要程序的日志流水,当出现无法预知的程序中断才可找到上回的断点。减少资金操作可以用+ -的流水来做余额。

再有就是资金的追逐。扯远点还有操作习惯的风控系统,会员总在过去一个区间日期内在某个城市操作账号,今天一下子变成其他城市ip,或者总是在某个时间段内操作,突然有一天在半夜进行操作,这个时候就需要验证账号的安全了。

总之,对于金融系统就是日志日志日志流水流水流水事务事务事务,不然,一旦资金数据出问题...且不说损失,修复数据绝对是痛苦的一件事。

魔力猫
魔力猫
实际上真正严格的财务场景,是不许搞9-1这种Update方式更新的。这种更新本身就是一种不安全的方式。哪怕有各种锁定也是不成的。
0
0
adamsun
adamsun
update table set money=8 where money=9 and xxx...
0
rock912
rock912
乐观锁
0
宏哥
宏哥

引用来自“eechen”的评论

数据库并发控制(库存/余额)
事务SELECT FOR UPDATE悲观锁(排它锁)使用场景举例(以MySQL InnoDB为例):
假设商品表单products内有一个存放商品数量的字段quantity,在下单之前必须先确定商品数量quantity是否足够.
假设编号为100的商品的库存剩余5件,用户下单购买1件.
不安全的做法:
SELECT quantity FROM products WHERE id=100;
UPDATE products SET quantity=5-1 WHERE id=100;
为什么不安全呢?
少量并发时或许不会有问题,但是大量的数据存取则有可能会出问题,除非你只用一个PHP-FPM工作进程.
如果我们需要在quantity>0的情况下才能扣库存,假设程序在第一行SELECT读到的quantity是5,看起来数字没有错,
但是当MySQL正准备要UPDATE的时候,可能已经有人把库存扣成0了,但是程序却浑然不知,将错就错的UPDATE下去了.
因此有必要使用MySQL事务来确保读取及提交的数据都是正确的:
SET AUTOCOMMIT=0;
BEGIN WORK;
SELECT quantity FROM products WHERE id=100 FOR UPDATE; /* 库存数量不少于用户下单量 */
UPDATE products SET quantity=5-1 WHERE id=100;
COMMIT WORK;
SET AUTOCOMMIT=1;
其中SELECT FOR UPDATE语句的语法与SELECT语句相同,只是在SELECT语句的后面加了FOR UPDATE子句.
该语句用来锁定特定的行,当这些行被锁定后,其他会话可以读取这些行,但不能更改或删除这些行,
直到该语句的事务被commit语句或rollback语句结束为止.
值得一提的是,事务中的SELECT FOR UPDATE遇到被其他事务的SELECT FOR UPDATE锁定的数据时,会等待其它事务结束后才执行,而事务中的SELECT则不受此影响.
如果只开启事务,SELECT里不加FOR UPDATE子句的话,当前事务执行SELECT时并不能阻止其他事务进行SELECT同一条数据的操作.
可见,SELECT FOR UPDATE是一个写操作,在事务提交后,其他事务的SELECT FOR UPDATE才可以操作同一条记录,如果操作的记录互不相同,则彼此不会被卡住.

myslqer原来也知道点事务

0
魔力猫
魔力猫
9变8,是插入一条-1。财务流水不得随意乱改。
0
南湖船老大
南湖船老大

你这么回答,果断“下一位”。最基本的数据库的概念都没有

你要考虑到其他业务也更新这个金额,或者其他业务读到了这个金额,可能读到9,也可能读到8.。

蛋看江湖
蛋看江湖
所以不能在where 后面加上金额限制,直接进行扣钱操作成功后追加流水
返回顶部
顶部