《凤凰架构》一书中对事务的隔离级别以及事务的定义很清晰
https://www.cnblogs.com/suBlog/p/16592859.html
总结
写锁:排他锁,其他事务不能写入数据,也不能施加读锁(可读,但是不可加读锁)
读锁:共享锁,多个事务可以同时施加读锁,但是其他事务不能写入数据
范围锁:不能修改范围内已有的数据,也不能对这个范围新增或删除数据
事务的隔离级别由高到低
可串行化:对所有读写数据的操作全部加上 写锁、读锁、范围锁
可重复读:只有读锁和写锁,但是没有范围锁。
问题:幻读--同一事务两个相同的查询范围得到了不同的结果。
因为更改了这个范围内的数据
读已提交:比起可重复读缺乏贯穿整个事务的读锁,select 后读锁立马释放了。
问题:不可重复读--同一事务对同一行数据的两次查询得到了不同的结果。
因为期间有事务2把这行数据改了,而事务1第一次读完读锁就释放了,使得事务2可以修改数据并提交了事务,事务1再读第二次时数据就变了。如果有贯穿整个事务的读锁的话,事务1两次都读完了,事务1提交后读锁释放 事务2才可以改数据,事务1两次读到的就是一样的。
读未提交:完全不加读锁
问题:脏读--读到了另一个事务未提交的数据。
事务2更改了数据,事务还没提交,持有写锁。但是因为没有读锁,所以事务1可以读到这个被更改的加了写锁但还没提交的数据(加了写锁可读但不可加读锁)
事务是在 MySQL 引擎层实现的,我们常见的 InnoDB 引擎是支持事务的。InnoDB 引擎通过什么技术来保证事务的这四个特性的呢?
- 持久性(Durability):事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失。是通过 redo log (重做日志)来保证的;
- 原子性(Atomicity):一个事务中的所有操作,要么全部完成,要么全部不完。通过 undo log(回滚日志) 来保证的;
- 隔离性(Isolation):允许多个并发事务同时对其数据进行读写和修改的能力。 MVCC(多版本并发控制) 或锁机制来保证的;
- 一致性(Consistency):是事务的目标,通过 持久性+原子性+隔离性 来保证;
不同的数据库厂商对 SQL 标准中规定的 4 种隔离级别的支持不一样,有的数据库只实现了其中几种隔离级别,我们讨论的 MySQL 虽然支持 4 种隔离级别,但是与SQL 标准中规定的各级隔离级别允许发生的现象却有些出入。
MySQL 在「可重复读」隔离级别下,可以很大程度上避免幻读现象的发生(注意是很大程度避免,并不是彻底避免),所以 MySQL 并不会使用「串行化」隔离级别来避免幻读现象的发生,因为使用「串行化」隔离级别会影响性能。
MySQL InnoDB 引擎的默认隔离级别虽然是「可重复读」,但是它很大程度上避免幻读现象(并不是完全解决了,详见这篇文章 (opens new window)),解决的方案有两种:
- 针对快照读(普通 select 语句),是通过 MVCC 方式解决了幻读,因为可重复读隔离级别下,事务执行过程中看到的数据,一直跟这个事务启动时看到的数据是一致的,即使中途有其他事务插入了一条数据,是查询不出来这条数据的,所以就很好了避免幻读问题。
- 针对当前读(select ... for update 等语句),是通过 next-key lock(记录锁+间隙锁)方式解决了幻读,因为当执行 select ... for update 语句的时候,会加上 next-key lock,如果有其他事务在 next-key lock 锁范围内插入了一条记录,那么这个插入语句就会被阻塞,无法成功插入,所以就很好了避免幻读问题。
MVCC 是怎么样工作的?
以下是 Read View 中的四个字段
Read View 有四个重要的字段:
- m_ids :指的是在创建 Read View 时,当前数据库中「活跃事务」的事务 id 列表,注意是一个列表,“活跃事务”指的就是,启动了但还没提交的事务。
- min_trx_id :指的是在创建 Read View 时,当前数据库中「活跃事务」中事务 id 最小的事务,也就是 m_ids 的最小值。
- max_trx_id :这个并不是 m_ids 的最大值,而是创建 Read View 时当前数据库中应该给下一个事务的 id 值,也就是全局事务中最大的事务 id 值 + 1;
- creator_trx_id :指的是创建该 Read View 的事务的事务 id。
聚簇索引记录中的两个隐藏列。
对于使用 InnoDB 存储引擎的数据库表,它的聚簇索引记录中都包含下面两个隐藏列:
- trx_id,当一个事务对某条聚簇索引记录进行改动时,就会把该事务的事务 id 记录在 trx_id 隐藏列里(最新的已经提交了的事务id);
- roll_pointer,每次对某条聚簇索引记录进行改动时,都会把旧版本的记录写入到 undo 日志中,然后这个隐藏列是个指针,指向每一个旧版本的版本链,于是就可以通过它找到修改前的记录。
在创建 Read View 后,我们可以将索引记录中的 trx_id 划分这三种情况:
一个事务去访问记录的时候,除了自己的更新记录总是可见之外,总结一下就是:
- 索引记录上的当前版本在创建 readView 时已经提交事务,可见。将聚簇索引记录上的当前版本值 trx_id 与 readView 中的字段比,包括以下两种情况:
- 小于 min_trx_id 的
- 在 min_trx_id 和 max_trx_id 之间,但是不存在于 m_ids 之中的(不在创建 readView 时还活跃的事务中)
- 索引记录上的当前版本在在创建 readView 时还没提交事务(或说活跃着),不可见;将聚簇索引记录上的当前版本值 trx_id 与 readView 中的字段比,包括以下两种情况:
- 大于 max_trx_id 的
- 在 min_trx_id 和 max_trx_id 之间,且存在于 m_ids 之中的(在创建 readView 时还活跃的事务中)
这种通过「版本链」来控制并发事务访问同一个记录时的行为就叫 MVCC(多版本并发控制)。
MVCC 怎样实现可重复读和读已提交隔离级别?
有锁化时它们的区别是 「读已提交」没有贯穿整个事务的读锁,而「可重复读」有。对于无锁化的 MVCC 实现(select 时是无锁的),就在于创建新 Read View 的时机不同:
- 「读已提交」隔离级别是在每个 select 都会生成一个新的 Read View,也意味着,事务期间的多次读取同一条数据,前后两次读的数据可能会出现不一致,因为可能这期间另外一个事务修改了该记录,并提交了事务。
- 「可重复读」隔离级别是启动事务时生成一个 Read View,然后整个事务期间都在用这个 Read View,这样就保证了在事务期间读到的数据都是事务启动前的记录。
标签:事务,隔离,Read,MVCC,trx,读锁,mysql,id From: https://www.cnblogs.com/suBlog/p/17366862.html