1.ACID特性
1.1.事务的四个特性
原子性:事务的所有操作要么全部提交成功,要么全部失败回滚,对于一个事务中的操作不能只执行其中一部分。
一致性:事务应确保数据库的状态从一个一致状态转变为另一个一致状态。一致性与业务规则有关,比如银行转账,不论事务成功还是失败,转账双方的总金额应该是不变的。
隔离性:多个并发事务之间需要相互隔离,即一个事务的执行不能被其他事务干扰。
持久性:一旦事务提交,则其所做的修改将永久保存到数据库中。即使发生系统崩溃,修改的数据也不会丢失。
1.2.如何保证事务的四个特性
1)原子性
undo log是InnoDB存储引擎来确保事务原子性的关键机制,undo log记录了事务发生之前的数据,如果事务失败,InnoDB会根据undo log回滚数据。
当事务开始修改数据时,InnoDB首先会在undo log中记录旧值(即修改前的值)。
如果事务顺利进行并最终提交,undo log会在某个时间点被清除。
如果事务中的某个操作失败或者事务被明确地回滚,InnoDB会使用undo log中的信息来撤销所有更改,确保数据的原子性。
2)一致性
一致性是ACID的目的,也就是说,只要保证原子性、隔离性、持久性,自然也就保证了数据的一致性。
3)隔离性
MySQL使用多种隔离级别来控制事务如何与其他并发事务隔离。InnoDB存储引擎使用MVCC (多版本并发控制)机制来处理并发事务,确保每个事务都有自己的数据版本。
4)持久性
由MySQL的存储引擎(如InnoDB)通过写入磁盘来确保。即使在系统崩溃之后,已提交事务的更改也不会丢失。InnoDB使用“redo log”来记录数据的更改,在系统崩溃后,redo log可用于恢复数据。
2.事务的隔离级别
事务的隔离级别是指在数据库管理系统中,多个并发事务之间如何相互隔离,避免相互干扰,从而保证数据的准确性和一致性。
不同的隔离级别定义了在并发事务中,如何控制事务间的可见性和对数据的访问,主要包括四种常见的级别:读未提交(Read Uncommitted)、读已提交(Read Committed)、可重复读(Repeatable Read)和串行化(Serializable)。隔离级别越高,对并发事务的限制越严格,但会降低系统的吞吐量和性能;而隔离级别越低,系统的吞吐量和性能越高,但可能会出现脏读、不可重复读或幻读等问题。
2.1.事务的隔离级别
1)读未提交
读未提交是最低的隔离级别,在这个级别,当前事务可以读取未被其他事务提交的数据,以至于会出现“脏读”、“不可重复读”和“幻读”的问题。
2)读已提交
当前事务只能读取已经被其他事务提交的数据,可以避免“脏读”现象。但不可重复读和幻读问题仍然存在。
3)可重复读
确保在同一事务中多次读取相同记录的结果是一致的,即使其他事务对这条记录进行了修改,也不会影响到当前事务。
是MySQL默认的隔离级别,避免了“脏读”和“不可重复读”,也在很大程度上减少了“幻读”问题。
串行化
最高的隔离级别,通过强制事务串行执行来避免并发问题,可以解决“脏读”、“不可重复读”和“幻读”问题。
但会导致大量的超时和锁竞争问题。
2.2.事务的隔离级别如何实现
读未提交是如何实现的?
不提供任何锁机制来保护读取的数据,允许读取未提交的数据(即脏读)。
读已提交&可重复读是如何实现的?
读已提交和可重复读通过 MVCC 机制中的 ReadView 来实现。
READ COMMITTED:每次读取数据前都生成一个 ReadView,保证每次读操作都是最新的数据。
REPEATABLE READ:只在第一次读操作时生成一个 ReadView,后续读操作都使用这个 ReadView,保证事务内读取的数据是一致的。
串行化是如何实现的?
事务在读操作时,必须先加表级共享锁,直到事务结束才释放;事务在写操作时,必须先加表级排他锁,直到事务结束才释放。
补充:
1)表级共享锁:允许多个事务同时读取表中的数据,但不允许修改数据,其他事务也只能读取,不能修改。
2)表级排他锁:阻止其他事务访问表中的数据,包括读取和修改,确保当前事务独占对该表的操作权限。
3.脏读、幻读、不可重复读
1)脏读
事务 A、B 交替执行,事务 A 读取到事务 B 未提交的数据,这就是脏读。
2)不可重复读
在一个事务范围内,两个相同的查询,读取同一条记录,却返回了不同的数据,这就是不可重复读。
3)幻读
事务A查询一个范围的结果集,另一个并发事务 B 往这个范围中插入 / 删除了数据,并静悄悄地提交(重点),然后事务 A 再次查询相同的范围,两次读取得到的结果集不一样了,这就是幻读。
4.MVCC机制
4.1.什么是MVCC
MVCC 是多版本并发控制,主要用来解决数据库并发问题。
在支持 MVCC 的数据库中,当多个用户同时访问数据时,每个用户都可以看到一个在某一时间点之前的数据库快照,并且能够无阻塞地执行查询和修改操作,而不会相互干扰。
在传统的锁机制中,如果一个事务正在写数据,那么其他事务必须等待写事务完成才能读数据,MVCC 允许读操作访问数据的一个旧版本快照,同时写操作创建一个新的版本,这样读写操作就可以并行进行,不必等待对方完成。
在 MySQL 中,特别是 InnoDB 存储引擎,MVCC 是通过版本链和 ReadView 机制来实现的。
4.2.什么是版本链(?)
在InnoDB中,每一行数据都有两个隐藏的列:一个是DB_TRX_ID,另一个是DB_ROLL_PTR。
DB_TRX_ID,保存创建这个版本的事务ID。
DB_ROLL_PTR,指向undo日志记录的指针,这个记录包含了该行的前一个版本的信息。通过这个指针,可以访问到该行数据的历史版本。
当事务更新一行数据时,InnoDB不会直接覆盖原有数据,而是创建一个新的数据版本,并更新DB_TRX_ID和DB_ROLL_PTR,使得它们指向前一个版本和相关的undo日志。这样,老版本的数据不会丢失,可以通过版本链找到。
由于undo日志会记录每一次的update,并且新插入的行数据会记录上一条undo日志的指针,所以可以通过这个指针找到上一条记录,这样就形成了一个版本链。
5.readview视图
5.1.什么是readview视图
ReadView(读视图)是 InnoDB 为了实现一致性读(Consistent Read)而创建的数据结构,它用于确定在特定事务中哪些版本的行记录是可见的。
ReadView 主要用来处理隔离级别为"可重复读"(REPEATABLE READ)和"读已提交"(READ COMMITTED)的情况。因为在这两个隔离级别下,事务在读取数据时,需要保证读取到的数据是一致的,即读取到的数据是在事务开始时的一个快照。
当事务开始执行时,InnoDB 会为该事务创建一个 ReadView,这个 ReadView 会记录 4 个重要的信息:
creator_trx_id:创建该 ReadView 的事务 ID。
m_ids:所有活跃事务的 ID 列表,活跃事务是指那些已经开始但尚未提交的事务。
min_trx_id:所有活跃事务中最小的事务 ID。它是 m_ids 数组中最小的事务 ID。
max_trx_id :事务 ID 的最大值加一。换句话说,它是下一个将要生成的事务 ID。
5.2.readview如何判断记录某个版本是否可见
当一个事务读取某条数据时,InnoDB 会根据 ReadView 中的信息来判断该数据的某个版本是否可见。
①如果某个数据版本的 DB_TRX_ID 小于 min_trx_id,则该数据版本在生成 ReadView 之前就已经提交,因此对当前事务是可见的。
②如果某个数据版本的 DB_TRX_ID 大于 max_trx_id,则表示创建该数据版本的事务在生成 ReadView 之后开始,因此对当前事务是不可见的。
③如果某个数据版本的 DB_TRX_ID 在 min_trx_id 和 max_trx_id 之间,需要判断 DB_TRX_ID 是否在 m_ids 列表中:
不在,表示创建该数据版本的活跃事务已经提交,因此对当前事务也是可见的。
在,则表示创建该数据版本的事务仍然活跃,或者在当前事务生成 ReadView 之后开始,因此对当前事务是不可见的。
5.3.可重复读和读已提交在readview上的区别
可重复读(REPEATABLE READ)和读已提交(READ COMMITTED)的区别在于生成 ReadView 的时机不同。
1)可重复读:在第一次读取数据时生成一个 ReadView,这个 ReadView 会一直保持到事务结束,这样可以保证在事务中多次读取同一行数据时,读取到的数据是一致的。
2)读已提交:每次读取数据前都生成一个 ReadView,这样就能保证每次读取的数据都是最新的。