6

数据库篇:mysql事务原理之MVCC视图+锁 - 潜行前行

 2 years ago
source link: https://www.cnblogs.com/cscw/p/16106338.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client
  • 数据库的事务特性
  • 数据并发读写时遇到的一致性问题
  • mysql事务的隔离级别
  • MVCC的实现原理
  • 锁和隔离级别

关注公众号,一起交流,微信搜一搜: 潜行前行

1 数据库的事务特性

  • 原子性:同一个事务里的操作是一个不可分割的,里面的 sql 要么一起执行,要不执行,是原子性
  • 隔离性:数据库系统提供一定的隔离机制,保证事务在不受外部并发操作影响的“独立”环境执行。这意味着事务处理过程中的中间状态对外部是不可见的
  • 一致性:在事务开始和完成时,数据约束都必须保持一致状态
  • 持久性:事务完成之后,它对于数据的修改是永久性的,即使出现系统崩溃也能够保持持久

2 数据并发读写时遇到的一致性问题

  • 脏读(针对未提交)
    • 两个事务同时进行,事务A修改了数据D,且事务A未提交,而事务B却可以读取到未提交的数据D,称之为脏读
  • 脏写
    • 两个事务同时尝试去更新某一条数据记录时,当事务A更新时,事务A还没提交,事务B就也过来进行更新,覆盖了事务 A 提交的更新数据,这就是脏写。一般要加锁解决
  • 不可重复读(针对已提交的 update)
    • 针对的是已经提交的事务修改的值,同时进行的其他事务给读取到了,事务内多次查询,多次读到的是别的已经提交的事务修改过的值,这就导致不可重复读
  • 幻读(针对已提交的 insert)
    • 事务读取到事务开始之后的插入数据,例如select * from table_user where id between 1 and 10,这条sql本应查出 1~9 的数据,id=10 此时不存在,之后其他事务再插入了一条 id=10 的记录。然后当前事务再次查询则会查出 10 条记录。这就是幻读
    • 和不可重复读的区别是,不可重复读的问题是读取最新的修改,幻读是读取到最新的插入数据

3 mysql事务的隔离级别

  • 读未提交(READ UNCOMITTED,RU):对应脏读,可以读取到最新未提交的修改
  • 读已提交(READ COMMITTED,RC):一个事务能读取另一个事务已经提交的修改。其避免了脏读,但仍然存在不可重复读和幻读问题
  • 可重复读(REPEATABLE READ,RR):同一个事务中多次读取相同的数据返回的结果是一样的。其避免了脏读和不可重复读问题,但幻读依然存在
  • 串行化读(SERIALIZABLE):事务串行执行。避免了以上所有问题

4 MVCC 的实现原理

MVCC 全称Multi-Version Concurrency Control,其好处是读不加锁,读写不冲突,并发性能好

MVCC 的 undo log 版本链

  • InnoDB中每行数据都有隐藏列,隐藏列中包含了本行数据的事务ID trx_id、指向 undo log 的 roll_pointer 指针
  • 基于undo log的版本链:前面说到每行数据的隐藏列中包含了指向 undo log 的指针 roll_pointer,而每条undo log 也会指向更早版本的undo log,从而形成一条版本链

readView

对于使用READ COMMITTEDREPEATABLE READ隔离级别的事务来说,都必须保证读到已提交事务修改过的记录,也就是说假如另一个事务修改了记录但尚未提交,是不能读取最新版本的记录的,其核心问题:需要判断 MVCC 版本链中的哪个版本是当前事务可见的。innodb 的解决方案 readView,readView 包含4个比较重要的属性

  • m_ids:在生成ReadView时,当前系统中活跃的读写事务 id 列表
  • min_trx_id:表示在生成ReadView时,当前系统中活跃的读写事务中最小的事务id,也就是m_ids中的最小值
  • max_trx_id:表示生成ReadView时系统中应该分配给下一个事务的 id 值
  • creator_trx_id:对应生成该ReadView 事务的id
readView 的访问步骤
  • 如果被访问版本的trx_id属性值与ReadView中的creator_trx_id值相同,表示当前事务在访问它自己修改过的记录,该版本可以被当前事务访问。
  • 如果被访问版本的trx_id属性值小于ReadView中的min_trx_id值,表明生成该版本的事务在当前事务生成ReadView前已经提交,所以该版本可以被当前事务访问。
  • 如果被访问版本的trx_id属性值在ReadViewmin_trx_idmax_trx_id之间,那就需要判断一下trx_id属性值是不是在m_ids列表中,如果在,说明创建ReadView时生成该版本的事务还是活跃的,该版本不可以被访问;如果不在,说明创建ReadView时生成该版本的事务已经被提交,该版本可以被访问
  • 如果被访问版本的trx_id属性值大于或等于ReadView中的max_trx_id值,表明生成该版本的事务在当前事务生成ReadView后才开启,该版本不可被当前事务访问。反之可见
  • 如果某个版本的数据对当前事务不可见的话,那就顺着版本链找到下一个版本的数据(undo log)。如果最后一个版本都不可见的话,那么就意味着该条记录对该事务完全不可见

读已提交和可重复读利用 ReadView 实现

  • 快照读:读取的是快照版本,也就是历史版本 readView 里的数据 ,普通的 SELECT 就是快照读
  • 当前读:读取的是最新版本,UPDATE、DELETE、INSERT、SELECT ... LOCK IN SHARE MODE、SELECT ... FOR UPDATE 是当前读,需要加锁
  • READ UNCOMMITTED:直接读取记录的最新版本就好
  • READ COMMITTED:每次读取数据前都生成一个ReadView
    • 针对当前读,RC 隔离级别保证对读取到的记录加锁 (记录锁),存在幻读现象
  • REPEATABLE READ:在第一次读取数据时生成一个ReadView
    • 针对当前读,RR 隔离级别保证对读取到的记录加锁 (记录锁),同时保证对读取的范围加锁,新的满足查询条件的记录不能够插入 (间隙锁),不存在幻读现象
    • RR 从严格意义上并没解决幻读。如果事务一开始先 update 一条看不见的数据(前面没有当前读操作),再查询,则会多查出这条记录,此时也是发生了幻读

5 锁和隔离级别

  • RC、RR、SERIALIZABLE 级别的隔离,当前读都会需要借助锁实现
  • MVCC 能实现多数情况避免幻读,但不能完全避免幻读的发生
  • RR 隔离级别需要先 select ... for update 加锁进行当前读操作,才能防止幻读
  • 对于SERIALIZABLE隔离级别的事务来说,InnoDB规定使用加锁的方式来访问记录

欢迎指正文中错误


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK