Mysql事务篇——Day02
- 事务有哪些特性
- 并发事务引发的问题
- 脏读
- 不可重复读
- 幻读
- 事务隔离级别
- Read View 在 MVCC里如何工作
事务有哪些特性
事务是依赖MySQL的储存引擎是实现的,我们常见的Innodb引擎就是支持事务的。
不过并不是所有的存储引擎都可以支持事务,比如MySQL原生的MyISAM就不支持事务,也正是这样,所以大多数的MySQL引擎用的都是innodb。
事务看起来很简单,但是要实现事务必须遵循4个特性:
- 原子性
一个事务中的所有操作,要么全部完成,要么全部不完成,不会结束在中间的某一步,而且事务在执行的过程中发生错误,会回滚到事务开始前的状态,就行这个事务从哥俩没有执行过一样。 - 一致性
指事务操作前后,数据满足完整性约束,数据库保持一致性,比如转账,用户A和用户B分别在银行存有800和600元,总共1400元,不管如何转账,总钱数必须是1400。 - 隔离性
数据库允许多个并发事务同时及对其数据进行读写和修改的能力,隔离性可以防止多个事务并发执行时由于交叉执行而导致数据不一致,因为多个事务同时使用相同的数据时,不会相互干扰,每个事务都有自己一个空间,对其他并发事务是隔离的。 - 持久性
事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失数据。
- 事务的持久性是通过redo log来保证的
- 事务的原子性是通过undo log来保证的
- 事务的隔离性是通过Mvcc 或者锁机制来保证的
- 事物的一致性是通过持久性+原子性+隔离性来保证的
并发事务引发的问题
Mysql 服务端是允许多个客户端连接的,这意味着MySQL会有同时处理多个事务情情况。
那么在同时处理多个事务的时候就可能出现脏读、不可重复读、幻读的问题。
脏读
如果一个事务读到了另一个事务未提交数据修改过的数据,就意味着发生了脏读的现象。
假设A和B两个事务同时在处理,事务A先从数据库中读取小林的余额数据,然会再执行更新操作,如果这时候A还没有提交事务,而此时正好B也从数据库中读取小c的余额数据,那么事务B读取到的余额数据就是事务A更新后的数据,即使没有提交事务。
因为事务A是还没有提交事务的,也就是它随时可能发生回滚操作,如果上面这种情况出现了事务A发生了回滚,那么事务B方才得到的数据库就是脏数据,这种现象就叫做脏读。
不可重复读
在一个事务内多次读取同一个数据,如果出现前后两次数据不一致的现象,就意味着发生了不可重复读。
假设A和B两个事务同时在处理,事务A开始先从数据库读取小c余额,然后继续执行代码处理逻辑,在这个过程中如果事务B更新了这条数据,并且提交了事务,那么事务A再次读取该数据的时候,就会发现亲后两次读取到的数据是不一致的,这种现象就叫做不可重复读。
幻读
在一个事务内多次查询某个符合查询条件的记录数量,如果前后两次查询到的记录数不一样,就意味着出现了幻读现象。
假设有 A 和 B 这两个事务同时在处理,事务 A 先开始从数据库查询账户余额大于 100 万的记录,发现共有 5 条,然后事务 B 也按相同的搜索条件也是查询出了 5 条记录。
接下来,事务 A 插入了一条余额超过 100 万的账号,并提交了事务,此时数据库超过 100 万余额的账号个数就变为 6。
然后事务 B 再次查询账户余额大于 100 万的记录,此时查询到的记录数量有 6 条,发现和前一次读到的记录数量不一样了,就感觉发生了幻觉一样,这种现象就被称为幻读。
事务隔离级别
上面我们说到,多个是事务并发执行可能会遇到的问题,这些问题会对事务的一致性产生不同的影响。
- 脏读:读到其他事务未提交的数据
- 不可重复读:前后读取到的数据不一致
- 幻读:前后读取到的记录数不一致
SQL标准提供了四种隔离级别来规避这些问题:
- 读未提交(read uncommitted),指一个事务还没提交时,它做的变更就能被其他事务看到;
- 读提交(read committed),指一个事务提交之后,它做的变更才能被其他事务看到;
- 可重复读(repeatable read),指一个事务执行过程中看到的数据,一直跟这个事务启动时看到的数据是一致的,MySQL InnoDB 引擎的默认隔离级别;
- 串行化(serializable );会对记录加上读写锁,在多个事务对这条记录进行读写操作时,如果发生了读写冲突的时候,后访问的事务必须等前一个事务执行完成,才能继续执行;
针对不同的隔离级别,并发事务时可能发生的现象也会不同。
也就是说:
在读未提交隔离级别下,可能发生脏读、不可重复读和幻读现象;
在读提交隔离级别下,可能发生不可重复读和幻读现象,但是不可能发生脏读现象;
在可重复读隔离级别下,可能发生幻读现象,但是不可能脏读和不可重复读现象;
在串行化隔离级别下,脏读、不可重复读和幻读现象都不可能会发生。
所以,要解决脏读现象,就要升级到读提交以上的隔离级别;要解决不可重复读现象,就要升级到可重复读的隔离级别,要解决幻读现象不建议将隔离级别升级到串行化。
不同的数据库厂商对 SQL 标准中规定的 4 种隔离级别的支持不一样,有的数据库只实现了其中几种隔离级别,我们讨论的 MySQL 虽然支持 4 种隔离级别,但是与SQL 标准中规定的各级隔离级别允许发生的现象却有些出入。
MySQL 在可重复读隔离级别下,可以很大程度上避免幻读现象的发生(注意是很大程度避免,并不是彻底避免),但是 MySQL 并不会使用串行化隔离级别来避免幻读现象的发生,因为使用串行化隔离级别会影响性能。
MySQL InnoDB 引擎的默认隔离级别虽然是可重复读,但是它很大程度上避免幻读现象,那么如何让解决幻读问题呢?:
- 针对快照读(普通 select 语句),是通过 MVCC 方式解决了幻读,因为可重复读隔离级别下,事务执行过程中看到的数据,一直跟这个事务启动时看到的数据是一致的,即使中途有其他事务插入了一条数据,是查询不出来这条数据的,所以就很好了避免幻读问题。
- 针对当前读(select … for update 等语句),是通过 next-key lock(记录锁+间隙锁)方式解决了幻读,因为当执行 select … for update 语句的时候,会加上 next-key lock,如果有其他事务在 next-key lock 锁范围内插入了一条记录,那么这个插入语句就会被阻塞,无法成功插入,所以就很好了避免幻读问题。
这四种隔离级别是如然后实现的呢?
- 对于读未提交隔离级别的事务来说,因为可以读取到未提交事务修改的数据,所以直接读取最新数据就可以;
- 对于串行化隔离级别的事务来说,通过加读写锁的方式来避免并行访问;
- 对于读已提交和可重复读隔离级别的事务来说,他们是通过Read View来实现的,他们的区别在于创建Read View 的时机不同,大家可以把Read View 理解成以恶搞数据快照,就像相机拍照,读已提交是在每个语句执行前都会重新生成一个Read View ,而可重复读是在启动时事务时生成一个Read View,然后整个事务期间都是用这个Read View。
注意:执行开始事务命令,并不意味着启动了十五,在MySQL有两种开启事务的方式,
- begin / start transaction
- start / transcation with consistent snapshot
这两种事务启动的方式,启动时机是不同的,第一种执行命令后只有在执行了增删改查操作后才会穷事务,第二种是直接启动。
Read View 在 MVCC里如何工作
我们需要知道两个知识,第一,Read View四个字段的作用;第二,聚簇索引记录中两个和事务有关的隐藏列。
什么是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。
知道了 Read View 的字段,我们还需要了解聚簇索引记录中的两个隐藏列。
- trx_id,当一个事务对某聚簇索引记录进行改动时,就会把该事务id记录在trx_id隐藏列中;
- roll_pointer,每次对某条聚簇索引记录进行改动时,就会把旧版本的记录写入到uodo日志中,然后够这个隐藏列是一个指针,指向上一个旧版本记录,可以通过它找到修改前的记录。
在创建Read View后,我们可以将记录中的trx_id划分为三种情况:
一个事务去访问记录的时候,除了自己的更新记录总是可见之外,还有几种情况:
- 如果记录的trx_id小于Read View中的min_trx_id值,表示这个版本的记录是在创建Read View之前已经提交的事务生成的,所以该版本的记录对当前事务可见。
- 如果该版本记录的trx_id大于等用途Read View中的max_trx_id值,表示这个版本的记录是在创建Read View后才启动的事务生成的,所以该版本的额记录对当前事务不可见。
- 如果记录的trx_id在Read View的min_trx_id和max_trx_id之间,需要判断trx_id是否在m_ids列表中:
- 如果记录的trx_id在m_ids列表中,表示生成该版本记录的事务依然活跃着(还没有提交事务),所以该版本的记录对当前事务不可见。
- 如果记录的trx_id不在m_ids列表中,表示生成该版本记录的事务已经被提交,所以该版本的记录对当前事务可见。
这种通过「版本链」来控制并发事务访问同一个记录时的行为就叫 MVCC(多版本并发控制)。