zoukankan      html  css  js  c++  java
  • 数据库事物导致的脏读 不可重复读 幻读的处理方法

    修正

    2020-04-23 之前是刚参加工作的理解 很多问题,现在回来重新梳理

    2021-08-05 事物原理 《mysql事物原理(一)-undo log、redo log、MVCC》

    2021-08-05 事物锁机制 《mysql deadlock、Lock wait timeout解决和分析》

    事物的特性

    原子性

    表示一个最小的逻辑单元,要么都执行 要么都不执行

    一致性

    事物处理前与处理后的状态的要是一致的(a账户有200元 b账户有300元   共计500元  a账户给b账户转账100元。事物处理后2个账户总额也为500元)

    隔离性

    每个事物都有自己的数据空间,使事物的处理结果不会被别的事物所影响,注:这里的隔离是指的自己受其他事物影响,而不是本身隔离别人(我把门关了别人就进不来)

    持久性

    事物提交 数据就永久的保存下来了

    事物隔离级别导致的异常现象

    脏读

    事物B 读取到了事物A 未提交的数据。

    如事物A 读取库存0 在原有库存上加5 ,事物B在事物A未提交前读取到了库存5 在原有基础上+5 update 库存变成了10,事物A出现异常回滚

    不可重复读

    事物A读取一次   事物B对数据进行修改或者删除提交事物  事物A再次读取 数据不一致

    幻读

    事物A 读取了一次   事物B  对数据进行新增或者删除并提交事物  导致读取数据列表数据多了

    事物的隔离级别

    Read uncommitted(读未提交)

    会导致脏读 幻读 不可 重复读

                  我们举例一个下单场景:

                                                  1.商品编号:001的库存是10。a用户下单购买了10个商品,库存变成了0

                                2.a用户因为地址拍错执行退货操作:退货分为 (1).开启事物 (2).订单状态改为取消,(3).库存还原 (4).执行其他业务逻辑 5.提交事物

                                                  3.在执行 2.4的其他业务逻辑的时候 b.用户发现库存有10 执行购买 并提交事物,后续a用户退货操作因为某些操作失败 事物回滚,导致超卖

    扣除库存代码

    public  boolean  reduceStock(Long id,Long purchaseCount){
            ProductStock productStock=ProductStockDao.findById(id);
            if(productStock.getCount()<purchaseCount){
                throw new BusinessException(500,"库存不足");
            }
            //库存充足
            productStock.setCount(productStock.getCount()-purchaseCount);
            ProductStockDao.update(productStock);
            return true;
        }

    Read committed  读取已经提交的

    sqlSrver默认的隔离级别,因为当前事物只能读取其他数据已经提交的数据,所以我们上面例子 第3步会校验库存不足  

    其实这里并发还是会超卖

    比如有10个用户都下单10  因为并发都同时走到 if(productStock.getCount()<purchaseCount){ 这个时候库存都是10 判断库存充足下单成功

    建议通过加锁,或者sql 原子性控制如:

    public  boolean  reduceStock(Long id,Long purchaseCount){
           int updated=  productStockDao.execute("update product_stock set count=count-:purchaseCount  where count-:purchaseCount>=0 and id=:id",purchaseCount,id);
           return updated>0;
        }

    版本号乐观锁

       public  boolean  reduceStock(Long id,Long purchaseCount){
            ProductStock productStock=ProductStockDao.findById(id);
            String newVersion=productStock.getVersion()+1;
            int updated=  productStockDao.execute("update product_stock set count=count-:purchaseCount,version=:newVersion where id=:id and version=:version",purchaseCount,newVersion,id,productStock.getVersion());
            return updated>0;
        }

    第一种适合数值的扣除,第二种适合数据修改(体验不是很好)

    此隔离级别,可以解决我们的脏读但是会出现不可重复读

    如:

        1.查询用户性别

        2.如果性别是null 则update set为男

        3.再次查询并返回用户性别

        在第三这个步骤时,用户性别被其他用户改成了女  返回女

    Repeatable read  可重复读

    可以解决重复读问题,因为这个隔离级别读取的是快照,同时update的时候会加x 事物没提交前别的事物不能修改 但是会出现幻读

    如:

       查询a用户的消费总额

       select sum(money) from pay_log where userId=a  第二次查询的时候 log表又增加了一条数据,导致幻读

       注:网上都是这样说 我的理解因为有gap锁其他事物也不能添加 我觉得这个隔离级别也可以一定程度避免幻读

    Serializable 序列化

    这个隔离级别最高 能够避免 脏读 不可重复读 幻影读  但是效率低(当进行当前读的时候 是锁表 性能差)

  • 相关阅读:
    在WM中画个带有边框的Panel
    在PPC上安装SQL Mobile库
    利用SQL语句清理日志
    Asp.net Ajax 中的脚本错误: Sys未定义的解决方法
    python搭建简易服务器
    STL源码剖析读书笔记第3章
    mongodb 查找 排序 索引 命令
    STL源码剖析读书笔记第2章
    词排序
    关于淘宝直通车优化的一点感悟
  • 原文地址:https://www.cnblogs.com/LQBlog/p/6064542.html
Copyright © 2011-2022 走看看