zoukankan      html  css  js  c++  java
  • 记一次事务隔离级别的学习和锁

    前几天自己在软件作坊写的程序没通过自己的压力测试.

      即使用了RR隔离级别,也会造成很多脏数据,账户余额负数,一次转账,多次收到的问题.

        后面改用serializable,负数不出现,但是还是有多次到账的情况,原因与自己使用了jfinal这种bean无状态的框架的原因.hibernate+spring事务一般不存在上述问题.

    ##事务的四种隔离级别
    在数据库操作中,为了有效保证并发读取数据的正确性,提出的事务隔离级别。我们的数据库锁,也是为了构建这些隔离级别存在的。

    隔离级别脏读(Dirty Read)不可重复读(NonRepeatable Read)幻读(Phantom Read)
    未提交读(Read uncommitted) 可能 可能 可能
    已提交读(Read committed) 不可能 可能 可能
    可重复读(Repeatable read) 不可能 不可能 可能
    可串行化(Serializable ) 不可能 不可能 不可能
    • 未提交读(Read Uncommitted):允许脏读,也就是可能读取到其他会话中未提交事务修改的数据
    • 提交读(Read Committed):只能读取到已经提交的数据。Oracle等多数数据库默认都是该级别 (不重复读)
    • 可重复读(Repeated Read):可重复读。在同一个事务内的查询都是事务开始时刻一致的,InnoDB默认级别。在SQL标准中,该隔离级别消除了不可重复读,但是还存在幻象读
    • 串行读(Serializable):完全串行化的读,每次读都需要获得表级共享锁,读写相互都会阻塞

      比较难以理解的是可重复读:对应解决不可重复读问题-一个事务中读到的数据不一致,就说这不可重复读,所以可以重复读到一致的数据.

    通过实例可以了解到innodb的rr是解决了幻读问题的.


    #一次封锁or两段锁?
    因为有大量的并发访问,为了预防死锁,一般应用中推荐使用一次封锁法,就是在方法的开始阶段,已经预先知道会用到哪些数据,然后全部锁住,在方法运行之后,再全部解锁。这种方式可以有效的避免循环死锁,但在数据库中却不适用,因为在事务开始阶段,数据库并不知道会用到哪些数据。
    数据库遵循的是两段锁协议,将事务分成两个阶段,加锁阶段和解锁阶段(所以叫两段锁)

      • 加锁阶段:在该阶段可以进行加锁操作。在对任何数据进行读操作之前要申请并获得S锁(共享锁,其它事务可以继续加共享锁,但不能加排它锁),在进行写操作之前要申请并获得X锁(排它锁,其它事务不能再获得任何锁)。加锁不成功,则事务进入等待状态,直到加锁成功才继续执行。
      • 解锁阶段:当事务释放了一个封锁以后,事务进入解锁阶段,在该阶段只能进行解锁操作不能再进行加锁操作。

    另一个问题是加锁:innodb有几种锁:行锁,表锁,共享锁(读锁),排他锁(写锁),还有意向锁.

      在rc模式中,假如筛选字段没有索引,按理说是会加表锁的,但在实际使用过程当中,MySQL做了一些改进,在MySQL Server过滤条件,发现不满足后,会调用unlock_row方法,把不满足条件的记录释放锁 (违背了二段锁协议的约束)。这样做,保证了最后只会持有满足条件记录上的锁,但是每条记录的加锁操作还是不能省略的。可见即使是MySQL,为了效率也是会违反规范的。(参见《高性能MySQL》中文第三版p181)


    美团的文章最后还讲了乐观锁与悲观锁,innodb是基于乐观锁的技术实现的(mvvc),数据库每行除了数据外,还记录了创建事务号,过期(删除)事务号.

     在可重读Repeatable reads事务隔离级别下:

    • SELECT时,读取创建版本号<=当前事务版本号,删除版本号为空或>当前事务版本号。
    • INSERT时,保存当前事务版本号为行的创建版本号
    • DELETE时,保存当前事务版本号为行的删除版本号
    • UPDATE时,插入一条新纪录,保存当前事务版本号为行创建版本号,同时保存当前事务版本号到原来删除的行

    然后还讲了快照读与当前读的区别

    基于mvvc(多版本并发控制)的读,多是快照读,而一些读语句或更新删除语句会触发"当前读",for update,lock in shared mode;

    但其实隔离级别定义的"读"是当前读,而MySQL为了效率引入快照读的概念.

    能不能说mvvc是对隔离级别的实现?

    ###写("当前读")
    事务的隔离级别中虽然只定义了读数据的要求,实际上这也可以说是写数据的要求。上文的“读”,实际是讲的快照读;而这里说的“写”就是当前读了。
    为了解决当前读中的幻读问题,MySQL事务使用了Next-Key锁。

    接下来介绍了gap锁,行锁,next-key锁,锁的是数据左右区间行,行锁防止别的事务修改或删除,GAP锁防止别的事务新增,行锁和GAP锁结合形成的的Next-Key锁共同解决了RR级别在写数据时的幻读问题。

    回到前头告诉我们为啥innodb的rr可以解决幻读.

    参考:https://tech.meituan.com/innodb-lock.html

  • 相关阅读:
    spring scheduled单线程和多线程使用过程中的大坑!!不看到时候绝对后悔!!
    在idea中配置 gitignore忽略文件(一)
    Cron表达式范例:每隔5秒执行一次:*/5 * * * * ?
    软件——protel 的pcb电路图制作
    【纪中受难记】——Day21:调整心态
    2019第十届蓝桥杯C/C++ B组省赛 —— 第二题:年号字串
    2019第十届蓝桥杯C/C++ B组省赛 —— 第三题:数列求值
    2019第十届蓝桥杯C/C++ B组省赛 —— 第三题:数列求值
    2019第十届蓝桥杯C/C++ B组省赛 —— 第一题:组队
    2019第十届蓝桥杯C/C++ B组省赛 —— 第一题:组队
  • 原文地址:https://www.cnblogs.com/imjamin/p/9248338.html
Copyright © 2011-2022 走看看