MySQL 事务的四大特性说⼀下?
- 原⼦性:事务作为⼀个整体被执行,包含在其中的对数据库的操作要么全部被执行,要么都不执行。
- ⼀致性:指在事务开始之前和事务结束以后,数据不会被破坏,假如 A 账户给 B 账户转 10 块钱,不管成功与否,A 和 B 的总⾦额是不变的。
- 隔离性:多个事务并发访问时,事务之间是相互隔离的,即⼀个事务不影响其它事务运⾏效果。简而⾔之,就是事务之间是进⽔不犯河⽔的。
- 持久性:表⽰事务完成以后,该事务对数据库所作的操作更改,将持久地保存在数据库之中。
那 ACID 靠什么保证的呢?
- 事务的隔离性是通过数据库锁的机制实现的。
- 事务的⼀致性由 undo log 来保证:undo log 是逻辑⽇志,记录了事务的 insert、update、deltete 操作,回滚的时候做相反的 delete、update、insert 操作来恢复数据。
- 事务的原⼦性和持久性由 redo log 来保证:redolog 被称作重做⽇志,是物理⽇志,事务提交的时候,必须先将事务的所有⽇志写⼊ redo log 持久化,到事务的提交操作才算完成。
事务的隔离级别有哪些?MySQL 的默认隔离级别是什么?
- MySQL 默认的事务隔离级别是可重复读 (Repeatable Read)。
事务的各个隔离级别都是如何实现的?
- 读未提交,就不⽤多说了,采取的是读不加锁原理。
-
- 事务读不加锁,不阻塞其他事务的读和写
-
- 事务写阻塞其他事务写,但不阻塞其他事务读;
- 读取已提交&可重复读:
- 读取已提交和可重复读级别利⽤了 ReadView 和 MVCC ,也就是每个事务只能读取它能看到的版本(ReadView)。
-
- READ COMMITTED:每次读取数据前都⽣成⼀个 ReadView
-
- REPEATABLE READ :在第⼀次读取数据时⽣成⼀个 ReadView。repeatable
- 串行化:串⾏化的实现采⽤的是读写都加锁的原理。
什么是幻读,脏读,不可重复读呢?
- 事务 A、B 交替执⾏,事务 A 读取到事务 B 未提交的数据,这就是脏读。
- 在⼀个事务范围内,两个相同的查询,读取同⼀条记录,却返回了不同的数据,这就是不可重复读。
- 事务 A 查询⼀个范围的结果集,另⼀个并发事务 B 往这个范围中插⼊ / 删除了数据,并静悄悄地提交,然后事务 A 再次查询相同的范围,两次读取得到的结果集不⼀样了,这就是幻读。
- 不同级别下,并发事务可能发生的问题:
MVCC 了解吗?怎么实现的?
- 中⽂名是多版本并发控制,简单来说就是通过维护数
据历史版本,从而解决并发访问情况下的读⼀致性问题。关于它的实现,要抓住几个关键点,隐式字段、undo ⽇志、版本链、快照读&当前读、Read View。
版本链
- 对于 InnoDB 存储引擎,每⼀⾏记录都有两个隐藏列DB_TRX_ID、DB_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
对于 Read Committed 和 Repeatable Read 隔离级别来说,都需要读取已经提交的事务所修改的记录,也就是说如果版本链中某个版本的修改没有提交,那么该版本的记录时不能被读取的。所以需要确定在 Read Committed 和 Repeatable Read 隔离级别下,版本链中哪个版本是能被当前事务读取的。于是就引⼊了 ReadView 这个概念来解决这个问题。
Read View 就是事务执⾏快照读时,产⽣的读视图,相当于某时刻表记录的⼀个快照,通过这个快照,我们可以获取:
- 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 时⽣成该版本的事务已经被提交,该版本可以被访问。
如果某个版本的数据对当前事务不可见的话,那就顺着版本链找到下⼀个版本的数据,继续按照上边的步骤判断可见性,依此类推,直到版本链中的最后⼀个版本。如果最后⼀个版本也不可见的话,那么就意味着该条记录对该事务完全不可见,查询结果就不包含该记录。