Skip to content

《面渣逆袭》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.事务的各个隔离级别都是如何实现的?

读未提交

读未提交,就不用多说了,采取的是读不加锁原理。

事务读不加锁,不阻塞其他事务的读和写事务写阻塞其他事务写,但不阻塞其他事务读;

读取已提交& 可重复读

读取已提交和可重复读级别利用了ReadViewMVCC,也就是每个事务只能读取它能看到的版本(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分别为100200的事务对这条记录进行update操作,整个过程如下:

配图

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

如下:

配图

ReadView

对于ReadCommittedRepeatableRead隔离级别来说,都需要读取已经提交的事务所修改的记录,也就是说如果版本链中某个版本的修改没有提交,那么该版本的记录时不能被读取的。所以需要确定在ReadCommittedRepeatableRead隔离级别下,版本链中哪个版本是能被当前事务读取的。于是就引入了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,这样就能保证后续读取的结果完全一致。

本文整理自三分恶《面渣逆袭》系列的公开内容,仅供个人学习使用

本站仅供个人学习使用,请勿外传