并行事务会引发什么问题?
同时处理多个事务的时候,就可能出现脏读(dirty read)、不可重复读(non-repeatable read)、幻读(phantom read)的问题。
1、脏读。如果一个事务「读到」了另一个「未提交事务修改过的数据」,就意味着发生了「脏读」现象.
2、不可重复读。在一个事务内多次读取同一个数据,如果出现前后两次读到的数据不一样的情况,就意味着发生了「不可重复读」现象。
3、幻读。在一个事务内多次查询某个符合查询条件的「记录数量」,如果出现前后两次查询到的记录数量不一样的情况,就意味着发生了「幻读」现象
事务有哪些特性?
事务的四大特性 : ACID
1、原子性(Atomicity): 一个事务中的所有操作,要么全部完成,要么全部不完成,不会结束在中间某个环节, 不会出现 a 扣钱了, b 没加钱。
2、一致性(Consistency):是指事务操作前和操作后,数据满足完整性约束,数据库保持一致性状态, 总钱数不会少
3、隔离性(Isolation): 数据库允许多个并发事务同时对其数据进行读写和修改的能力, 每个事务都有一个完整的数据空间,对其他并发事务是隔离的, 也就是 a 购买不会导致 b 购买
4、持久性(Durability):事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失
InnoDB 引擎通过什么技术来保证事务的这四个特性的呢?
1、原子性 : 通过undo log (回滚日志) 实现
2、隔离性 : 通过 MVCC (多并发版本控制) 或锁机制保证
3、持久性 : 通过 redo log (重做日志) 实现
4、一致性 : 通过原子性 + 隔离性 + 持久性实现
事务的隔离级别有哪些?
1、读未提交(read uncommitted),指一个事务还没提交时,它做的变更就能被其他事务看到, 可能发生脏读、不可重复读和幻读现象。
2、读提交(read committed),指一个事务提交之后,它做的变更才能被其他事务看到, 可能发生不可重复读和幻读现象
3、可重复读(repeatable read),指一个事务执行过程中看到的数据,一直跟这个事务启动时看到的数据是一致的**,可能发生幻读现象, 但是它很大程度上避免幻读现象, MySQL InnoDB 引擎的默认隔离级别。
4、串行化(serializable );会对记录加上读写锁,在多个事务对这条记录进行读写操作时,如果发生了读写冲突的时候,后访问的事务必须等前一个事务执行完成,才能继续执行。
这四种隔离级别具体是如何实现的呢?
1、读未提交 : 直接读取最新的数据。
2、串行化 : 读写锁。
3、读提交和可重复读 : 通过 Read View 来实现 , 区别在于生成 Read View 的时机不同,。
3-1、读提交是在「每个语句执行前」都会重新生成一个 Read View。
3-2、可重复读是「启动事务时」生成一个 Read View。
Read View 在 MVCC 里如何工作的?
m_ids(Read View中未提交事务ID列表) :指的是在创建 Read View 时,当前数据库中启动但是未提交的事务 id 列表,注意是一个列表。
min_trx_id(Read View中 未提交事务列表中事务id最小的一个) : 指的是在创建 Read View 时, 启动但是未提交的事务 id 列表中事务 id 最小的值。
max_trx_id(Read View中 未提交事务列表中预留下一个事务的ID值) :这个并不是 m_ids 的最大值,而是创建 Read View 时当前数据库中应该给下一个事务的 id 值,也就是全局事务中最大的事务 id 值 + 1。
creator_trx_id :指的是创建该 Read View 的事务的事务 id
聚簇索引记录中的两个隐藏列。
trx_id : 该事务的事务 id
roll_pointer : 这个隐藏列是个指针,指向每一个旧版本记录,可以通过它找到修改前的记录。
MVCC 工作过程
一个事务去访问记录的时候,除了自己的更新记录总是可见之外,还有这几种情况:
1、如果该记录的 trx_id 小于 Read View 的 min_trx_id, 说明该记录是在创建 Read View 之前的事务生成的, 可见。
2、如果该记录的 trx_id 大于 Read View 的 max_trx_id, 说明该记录是在创建 Read View 之后的事务生成的, 不可见。
3、如果记录的 trx_id 值在 Read View 的 min_trx_id 和 max_trx_id 之间, 需要判断 trx_id 是否在 m_ids 列表中
3-1、如果 trx_id 在 m_ids 列表中, 说明创建该记录的事务还未提交, 不可见。
3-2、如果 trx_id 不在 m_ids 列表中, 说明创建该记录的事务已经提交, 可见。
当前不可见的可以通过 roll_pointer 指针寻找 undo log 版本链找到事务开始时的数据。这种通过「版本链」来控制并发事务访问同一个记录时的行为就叫 MVCC(多版本并发控制)
MySQL 可重复读隔离级别,完全解决幻读了吗?
并没有,还是会发生幻读现象。通过串行读来避免。
当前读是如何避免幻读的?
MySQL 里除了普通查询是快照读,其他都是当前读(读提交),比如 update、insert、delete,这些语句执行前都会查询最新版本的数据,然后再做进一步的操作, 另外, select ... for update 这种查询语句是当前读,每次执行的时候都是读取最新的数据。
Innodb 引擎为了解决「可重复读」隔离级别使用「当前读」而造成的幻读问题,就引出了间隙锁(next-key lock 是间隙锁+记录锁的组合)。
幻读被完全解决了吗?
可重复读隔离级别下虽然很大程度上避免了幻读,但是还是没有能完全解决幻读。
总结
在多线程并发的场景下,会经常产生事务并行,而并行事务会发生脏读、不可重复读、幻读问题,这跟事务本身的特性相驳。事务的特性:ACID 原子性 一致性 隔离性 持久性。而这些特性是通过一些逻辑进行保证,但是如果每次操作都保证绝对的一致性,会非常影响性能。为了尽可能的保证性能,事务的隔离级别分为4种读未提交、读提交、可重复读、串行读。可以根据业务场景选择不同的隔离级别进行处理,而这些隔离级别通过MVCC/读写锁来保证。另外mysql默认可重复读隔离级别。