暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

数据库事物隔离级别

阿哲是哲学的哲 2021-02-05
621

mysql 事物篇

一. 事物的定义

    以一种可控的方式来访问数据库资源的一组操作, 事物应该满足 ACID 原子性 一致性 隔离性 永久性

  • 原子性: 要么全部成功 要么全部失败

  • 一致性: 执行前后, 整体结果的一致的, A转100到B  A要扣去100 B要增加100  执行前后两个账户总和是一致的!

  • 隔离性: 主要是针对并发时, 多个事务之间应该有隔离程度. 事物隔离度越高, 系统并发性越差, 数据一致性越能保证. 总的来说, 一致性的考虑应该优于系统性能

    对于数据库来说, 不一定支持所有的事级别, 默认的事物级别也不同. 例如Oracle 只支持 Read Committed 和 Serializable . EJB Spring jdbc 可以指定四种隔离级别, 但是最终使用什么级别还是得根据使用的数据库来决定.

  • 持久性: 事物一旦提交成功, 那么本次操作将被记载不可逆转.

二. 四种事物隔离级别

主要面临的问题

  • 脏读 (Dirty Read)会读取到别的事物尚未提交的数据, 如果别的事物回滚, 那么自身事物将会读取到脏数据 (开启读已提交解决)
  • 不可重复读(Non-repeatable Read)  不可重复读读取是值同一个事物在读取同一条数据的时候, 多次读取到的结果都不一样
    例如: A事物读取到 B事物尚未开始更新前是1, B事物完成更新后了是2 , A读取的都是未提交的事物, 但是结果都不同 (开启可重复读解决)
  • 幻读(Phantom Read) 在查询整体结果集的时候, 两次读取的结果不一样.   也就是针对多条数据的读取结果不一致 (间隙锁或串行化解决)
间隙锁: 对某个结果集的查询范围进行加锁, 避免全表锁

对应的解决办法 四种隔离级别

  1. Read committed(读提交)  隔离性最低的策略 , 三种问题都会出现
  2. Read Uncommitted (读未提交) 大多数数据库采用的策略 ,读取已提交的数据, 可避免 脏读
  3. Repeatable Read (可重复读 Mysql使用MVCC) 保证在本次事物内, 针对一条数据, 怎么读取都是一致的. 但是针对整体的数据集就没法保证了(无法避免幻读)
  4. Servilazible 串行化 最严格的级别 , 每个事物都是串行化依次进行, 可避免所有问题, 但是性能最差 . 

三. MVCC底层实现

1. 问题: 读已提交的级别下出现不可重复读

    大多数数据库默认在不同事物下, 读取都是读取已提交的数据.

    当对同一条数据重复读取两次时, 中间对此数据进行了修改, 则前后两次读取(读取已提交)的结果将会出现不同, 这就是不能对同一条事物重复读取问题.

2. 解决思路: 目的是为了确保同一事务的多个实例在并发读取数据时, 能看到同样的数据.

  • 解决方案1:LBCC 采用读写锁思想进行读写分离(即串行化), 保证当前事物如果对本条数据进行加锁, 则其他事物无法进行修改。

  • 解决方案2:Mysql-innoDB的解决方式: - MVCC 多版本并发控制 MVCC使得数据库读不会对数据加锁,普通的SELECT请求不会加锁,提高了数据库的并发处理能力
        MVCC只是一个概念而不是具体的实现, 其核心理念就是数据快照,不同的事务访问不同版本的数据快照,从而实现不同的事务隔离级别。

3. InnoDB 的MVCC实现

    InnoDB 的 MVCC 通过在每行记录后面保存两个隐藏的列, 一列存行事物ID, 一列存行回滚指针. 每个事物只要影响到此行的话, 都会将当前事物id 放入影响行的事物id列中

    这样我们就能通过事物id来比较当前行的最新事物状态 同时也会将旧记录叠加到一个undo log 版本链中

4. 回滚段 undo log

MVCC 的实现, 通过undo log 来回溯本次事物可见的版本.

根据行为的不同,undo log分为两种:insert undo log 和 update undo log

insert undo log

    在inset 操作时, 将会产生一条 undo log.  由于插入操作对其他事物本来就是不可见的, 所以这条记录将会在事物提交后马上删去

update undo log

    当执行 delete 和 update 操作的时候将会产生update undo log. 这种类型的undo log 会被MVCC使用, 所以提交后并不会马上删除, 会有一个purge线程单独进行回收

    update 的时候, 首先会进行排它锁进行加锁, 在对行进行复制到undo log 更新事物id和回滚指针再释放锁

    为了保证并发的操作, 每条语句都有自己独立的undo log回滚组, 每次事物要对某行进行update都会进行行锁保证当前行只会有一个事务进行产生update undo log的产生

    多个事务参与过后, 就会形成一条有顺序的版本链

5. ReadView 活跃事物列表

    读未提交:直接读最新版本的数据, serializable串行化直接加悲观锁. 而对于 读已提交和可重复度的级别, 则需要使用ReadView来判断当前事物的可见版本是哪一个

    ReadView 中主要包含当前系统中还有哪些活跃的读写事务,把它们的事务id放到一个列表中,我们把这个列表命名为为m_ids(如下图)

判断当前事物可见性

  • 如果被访问版本的 trx_id 属性值小于 m_ids 列表中最小的事务id,表明生成该版本的事务在生成ReadView 前已经提交,所以该版本可以被当前事务访问。
  • 如果被访问版本的 trx_id 属性值大于 m_ids 列表中最大的事务id,表明生成该版本的事务在生成ReadView 后才生成,所以该版本不可以被当前事务访问。
  • 如果被访问版本的 trx_id 属性值在 m_ids 列表中最大的事务id和最小事务id之间, 那就需要判断一下 trx_id 属性值是不是在 m_ids 列表中如果在,说明创建 ReadView 时生成该版本的事务还是活跃的,该版本不可以被访问;如果不在,说明创建 ReadView 时生成该版本的事务已经被提交,该版本可以被访问。

READ COMMITTED 每次读取数据前都生成一个ReadView快照,记录此刻的ReadView

  • ReadView快照 最小值还小的trx_id 是可见的, 意味着这条undo log之前的事物了
  • ReadView快照 记录中存在trx_id 则不可见, 意味着这条undo log的事物还未提交

REPEATABLE READ 在事务开始后 第一次读取数据时生成一次ReadView快照, 此后一直沿用次快照

  • ReadView快照 最小值还小的trx_id 是可见的, 意味着在第一次查询开始前就已经提交了
  • ReadView快照 记录中存在trx_id 则不可见, 意味着在第一次查询开始的时候这条undo log的事物还未提交

总结: 每次读取一次生成一次ReadView快照的时候, 就会定格这一刻的所有活跃事物, 追溯undo log 中的trx_id就能判断版本链中的可见性了

  • 对于 READ COMMITTED 每次查询都重新生成快照, 意味着每次都读取最新提交的数据;
  • 对于 REPEATABLE READ在第一次读取的时候生成, 意味着只读取一开始完成事物的数据;

文章转载自阿哲是哲学的哲,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论