主题
《面渣逆袭》MySQL 篇 · 第 8/10 章。原版 PDF(下载 / 打印)
48.MySQL 事务的四大特性说一下?

原子性:事务作为一个整体被执行,包含在其中的对数据库的操作要么全部被执行,要么都不执行。
一致性:指在事务开始之前和事务结束以后,数据不会被破坏,假如 A 账户给 B 账户转 10 块钱,不管成功与否,A 和 B 的总金额是不变的。
隔离性:多个事务并发访问时,事务之间是相互隔离的,即一个事务不影响其它事务运行效果。简言之,就是事务之间是进水不犯河水的。
持久性:表示事务完成以后,该事务对数据库所作的操作更改,将持久地保存在数据库之中。
49.那 ACID 靠什么保证的呢?
事务的隔离性是通过数据库锁的机制实现的。
事务的一致性由 undolog 来保证:undolog 是逻辑日志,记录了事务的 insert 、update 、
deltete 操作,回滚的时候做相反的 delete 、update 、insert 操作来恢复数据。
事务的原子性和持久性由 redolog 来保证:redolog 被称作重做日志,是物理日志,事务提交的时候,必须先将事务的所有日志写入 redolog 持久化,到事务的提交操作才算完成。

50.事务的隔离级别有哪些?MySQL 的默认隔离级别是什么?

事务的四个隔离级别读未提交(ReadUncommitted )
读已提交(ReadCommitted )
可重复读(RepeatableRead )
串行化(Serializable )
MySQL 默认的事务隔离级别是可重复读 (Repeatable Read)。51.什么是幻读,脏读,不可重复读呢?
事务 A 、B 交替执行,事务 A 读取到事务 B 未提交的数据,这就是脏读。
在一个事务范围内,两个相同的查询,读取同一条记录,却返回了不同的数据,这就是不可重复
读。
事务 A 查询一个范围的结果集,另一个并发事务 B 往这个范围中插入 / 删除了数据,并静悄悄地提交,然后事务 A 再次查询相同的范围,两次读取得到的结果集不一样了,这就是幻读。
不同的隔离级别,在并发事务下可能会发生的问题:
| 隔离级别 | 脏读 | 不可重复读 | 幻读 |
|---|---|---|---|
| ReadUncommited 读取未提交 | 是 | 是 | 是 |
| ReadCommited 读取已提交 | 否 | 是 | 否 |
| RepeatableRead 可重复读 | 否 | 否 | 是 |
| Serialzable 可串行化 | 否 | 否 | 否 |
52.事务的各个隔离级别都是如何实现的?
读未提交
读未提交,就不用多说了,采取的是读不加锁原理。
事务读不加锁,不阻塞其他事务的读和写事务写阻塞其他事务写,但不阻塞其他事务读;
读取已提交& 可重复读
读取已提交和可重复读级别利用了ReadView和MVCC,也就是每个事务只能读取它能看到的版本(R eadView )。
READCOMMITTED :每次读取数据前都生成一个 ReadView
REPE ATA BLEREAD :在第一次读取数据时生成一个 ReadView
串行化
串行化的实现采用的是读写都加锁的原理。
串行化的情况下,对于同一行事务,写会加写锁,读会加读锁。当出现读写锁冲突的时候,后访问的事务必须等前一个事务执行完成,才能继续执行。
53.MVCC 了解吗?怎么实现的?
MVCC(Multi Version Concurrency Control),中文名是多版本并发控制,简单来说就是通过维护数据历史版本,从而解决并发访问情况下的读一致性问题。关于它的实现,要抓住几个关键点,隐式字
段、undo 日志、版本链、快照读& 当前读、ReadView。
版本链
对于 InnoDB 存储引擎,每一行记录都有两个隐藏列DB_TRX_ID 、D B_ROLL_PTR
DB_TRX_ID,事务 ID ,每次修改时,都会把该事务 ID 复制给DB_TRX_ID;
DB_ROLL_PTR,回滚指针,指向回滚段的 undo 日志。

假如有一张user表,表中只有一行记录,当时插入的事务 id 为 80 。此时,该条记录的示例图如下:

接下来有两个DB_TRX_ID分别为100、200的事务对这条记录进行update操作,整个过程如下:

由于每次变动都会先把undo日志记录下来,并用DB_ROLL_PTR指向undo日志地址。因此可以认为,对该条记录的修改日志串联起来就形成了一个版本链,版本链的头节点就是当前记录最新的值。
如下:

ReadView
对于ReadCommitted和RepeatableRead隔离级别来说,都需要读取已经提交的事务所修改的记录,也就是说如果版本链中某个版本的修改没有提交,那么该版本的记录时不能被读取的。所以需要确定在ReadCommitted和RepeatableRead隔离级别下,版本链中哪个版本是能被当前事务读取的。于是就引入了ReadView这个概念来解决这个问题。
ReadView 就是事务执行快照读时,产生的读视图,相当于某时刻表记录的一个快照,通过这个快照,我们可以获取:

m_ids :表示在生成 ReadView 时当前系统中活跃的读写事务的事务 id 列表。
min_trx_id :表示在生成 ReadView 时当前系统中活跃的读写事务中最小的 事务 id ,也就是
m_ids 中的最小值。
max_trx_id :表示生成 ReadView 时系统中应该分配给下一个事务的 id 值。
creator_trx_id :表示生成该 ReadView 的事务的 事务 id有了这个 ReadView ,这样在访问某条记录时,只需要按照下边的步骤判断记录的某个版本是否可见:
如果被访问版本的 DB_TRX_ID 属性值与 ReadView 中的 creator_trx_id 值相同,意味着当前事务在访问它自己修改过的记录,所以该版本可以被当前事务访问。
如果被访问版本的 DB_TRX_ID 属性值小于 ReadView 中的 min_trx_id 值,表明生成该版本的事务在当前事务生成 ReadView 前已经提交,所以该版本可以被当前事务访问。
如果被访问版本的 DB_TRX_ID 属性值大于 ReadView 中的 max_trx_id 值,表明生成该版本的事务在当前事务生成 ReadView 后才开启,所以该版本不可以被当前事务访问。
如果被访问版本的 DB_TRX_ID 属性值在 ReadView 的 min_trx_id 和 max_trx_id 之间,那就需要判断一下 trx_id 属性值是不是在 m_ids 列表中,如果在,说明创建 ReadView 时生成该版本的事务还是活跃的,该版本不可以被访问;如果不在,说明创建 ReadView 时生成该版本的事务已经被提交,该版本可以被访问。
如果某个版本的数据对当前事务不可见的话,那就顺着版本链找到下一个版本的数据,继续按照上边的步骤判断可见性,依此类推,直到版本链中的最后一个版本。如果最后一个版本也不可见的话,那么就意味着该条记录对该事务完全不可见,查询结果就不包含该记录。
在 MySQL 中, READCOMMITTED 和 REPE ATA BLEREAD 隔离级别的的一个非常大的区别就是它们生成 ReadView 的时机不同。
READCOMMITTED 是每次读取数据前都生成一个 ReadView,这样就能保证自己每次都能读到其它事务提交的数据;REPE ATA BLEREAD 是在第一次读取数据时生成一个 ReadView,这样就能保证后续读取的结果完全一致。