• MySQL如何解决幻读?


    • SERIALIZABLE 串行化
    • MVCC + Next-Key Lock

    幻读:

      幻读指的是一个事务在进行一次查询之后发现某个记录不存在,然后会根据这个结果进行下一步操作,此时如果另一个事务成功插入了该记录,那么对于第一个事务而言,其进行下一步操作(比如插入该记录)的时候很可能会报错。从事务使用的角度来看,在检查一条记录不存在之后,其进行插入应该完全没问题的,但是这里却抛出主键冲突的异常。

    简单来说:事务A的两次读之间有其他事务写操作,比如事务A统计年龄 > 30,当A两次读数据之间其他事务新添加了记录,所以事务A第二次读取到的数据突然多了一个,仿佛出现了幻觉一般,这就是一种幻读

    串行化:

    • 事务在读操作时,先加表级别的共享锁,直到事务结束才释放
    • 事务在写操作时,先加表级别的排它锁,直到事务结束才释放

     串行化锁定了整张表,幻读不存在的!!! 

    --------------------------------------------------------------------------------------------------------------------------------------------------------------------

    多版本并发MVCC:

    InnoDB 的 MVCC, 是 通过 在 每 行 记录 后面 保存 两个 隐藏 的 列 来 实现 的。 这 两个 列, 一个 保存 了 行的 创建 时间, 一个 保存 行的 过期 时间( 或 删除 时间)。 当然 存储 的 并不是 实际 的 时间 值, 而是 系统 版 本号( system version number)。 每 开始 一个 新的 事务, 系统 版本 号 都会 自动 递增。 事务 开始时 刻 的 系统 版 本号 会 作为 事务 的 版 本号, 用来 和 查询 到 的 每 行 记录 的 版本 号 进行 比较。

    MVCC 只在 REPEATABLE READ 和 READ COMMITTED 两个 隔离 级别 下 工作。

    MVCC快照读需满足条件:

    1. InnoDB 只 查找 版本 早于 当前 事务 版本 的 数据 行( 也就是, 行的 系统 版本 号 小于 或 等于 事务 的 系统 版 本号), 这样 可以 确保 事务 读 取的 行, 要么 是在 事务 开始 前 已经 存在 的, 要么 是 事务 自身 插入 或者 修 改过 的。
    2. 行的 删除 版本 要么 未定义, 要么 大于 当前 事务 版 本号。 这可 以 确保 事务 读取 到 的 行, 在 事务 开始 之前 未被 删除。

    MVCC解决了基于快照读下的幻读,事务 读 取的 行, 要么 是在 事务 开始 前 已经 存在 的, 要么 是 事务 自身 插入 或者 修 改过 的。

    并不会读到其他事务的写操作 !!!

    但是MVCC无法解决当前读下的幻读。

    当前读:

    select * from tb where ? lock in share mode;
           select * from tb where ? for update;

    for update:IX锁(意向排它锁),即在符合条件的rows上都加了排它锁

    lock in share mode:是IS锁(意向共享锁),即在符合条件的rows上都加了共享锁

    排它锁:X锁、 写锁,事务A对一个资源加了X锁后只有A本身能对该资源进行读和写操作,其他事务对该资源的读和写操作都将被阻塞,直到A释放锁为止  

    共享锁:S锁、 读锁, 事务A锁定的数据其他事务可以共享读该资源,但不能写,直到事务A释放

    InnoDB 默认开启间隙锁,  innodb_locks_unsafe_for_binlog参数表示 是否禁用Gap Lock间隙锁,默认值0 ,启动间隙锁

    测试时,设置参数为1 禁用间隙锁,演示幻读场景

    SHOW VARIABLES LIKE '%innodb_locks_unsafe_for_binlog%'

    start transaction;
    select * from tb where id>100 for update;
    update tb set product_num=product_num-1 where id>100;

    由于Mysql Server会针对update和delete操作里面的where条件查找满足条件的记录

    (查找的不是快照)然后Innodb引擎会返回的满足条件的加锁记录,

    当其他事务进行Insert 操作后,进行一次当前读时,就会读到其他事务 Insert 记录,可以明显的发现其会导致幻读

    -------------------------------------------------------------------------------------------------------------------------------------------------------


    Next-Key Lock是Gap Lock(间隙锁)和Record Lock(行锁)的结合版,都属于Innodb的锁机制

    select * from tb where id>100 for update;
    1. 主键索引 id 会给 id=100 的记录加上 record行锁
    2. 索引 id 上会加上 gap 锁,锁住 id(100,+无穷大)这个范围

    其他事务对  id>100 范围的记录读和写操作都将被阻塞

    插入 id=1000的记录时候会命中索引上加的锁会报出事务异常;


    Next-Key Lock会确定一段范围,然后对这个范围加锁,保证A在where的条件下读到的数据是一致的,因为在where这个范围其他事务根本插不了也删不了数据,都被Next-Key Lock锁堵在一边阻塞掉了。

    施瓦茨(Baron Schwartz); 扎伊采夫(Peter Zaitsev); 特卡琴科(Vadim Tkachenko). 高性能MySQL(第3版)

  • 相关阅读:
    静态(static)代码块、构造代码块、构造函数、父类子类执行顺序
    Java基本特征
    下列哪项不属于jdk1.6垃圾收集器?
    Model-View-Controller(MVC) is an architectural pattern that frequently used in web applications. Which of the following statement(s) is(are) correct?
    ServletConfig对象详解
    ServletConfig接口默认是哪里实现的?
    eclipse根据父类打开子类快捷键
    IDE:Eclipse查看Servlet源码
    IDE:Eclipse查看接口实现类快捷键
    Qt探索之路——多线程实现方法
  • 原文地址:https://www.cnblogs.com/JMrLi/p/12705188.html
Copyright © 2020-2023  润新知