zoukankan      html  css  js  c++  java
  • MySQL中的MVCC

    MySQL中的MVCC

    MVCC的概念

    MVCC: Multi-Version Concurrency Control,即多版本并发控制.
    是乐观锁的一种实现方式.

    并发事务存在的问题:

    1. 更新丢失(Lost Update):多个事务同时更新同一行时,最后的更新会覆盖之前的更新。
    2. 脏读(Dirty Reads):一个事务对记录的未提交修改被其他事务读取到。
    3. 不可重复读(Non-Repeatable Reads):一个事务内多次查询相同记录结果不一致。
    4. 幻读(Phantom Reads):一个事务重新查询之前检索过的数据,发现出现新的数据。

    解决:

    • 加读写锁。
    • 一致性快照读(MVCC)。

    特点

    • 用来提高数据库高并发场景下的吞吐性能。
    • MySQL中InnoDB引擎支持MVCC。
    • 比加行锁效率高,开销低。
    • 读已提交(Read Committed)和可重复读(Repeatable Read)隔离级别下起作用。
    • 可以基于乐观锁悲观锁实现。
    • 使用行级锁(row_level_lock),而非行锁(innodb_row_lock).
    • 同一个事务能够看到数据一致的视图.
    • 事务开始的时间不同,看到相同表的数据可能不同.

    基本原理

    • 通过保留某个时间点的快照实现的.

    基本特征

    • 每行数据都存在一个版本,每次数据更新时都更新该版本.
    • 修改数据时复制当前版本的数据进行修改,各个事务之间互不影响.
    • 保存时比较版本号,成功(commit)则覆盖原记录,失败则放弃(rollback).

    InnoDB存储引擎MVCC实现策略

    细节:

    • 每一行保存两个隐藏列:当前行创建时版本号删除时版本号.
    • 版本号是系统版本号,每开始一个新事务,系统版本号自增.而事务的版本号为事务开始时的系统版本号.
    • 每个事务有自己的版本号.

    MVCC下的InnoDB的增删改查

    插入数据

    1. 设记录的版本号为当前事务的版本号。
    2. 向表中插入数据。
    3. create version设置为当前事务的版本号,delete version为空。

    更新操作

    1. 将旧的记录标记为已删除,delete version为当前事务版本号。
    2. 插入一行新的记录,create version为当前事务版本号,delete version为当前版本号。

    删除操作

    1. 将待删除的行的delete version设置为当前事务版本号。

    查询操作

    记录需满足两个条件:

    • delete version为空或者设置的版本号大于当前事务的版本号(即:删除操作发生在当前事务之后)
    • create version小于等于当前事务版本号(即:记录创建在当前事务之前)

    注:

    1. MVCC只适用于MySQL中的读已提交(Read Committed)和可重复读(Repeatable Read)。
    2. Read uncommitted存在脏读,即:读到未提交事务的数据行。
    3. 串行化是对表加锁。

    InnoDB MVCC 实现原理

    实现方式:

    • 每一行记录都有两个隐藏列:DATA_TRX_IDDATA_ROLL_PTR。(若没有主键,则还有一个隐藏主键)
    • DATA_TRX_ID:记录最近更新这条记录的事务ID(6字节)
    • DATA_ROLL_PTR:指向该行回滚段的指针,通过指针找到之前版本,通过链表形式组织(7字节)
    • DB_ROW_ID:行标识(隐藏单增ID),没有主键时主动生成(6字节)

    多事务并发操作数据

    特征:

    • 不同事务对同一行的更新操作产生多个版本
    • 通过回滚指针将这些版本链接成一条Undo Log链

    更新操作流程:

    1. 将待操作的行加排他锁
    2. 将该行原本的值拷贝到Undo Log中,DB_TRX_IDDB_ROLL_PTR保持不变。(形成历史版本)
    3. 修改该行的值,更新该行的DATA_TRX_ID为当前操作事务的事务ID,将DATA_ROLL_PTR指向第二步拷贝到Undo Log链中的旧版本记录。(通过DB_ROLL_PTR可以找到历史记录)
    4. 记录Redo Log,包括Undo Log中的修改。
    • INSERT操作:产生新的记录,其DATA_TRX_ID为当前插入记录的事务ID。
    • DELETE操作:软删除,将DATA_TRX_ID记录下删除该记录的事务ID,真正删除操作在事务提交时完成。

    一致性读的实现

    • RU隔离级别下 ==> 直接读取版本的最新记录。
    • SERIALIZABLE隔离级别 ==> 通过加锁互斥访问数据实现。
    • RC和RR隔离级别 ==> 使用版本链(ReadView,可读视图)

    RR下的ReadView生成

    特点:

    • 每个事务首次执行SELECT语句时,会将当前系统所有活跃事务拷贝到一个列表中生成ReadView。
    • 每个事务后续的SELECT操作复用其之前生成的ReadView
    • UPDATE,DELETE,INSERT对一致性读snapshot无影响。

    示例:事务A,B同时操作同一行数据

    • 若事务A的第一个SELECT在事务B提交之前进行,则即使事务B修改记录后先于事务A进行提交,事务A后续的SELECT操作也无法读到事务B修改后的数据。
    • 若事务A的第一个SELECT在事务B修改数据并提交事务之后,则事务A能读到事务B的修改。

    RC下的ReadView生成

    特点:

    • 每次SELECT执行,都会重新将当前系统中的所有活跃事务拷贝到一个列表中生成ReadView。
    • ReadView的组成:(当前活跃事务ID列表,称为m_ids)
      • 最小值为up_limit_id:最先开始的事务。
      • 最大值为low_limit_id:最后开始的事务。
    • ID越小,事务开始的越早;ID越大,事务开始的越迟。
    • 若被访问版本的trx_id小于up_limit_id == > 生成该版本的事务在ReadView生成前就已提交 == > 该版本可以被当前事务访问。
    • 若被访问版本的trx_id大于low_limit_id == > 生成该版本的事务在ReadView生成之后才提交 == > 该版本不可被当前事务访问 == > 通过Undo Log找到之前的版本重新判断。
    • 若被访问的版本在up_limit_idlow_limit_id之间 == > 需要判断trix_id是否在m_ids中存在 == > 若存在,则生成该版本的事务还在活跃,则该版本不可访问,可由Undo Log找到之前的版本进行重新判断;若不存在,则创建ReadView时该版本对应的事务已提交,可以访问该版本。
    • 找到记录后,还要判断delete_flag是否为true,若为true,则该记录已被删除,不返回;若为false,则记录可以返回。

    注:对于ID较大的事务较ID较小的事务先提交的情况,即事务发生晚但提交的早

    • RC的本质:每一条SELECT都可以看到其他已经提交的事务对数据的修改,只要事务提交,其结果都可见,与事务开始的先后顺序无关
    • RR的本质:第一条SELECT生成ReadView前,已经提交的事务的修改可见。

    参考:

  • 相关阅读:
    Flex中States的用法
    MAX脚本翻译教学
    WARN No appenders could be found for logger 解决
    解压版(绿色版)Tomcat配置
    Bootstrap入门
    什么时候用margin、padding
    简易的商品统计
    块级元素&行内元素
    不定宽元素水平居中
    JavaScript与表单交互(表单验证模型)
  • 原文地址:https://www.cnblogs.com/truestoriesavici01/p/13224749.html
Copyright © 2011-2022 走看看