MySQL并发导致的脏数据分析

来源:互联网 发布:服务器提供商 知乎 编辑:程序博客网 时间:2024/05/17 23:50

记录一下一个并发导致的脏数据问题(基于MySQL)。问题描述(银行操作员例子):比如A、B操作员同时读取一余额为1000元的账户,A操作员为该账户增加100元,B操作员同时为该账户扣除50元,A先提交,B后提交。最后实际账户余额为1000-50=950元,但本该为1000+100-50 = 1050 。

首先分析下如何会导致问题的出现:


我们来些一个伪代码表示下A操作员操作执行的过程:User user = userDao.getById(1);      ----------(步骤1)user.setAccount(user.getAccount()+100);    ----------(步骤2)userDao.update(user);         ------------------(步骤3)而B操作员执行的过程为:User user = userDao.getById(1);      ----------(步骤1)user.setAccount(user.getAccount()-50);    ----------(步骤2)userDao.update(user);         ------------------(步骤3)


当A操作的程序执行到步骤1之后,步骤3之前的时候B执行了步骤1 。之后A执行了步骤3,B再执行步骤2跟3 。此时问题就出现了。这就是典型的因为并发而导致脏数据的问题。有人说给代码加事务啊,在方法上加入@Transational就不会出现这问题了嘛。在这我想说这样做是没用的。当然,加事务也是可以解决这个问题的,但是你需要设置事务的隔离级别为SERIALIZABLE(可串行化)才能做到不会有幻读的出现。但是这样做在并发的情况下效率会特别低下(每个对该数据行的请求,包括读跟写的请求都需要等待前面的读跟写全部执行完才能执行)。然而对于MySQL的默认事务隔离级别是REPEATABLE(可重复读),在这个隔离级别下,即使A执行步骤1后给数据行加上了读锁,但是B执行步骤1时也可以对数据行进行读取,此时就出现了幻读的问题。所以单纯的在方法上加入@Transational注解是解决不了问题的。

相关知识点

事务的四个特性:原子性,一致性,隔离性,持久性

  1. 原子性:包含在事务内的所有操作,要么全部执行完成,要么全部执行失败
  2. 一致性:包含在事务内的所有操作设计的数据行,能被查看到的要么全部执行完成后的结果,要么全部完成前的结果。也就是说小明有100块,小花有10块,小明给小花转了50块。那么对于其他事务来说,能看到的只有是小明有50块同时小花有60块。或者是小明有100块同时小花有10块。而不能出现小明有50块而小花有10块。这就叫做一致性
  3. 持久性:事务执行完成后数据被持久化到磁盘。
  4. 隔离性:隔离性有四大隔离级别:

        ① Serializable (串行化):可避免脏读、不可重复读、幻读的发生。

   ② Repeatable read (可重复读):可避免脏读、不可重复读的发生。

   ③ Read committed (读已提交):可避免脏读的发生。

   ④ Read uncommitted (读未提交):最低级别,任何情况都无法保证。

那么什么是脏读,幻读呢?

1,脏读

  脏读是指在一个事务处理过程里读取了另一个未提交的事务中的数据。

  当一个事务正在多次修改某个数据,而在这个事务中这多次的修改都还未提交,这时一个并发的事务来访问该数据,就会造成两个事务得到的数据不一致。例如:用户A向用户B转账100元,对应SQL命令如下


    update user set account=account+100 where name=’B’;  (此时A通知B)    update user set account=account - 100 where name=’A’;

当只执行第一条SQL时,A通知B查看账户,B发现确实钱已到账(此时即发生了脏读),而之后无论第二条SQL是否执行,只要该事务不提交,则所有操作都将回滚,那么当B以后再次查看账户时就会发现钱其实并没有转。

2,不可重复读

  不可重复读是指在对于数据库中的某个数据,一个事务范围内多次查询却返回了不同的数据值,这是由于在查询间隔,被另一个事务修改并提交了。

  例如事务T1在读取某一数据,而事务T2立马修改了这个数据并且提交事务给数据库,事务T1再次读取该数据就得到了不同的结果,发送了不可重复读。

  不可重复读和脏读的区别是,脏读是某一事务读取了另一个事务未提交的脏数据,而不可重复读则是读取了前一事务提交的数据。

  在某些情况下,不可重复读并不是问题,比如我们多次查询某个数据当然以最后查询得到的结果为主。但在另一些情况下就有可能发生问题,例如对于同一个数据A和B依次查询就可能不同,A和B就可能打起来了……

3,虚读(幻读)

  幻读是事务非独立执行时发生的一种现象。例如事务T1对一个表中所有的行的某个数据项做了从“1”修改为“2”的操作,这时事务T2又对这个表中插入了一行数据项,而这个数据项的数值还是为“1”并且提交给数据库。而操作事务T1的用户如果再查看刚刚修改的数据,会发现还有一行没有修改,其实这行是从事务T2中添加的,就好像产生幻觉一样,这就是发生了幻读。

  幻读和不可重复读都是读取了另一条已经提交的事务(这点就脏读不同),所不同的是不可重复读查询的都是同一个数据项,而幻读针对的是一批数据整体(比如数据的个数)。

 

巴拉巴拉了一大堆,现在来讲讲解决方案(自己的想法,有不足之处请大家帮忙指出~)

对于这个问题我们可以使用乐观锁的方式来实现:在user表中加入一个version字段,每次执行update时version++;


操作员A执行的sql操作:select id, account,version from user where id="1"; 查询结果:id=1, account=1000, version=1 update usersetaccount=account+100, version=version+1 whereid="1" and version=1 select id, account,version from user where id="1"; 查询结果:id=1, account=1100, version=2操作员B执行的sql操作select id, account, version from user where id="1"; 查询结果:id=1, account=1000, version=1#操作员A已修改成功,实际user.account=1100、user.version=2,操作员B也将版本号加一(version=2)试图向数据库提交数据(account=950),但此时比对数据库记录版本时发现,操作员B提交的数据版本号为2,数据库记录当前版本也为2,不满足 “提交版本必须大于记录当前版本才能执行更新 “的乐观锁策略,因此,操作员B的提交被驳回。update userset account=account-50, version=version+1 where id="1" and version=1select id, account, version from user where id="1"; 查询结果:id=1, account=1100, version=2
 
阅读全文
0 0
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 药吃了胃难受怎么办 吃了牙痛药胃痛怎么办 吃了药刺激胃怎么办 吃凉的刺激到胃怎么办 误食打农药的菜怎么办 狗把蛇咬死了怎么办 吃过毒死的狗怎么办 偷用室友东西被发现怎么办 室友看综艺太吵怎么办 被甲鱼咬住不放怎么办 凤仙叶子干焦怎么办 香槟开了没喝完怎么办 土豆酸了吃了怎么办 吃了发酸的土豆怎么办 土豆没煮熟吃了怎么办 吃了发绿的土豆怎么办 孩子吃蒸土豆发恶心怎么办 吃了发麻的土豆怎么办 吃了没熟的土豆怎么办 吃土豆没熟中毒怎么办 吃小土豆能中毒怎么办 猪吃土豆中毒了怎么办 吃了不熟的土豆怎么办 吃了变绿的土豆怎么办 吃炸洋芋中毒了怎么办 脸上长毒气痘痘怎么办 吃鸡游戏中遇到毒气怎么办 吃多了颠茄片怎么办 玲珑骰子沾了水怎么办 花的枝干长歪了怎么办 电脑中毒了怎么办开不了机 台湾竹长得太高怎么办 文竹长得太高怎么办 桑叶牡l丹长虫怎么办 日本海棠开完花后枯萎了怎么办 长寿冠海棠烂根怎么办 夏季长寿冠海棠掉叶怎么办 竹节海棠有点烂根怎么办? 丽格海棠烂茎怎么办 长寿花徒长不开花怎么办 长寿花植株长了怎么办