• Mysql的事务隔离


    起源

    当多个事务同时执行时,可能会产生脏读、幻读、不可重复读等。为了解决这些问题,所以有了隔离级别。

    脏读:事务还没有提交,就被其他事务读到修改值;

    幻读:比如你执行update t set field1=newValue where field2=xxxx时,应该是满足field2=xxxx的行都被修改了,结果发现有一行没被改,因为这时候有个事务在插入了一行field2=xxxx field1=otherValue;

    不可重复读:是指在一个事务内,多次读同一数据。在这个事务还没有结束时,另外一个事务也访问该同一数据。那么,在第一个事务中的两 次读数据之间,由于第二个事务的修改,那么第一个事务两次读到的的数据可能是不一样的。这样就发生了在一个事务内两次读到的数据是不一样的,因此称为是不 可重复读。例如,一个编辑人员两次读取同一文档,但在两次读取之间,作者重写了该文档。当编辑人员第二次读取文档时,文档已更改。原始读取不可重复。如果 只有在作者全部完成编写后编辑人员才可以读取文档,则可以避免该问题。

    不可重复读的重点是修改 :
    同样的条件,你读取过的数据, 再次读取出来发现值不一样了
    幻读的重点在于新增或者删除
    同样的条件 ,   第 1 次和第 2 次读出来的记录数不一样

    mysql支持多种隔离级别,默认是可重复度

    读未提交

    其实就是没有隔离。未提交读隔离级别是最低的隔离级别,该级别不涉及到任何数据库锁,会产生脏读的情况,脏读是指如果一个事务的更改还未提交,其他数据库的会话就读了该更改数据。

    读提交

    读提交是oracle的默认隔离级别,所以如果做数据迁移,需要注意隔离级别要保持一致。当一个事务所作修改还没提交时,这些修改对其他事务是不可见的。

    可重复读

    Mysql的默认隔离级别。可重复读指的是在一个事务开启后,可能会对某一个变量重复读,那么多次读写的值是不变的。

    串行化

    最严格的隔离级别,用读写锁实现,一个事务对某些行进行读写会阻塞其他事务的读写。

    事务隔离的实现

    想知道事务隔离是如何实现的,先要了解视图(ReadView)的概念。

    ReadView是某一个时间点,事务执行状态的一个快照(snapshot),可以用来判断事务的可见性。ReadView的基本结构如下:

    ReadView {
        creator_trx_id
        low_limit_id
        up_limit_id
        ids
        ...
    }

    creator_trx_id 创建这个ReadView的事务ID,也就是本事务ID

    low_limit_id 所有事务ID大于或等于low_limit_id对当前事务都不可见

    up_limit_id 所有事务ID严格小于up_limit_id的事务对当前事务可见 

    ids 未提交的事务ID列表

    可见性的判断

     事务通过当前事务(或SQL语句,取决于隔离级别)的ReadView来判断一个事务id的操作是否对当前事务可见。

    IsVisible(trx_id)
        if (trx_id == creator_trx_id)     // 当前事务
            return true;
        else if (trx_id < up_limit_id)    // ReadView创建时, 事务已提交
            return true;
        else if (trx_id >= low_limit_id)  // ReadView创建时,事务还未被创建
            return false;
        else if (trx_id is in m_ids)  // ReadView创建时,事务正在执行,但未提交
            return false
        else                          // ReadView创建时, 事务已提交
            return true;

    以可重复读为例,了解一下隔离实现。在mysql中,update操作都会记录一条回滚操作。DB记录好最新的值,然后通过回滚操作,得到某个视图想要的版本的信息。

    比如数据从1被改为2,3,4。那么回滚日志里就会记录下如下信息。

     当前值是4,其实在记录4这条信息的时候,还会记录不同回滚日志,不同的时刻启动的事务会有不同的read view就被以日志形式记录起来了。同一个信息在系统中可以存在多个版本,这就是多版本并发控制(MVCC)。(其实我暂时也不太懂mvcc,后续继续学习在补充吧。)这时候又有一个事务将4改成5,但是和read view A, B, C是不冲突的。

    可见,在可重复读的情况下,读不加锁,只有写才加锁,读写互不阻塞,并发度相对于可串行化级别要高,但会有Write Skew异常。

    事务在开始时创建一个ReadView,当读一条记录时,会遍历版本链表,通过当前事务的ReadView判断可见性,找到第一个对当前事务可见的版本,读这个版本。

    那么回滚日志什么时候删除呢,总不能一直存着吧。当不再用到这个视图时会被删除。系统会判断是否还需要这条回滚日志的。

    什么时候才不需要了呢?就是当系统里没有比这个回滚日志更早的read view的时候。

    比如可重复读,因为我的事务还没结束,所以我的readview是不会更新的,readview结构体也不会变化,所以别人始终不可见,别人也就是怎么读都是一个值,我怎么读别人也是一个值(我也只是一个事务啊,事务id没有变化)。但是read-commited,由于每句sql开始都会创建一个readview,所以,事务执行期间,一旦有其他的事务A提交了A的修改的行,等我下一句sql开始时就能感知到了(虽然我的事务id没有变,但是我的read view刷新了,结构体里的数据变了),不用等到下次事务创建时才能见到。

  • 相关阅读:
    @EnableTransactionManagement的使用
    Spring事务管理之几种方式实现事务
    instr和like的使用区别
    linux查看服务安装目录redis
    struts2的结果类型
    ajax 的 get 方式
    数据库隔离级别
    数据库隔离级别
    input from 表单提交 使用 属性 disabled=&quot;disabled&quot; 后台接收不到name=&quot;username&quot;的值
    Path Sum
  • 原文地址:https://www.cnblogs.com/howo/p/11542886.html
Copyright © 2020-2023  润新知