事务:指作为一个逻辑工作单元执行的一系列数据库操作;这些操作要不全部成功,要不全部失败,是一个不可分割的单元。
事务的目的是确保数据库操作的一致性和完整性,并且同时提高对并发访问的隔离性和恢复能力。
事务具有四大特性,既ACID特性:
原子性:事务要不全部成功要不全部失败; (由undolog保证)
一致性:事务执行前后,数据必须保持一致性; (由undolog保证)
隔离性:事务之间互不影响,相互隔离;
(由多版本并发控制MVCC和间隙锁Gap Lock保证的)
持久性:一旦事务提交成功,对数据库的修改应该永久保持,即使系统发送故障或重启;
(由redolog日志保证)
数据库的一次更新事务过程:
1、当进行数据更新时,innoDB首先在buffer poll中会检查被更新的数据是否在内存中,若不存在从磁盘文件读取数据到buffer poll中。
buerr poll是innoDB引擎提供的临时存储区,用于提升数据读取和修改的速度,将数据加载到bufer poll中,后续的更新操作均在里面进行。这样可以减少磁盘IO操作,从而提高事务处理速度,优化事务处理性能。
2、在更新事务之前,innoDB会将原始数据的副本写入Undo log(回滚日志)内
Undo log是保证事务回滚和并发控制的关键部分,也是确保事务原子性和一致性的重要机制。Undo log记录事务开始前的数据状态,以便在回滚时进行数据恢复。
3、更新内存数据
接下来,innoDB会在缓冲池中更新数据,这意味着当执行更新语句,innoDB会先更新已经读取到Buffer poll中的数据,修改操作会在内存中操作,而不是之间写入磁盘。此时,缓冲池中的数据被标记为 脏页 (表示区磁盘数据的不一致),脏页是指缓冲池中已经修改,但未写入磁盘中的数据页。他需要后续的处理才能修改到磁盘中,保证数据的一致性
4、写入Redo log(重做日志)
为了保证事务的持久性,innoDB在buffer poll修改操作时的同时,会将更新操作写入Redo log中。
Redo log是物理日志,记录了事务对数据库的修改操作。通过Redo log,即使系统故障,也可以通过Redo log来恢复事务修改之前的操作;重做日志是保证持久性和恢复性的关键。
5、提交事务
当事务完成以上操作时,事务被提交。在提交事务的时候,innoDB会将事务的状态更新为commit(准备提交)状态,此时事务的修改操作仍在缓冲池中,尚未写入磁盘,事务提交是事务处理的重要环节,它标志着事务处理完毕,可以进行后续的操作。
6、写入Bin log(归档日志)
在事务提交之后,innoDB会将事务的修改操作写入Bin log中。Bin log 是mysql的二进制文件,它记录着事务开始的时间、数据库名、表名、事务ID、SQL等。它可以用来数据恢复、主从复制、数据分析、同步等场景。
7、刷新脏页到磁盘中
最后,在提交过程完成后,innoDB会将缓冲池中的脏页刷新到物理磁盘上的数据文件中。
这个过程称为刷脏,通过刷脏操作,将缓冲池中的修改操作同步到磁盘中,确保数据的持久性。
但这个过程并非立即执行,而是由后台线程异步执行的,会有一定的延迟。
MySQL 在不同情况下进行刷盘的详细机制和时机:
1. InnoDB 缓冲池刷盘
InnoDB 缓冲池用于缓存数据页和索引页。以下几种情况会触发缓冲池的刷盘:
-
定期刷新脏页:InnoDB 有一个后台线程,会定期将缓冲池中的脏页(已被修改但尚未写入磁盘的数据页)刷新到磁盘。这是为了保证缓冲池有足够的可用空间并减少崩溃恢复时间。
-
检查点(Checkpoint):当达到一定的脏页阈值或重做日志空间使用达到一定百分比时,会触发检查点操作,InnoDB 将缓冲池中的脏页刷新到磁盘并更新检查点信息。
-
事务提交时:如果
innodb_flush_log_at_trx_commit
参数设置为 1,事务提交时会将重做日志刷入磁盘,从而间接引发脏页刷盘。
2. 重做日志(Redo Log)刷盘
重做日志用于记录数据页的物理修改操作,以保证事务的持久性。以下几种情况会触发重做日志的刷盘:
-
事务提交时:
-
如果
innodb_flush_log_at_trx_commit
设置为 1,每次事务提交时都会将重做日志写入磁盘。 -
如果设置为 2,每次事务提交时将重做日志写入日志文件,但不立即刷盘,重做日志每秒刷新到磁盘一次。
-
如果设置为 0,重做日志每秒刷新到磁盘一次,不会在事务提交时刷盘。
-
-
检查点操作:在检查点时,重做日志会被刷新到磁盘,以确保数据的一致性和减少恢复时间。
3. 二进制日志(Binlog)刷盘
二进制日志记录所有修改数据的 SQL 语句,主要用于复制和崩溃恢复。以下几种情况会触发二进制日志的刷盘:
-
事务提交时:
-
如果
sync_binlog
设置为 1,每次事务提交时都会将二进制日志写入磁盘。 -
如果设置为 0,二进制日志的刷盘由操作系统控制,不保证每次事务提交时刷盘。
-
如果设置为大于 1 的值,每 N 次事务提交时,二进制日志刷盘一次。
-
4. Doublewrite Buffer 刷盘
InnoDB 使用 doublewrite buffer 来防止部分页写入失败导致的数据损坏。以下几种情况会触发 doublewrite buffer 的刷盘:
-
脏页写入磁盘时:当 InnoDB 需要将脏页写入磁盘时,首先将这些页写入 doublewrite buffer,然后从 doublewrite buffer 写入到数据文件。这样可以确保即使在写入过程中发生崩溃,也能通过 doublewrite buffer 进行恢复。
5. 操作系统刷盘机制
操作系统的缓存机制也会影响 MySQL 的刷盘操作。具体来说,以下几种情况会影响刷盘:
-
文件系统同步操作:MySQL 可以通过
fsync()
或fdatasync()
系统调用强制将文件系统缓存中的数据写入磁盘。 -
操作系统定期刷新:大多数操作系统会定期将文件系统缓存中的数据写入磁盘,以减少数据丢失风险。
具体时机总结
-
事务提交时:根据配置参数,决定是否立即将重做日志和二进制日志刷入磁盘。
-
定期刷新:InnoDB 和操作系统都有定期刷新机制,以确保数据从内存写入磁盘。
-
检查点操作:触发检查点时,将缓冲池中的脏页和重做日志写入磁盘。
-
脏页超过阈值:当缓冲池中的脏页数量超过设定的阈值时,会触发刷盘操作。
数据库隔离级别
读未提交(RU):
一个事务读取到另一个事务未提交的数据,出现脏读、幻读、不可重复读(不使用 MVCC 或间隙锁,只能保证最低级别的隔离性,可能出现脏读、不可重复读和幻读)
读已提交(RC):
事务读取到事务提交的数据;出现幻读、不可重复读(主要依赖 MVCC 实现,每次读取时都读取最新已提交的数据版本,可以防止脏读,但不能防止不可重复读和幻读)
可重复读(RR):
一个事务执行过程中看到的数据,总是和这个事务开启时看到的数据是一致; 出现幻读
(依赖 MVCC 和间隙锁,读取时保证数据的一致性版本,并通过间隙锁防止幻读。)
可串行化(SR):
隔离性比较高,可以实现串行化读取数据,但是事务的并发度就没有了(通过锁机制(包括间隙锁)完全隔离事务,防止所有并发问题,但会导致大量的锁争用和性能开销)
MVCC(多版本并发控制)
innoDB采用的隔离级别是RR,它采用了MVCC(多版本并发控制)解决幻读,它是一种并发控制的方法,在数据库管理系统中,实现对数据库的并发访问。
幻读是指一个事务在读取某范围内的数据时,另一个事务在该范围内插入了新数据,导致前后读取结果不一致。MVCC 本身并不能完全解决幻读问题,因为它主要解决的是行级别的并发问题,而幻读是范围查询中的问题。
-
在可重复读隔离级别下,MVCC 通过记录每个事务的快照来确保读取的数据版本一致,从而部分缓解幻读问题。
-
然而,要彻底解决幻读问题,InnoDB 还引入了间隙锁(Gap Lock)机制。间隙锁不仅锁定现有的行,还锁定这些行之间的间隙,防止其他事务在这些间隙中插入新行,从而避免幻读
MVCC 主要解决脏读和不可重复读问题,通过多版本数据确保每个事务只能看到在它开始之前已经提交的数据版本。
MVCC 通过快照读部分缓解幻读问题,但不能完全解决幻读问题。为了彻底解决幻读问题,InnoDB 还使用了间隙锁机制。
MVCC解决的场景:
读读冲突:多个读之间不会产生冲突,可以并发执行
读写冲突:读操作不会被写操作阻塞,读操作可以读取到之前的数据版本,保证了数据的一致性
写写冲突:写之间不会产生冲突,可以并发执行
MVCC实现原理:
1、三个隐藏字段:
DB_TRX_ID:用来标识事务的唯一ID
DB_ROLL_PTR:指向undo log的回滚段指针
DB_ROW_ID:若表中没有主键时,出现隐藏的主键ID
2、undo log(回滚日志):
MVCC实现中的重要组件,用来记录数据的修改历史。
insert操作时:会生成一条insert类型的undo log,记录插入的操作
update和delete时,会生成对应的undo log,记录修改和删除之前的操作
3、read view(读视图):
MVCC中用来管理数据版本的重要概念,是逻辑数据视图;
readview是事务开始是创建的数据视图,用来确定事务的隔离级别和可见性规则;
还用来确定一个事务可以看到哪些数据版本,以及其他事务是否可以看到该事务修改的数据
read view中三个全局变量:
trx_list: 记录了当前活动的事务集合,其中每个事务包含一个事务ID和一个读视图
max_id: 全局的递增id,用于判断事务的可见性(活跃的最大事务id)
min_id: 全局的递增id,用于判断事务的可见性(活跃的最小事务id)
read view可见性比较规则:
如果DB_TRX_ID < min_id, 则当前事务可以看到DB_TRX_ID所在的记录;
如果DB_TRX_ID >= max_id,则表示DB_TRX_ID所在的记录在read view生成后才出现的,对当前事务 不可见
判断DB_TRX_ID是否在活跃事务集合内,如果在,则表示在read view生成时刻,这个事务还是活跃的,还 没有commit,修改的数据,当前事务是不可见的;如果没在,则说明这个事务在read view生成之前就 commit了,那么修改的数据是可见的
MVCC整体流程处理
假设有a、b、c、d四个事务,他们按顺序执行,并进行读写操作。MVCC的整体流程如下
-
a开始执行,并生成新的read view,并设置max_id、min_id为无穷大
-
a开始读操作,并获取数据库中的数据版本
-
a开始写操作,修改数据库中的数据,并生成对应的undolog
-
a提交事务,并将max_id设置为a的事务id
-
b开始执行,创建一个新的read view,并设置max_id为无穷大,min_id为a的事务id
-
b开始读操作,并获取数据库的数据版本
-
c开始执行,创建一个新的read view,并设置max_id为无穷大,min_id为a的事务id
-
b提交事务,并将max_id设置为b的事务id
-
依次类推