以下笔记都是基于黑马程序员的面试题写的:
一、什么是事物?
事务是一组操作的集合,它是一个不可分割的工作单位,事务会把所有的操作作为一个整体一起向系统提交或撤销操作请求,即这些操作要么同时成功,要么同时失败。
事物有以下四种特性:
- 原子性(Atomicity):事务是不可分割的最小操作单元,要么全部成功,要么全部失败。
- 一致性(Consistency):事务完成时,必须使所有的数据都保持一致状态。
- 隔离性(Isolation):数据库系统提供的隔离机制,保证事务在不受外部并发操作影响的独立环境下运行。
- 持久性(Durability):事务一旦提交或回滚,它对数据库中的数据的改变就是永久的。
二、并发事务
在数据库中,多个事务同时执行即并发事务。它会带来三大问题:脏读、不可重复读和幻读。
1.并发事务带来的问题
脏读:一个事务读取到另一个事务尚未提交的数据变更。例如,事务 A 更新了某条记录但未提交,此时事务 B 读取了这条被事务 A 更新后的数据,若事务 A 最终回滚,那么事务 B 读取到的数据就是无效的脏数据。
不可重复读:在一个事务内的多次相同查询,得到的结果不一致。比如事务 A 在查询某条记录后,事务 B 对该记录进行了更新并提交,事务 A 再次查询时就会得到不同的结果。
幻读:一个事务按照条件查询数据时,没有对应的数据行,但是在插入数据时,又发现这行数据已经存在,好像出现了”幻影”。例如,事物A插入数据时并没有这条数据,此时事物B插入了该条数据,事物A发现数据又存在了。
幻读发生在不可重复度解决的前提下。
2.解决办法
设置事务隔离级别:数据库通过设置不同的事务隔离级别来控制并发事务之间的干扰程度。
读未提交(Read Uncommitted):最低的隔离级别,允许一个事务读取另一个事务未提交的数据,会导致脏读、不可重复读和幻读问题,实际应用中很少使用。
读已提交(Read Committed):一个事务只能读取另一个事务已经提交的数据。可以避免脏读,但仍可能出现不可重复读和幻读问题。在 MySQL 的 InnoDB 和 Oracle 数据库中,这是默认隔离级别之一。
可重复读(Repeatable Read):在一个事务内的多次相同查询,结果是一致的。可以避免脏读和不可重复读问题,默认使用。
串行化(Serializable):最高的隔离级别,事务串行执行,避免了脏读、不可重复读、幻读等所有并发问题,但会严重影响并发性能,只有在对数据一致性要求极高且并发量较低的场景下才会使用。
注:事务隔离级别越高,数据越安全,但是性能越低。
三、undo log和redo log
undo log 和 redo log 是 MySQL 中 InnoDB 存储引擎为了保证事务的原子性、一致性、持久性等特性而使用的两种重要日志。
Mysql中的数据是如何进行持久化存储的?
MySQL中对数据的修改并不是直接在磁盘上进行操作的,执行增删改查操作时,先操作缓冲池中的数据(若缓冲池没有数据,则从磁盘加载并缓存),以一定频率刷新到磁盘,从而减少磁盘IO,加快处理的速度
1.redo log
redo log:又叫重做日志,记录的是事务提交时数据页的物理修改,是用来实现事务的持久性。
该日志文件由两部分组成:重做日志缓冲(redo log buffer)以及重做日志文件(redo log file),前者是在内存中,后者在磁盘中。当事务提交之后会把所有修改信息都存到该日志文件中, 用于在刷新脏页到磁盘,发生错误时, 进行数据恢复使用。
2.undo log
undo log:又叫回滚日志,用于记录数据被修改前的信息 , 作用包含两个 : 提供回滚 和 MVCC(多版本并发控制) 。undo log和redo log记录物理日志不一样,它是逻辑日志,undo log可以实现事务的一致性和原子性。
- 可以认为当delete一条记录时,undo log中会记录一条对应的insert记录,反之亦然, 当update一条记录时,它记录一条对应相反的update记录。
- 当执行rollback时,就可以从undo log中的逻辑记录读取到相应的内容并进行回滚。
四、MVCC
上述redo log和undo log分别保证了事物的持久性,原子性和一致性,那么事物的隔离性是如何保证的呢?
数据库主要通过锁机制、MVCC(多版本并发控制)机制来保证事务的隔离性。
如锁机制中的排他锁,当一个事物获取到了数据行的排他锁,那么其他事物是无法再在该数据行上获取其他锁。下面详细说明一下MVCC:
MVCC全称 Multi-Version Concurrency Control,多版本并发控制。指维护一个数据的多个版本,使得读写操作没有冲突。MVCC的具体实现,主要依赖于数据库记录中的隐式字段、undo log日志、readView。
1. 数据行的隐藏字段
每行数据除了用户定义的字段外,还包含 3 个隐藏字段:
- DB_TRX_ID:记录最后一次修改该数据行的事务 ID(事务开始时会分配一个唯一递增的 ID)。
- DB_ROLL_PTR:回滚指针,指向该行数据的上一个版本(存储在 undo log 中)。
- DB_ROW_ID:若表没有主键,InnoDB 会自动生成一个行 ID,用于唯一标识行。
2. undo log(回滚日志)
当事务修改数据时,InnoDB 会先将旧数据写入 undo log,作为数据的 “历史版本”。
每行数据的多个版本通过DB_ROLL_PTR
串联成一条 “版本链”,最新版本在数据表中,旧版本在 undo log 中。
3. 事务的 Read View(读视图)
- 每个事务在读取数据时,会生成一个 Read View,用于判断哪些版本的数据对当前事务 “可见”。
- Read View 包含 4 个核心参数:
m_ids
:当前活跃(未提交)的事务 ID 列表。min_trx_id
:m_ids
中的最小事务 ID。max_trx_id
:系统下一个将要分配的事务 ID(即当前最大事务 ID+1)。creator_trx_id
:当前事务自身的 ID。
不同的隔离级别,生成ReadView的时机不同:
- READ COMMITTED :在事务中每一次执行快照读时生成ReadView。
- REPEATABLE READ:仅在事务中第一次执行快照读时生成ReadView,后续复用该ReadView。
示例:
在RC隔离级别下,事物5第一次查询读取的记录是事物2提交的记录,第二次读取的记录是事物3提交的记录。因为是读已提交,也就是读取的是其他事物已提交的记录:
在RR隔离级别下,因为仅在事务中第一次执行快照读时生成ReadView,后续复用该ReadView,所以两次读取的记录都是事物2已提交的记录。
五、相关面试问题
5.1 什么是事物?事物的特性又是什么?
事物是一组操作的集合,它是一组不可分割的单位。事物将这些操作作为一个整体一起向系统提交或撤销,这些操作要么同时成功,要么同时失败。
事物有四大特性:ACID,即原子性、一致性、隔离性和持久性。
例如,A向B转账,要么都成功,要么都失败,体现了原子性。A向B转账100元,B账户就必须增加100元,体现了一致性。隔离性体现在该事物的操作不受其他事物影响。持久性体现在,数据一经提交,就要对数据持久化存储。
5.2 并发事物会带来哪些问题?
并发事物会带来脏读、不可重复读和幻读等问题。
首先,脏读是指一个事物读取到了另一个事物未提交的数据变更。
其次,不可重复读是指一个事物内的多次查询结果不一致。
然后,幻读是指一个数据根据条件查询数据行时,并不存在这条数据,但是在插入时却发现该条数据又存在,就像出现了幻影一样。
5.3 如何解决并发事物带来的这些问题?
数据库通过设置不同的事务隔离级别来控制并发事务之间的干扰程度,有以下四种事物隔离级别:读未提交、读已提交、可重复读和串行化。
首先,读未提交是指一个事物可以读取另一个事物未提交的数据,不可以解决脏读、不可重复读和幻读问题。
其次,读已提交是指一个事物只能读取另一个事物提交过的数据,解决了脏读问题。
然后,可重复读是指一个事物内的多次查询,结果是一样的,解决了脏读和不可重复读问题,MySQL默认隔离级别就是可重复读。
最后,串行化是指事物串行执行,避免了以上三种问题,但是性能也随之下降。
5.4 undo log和redo log的区别有哪些?
redo log又叫重做日志,用于记录数据页的物理变化,在服务宕机恢复时可用于恢复数据,实现了事物的持久化。
undo log又叫回滚日志,主要记录的是逻辑日志,当事物回滚时,可通过逆操作来恢复原来的数据,保证了事物的原子性和一致性。
5.5 事物的隔离性是如何保证的?(解释一下MVCC)
数据库中事物的隔离性是通过锁机制和MVCC机制来保证的。
其中,MVCC即多版本并发并发控制,指维护一个数据的多个版本,使得读写操作没有冲突,。MVCC的具体实现依赖于三个部分:隐式字段、undo log和readView。
首先,隐式字段是指MySQL给每一个表都添加了隐式字段,包括trx_id,也就是记录每一次操作的事物id;roll pointer,回滚指针,指向上一次操作的事物地址。
其次,undo log的主要作用有记录回滚日志以及存储老版本信息,在其内部会形成一个版本链,链头是最新记录,链尾是第一次操作的记录。
最后,readView解决的是不同事物查询选择版本的问题,其中定义了一些规则用于判断查询该读取哪个版本的信息。不同隔离级别的readView是不一样的,最后所查询的版本也会不一样,如读已提交,在每次查询时都会生成一份readView,如可重复读,只有在第一次查询时生成一份readView,后续查询继续沿用此readView。