1 概述
事务是一组操作的集合,它是一个不可分割的工作单位,事务会把所有操作作为一个整体一起向系统提交或者撤销操作请求,即这些操作要么同时成功,要么同时失败。
- 事务特性
- 原子性(Atomatic):事务是不可分割的最小操作单位,要么全部成功,要么全部失败;
- 一致性(Consistency):事务完成时,所有数据保持一致状态;
- 隔离性(Isolation): 数据库系统提供的隔离机制,保证事务在不受外部并发操作影响的独立环境下运行;
- 持久性(Durability):事务一旦提交或回滚,它对数据库中数据的改变是永久的。
2 事务原理
事务的原子性、一致性和持久性是由两类日志,redo log和undo log保证的;事务的隔离性则由锁和mvcc保证。
2.1 redo log 和 持久性
重做日志,记录的是事务提交时数据页的物理修改,用来实现事务的持久性。
该日志文件由两部分组成:重做日志缓冲(redo log buffer)以及重做日志文件(redo log file),前者在内存中,后者在磁盘中。当事务提交之后会把所有修改信息保存到该日志文件中,用于在刷新脏页到磁盘发生错误时,进行数据恢复使用。
- 没有redo log时的更新流程
数据修改,事务提交后,缓冲区数据直接刷新到磁盘,已经通知客户端事务提交成功。但是刷新脏页数据出现错误,导致数据的不一致。
- redo log 更新流程
事务提交后,首先redo log buffer保存数据页的变化,更新到磁盘redo log 文件。然后缓冲区数据变更刷新到磁盘,如果执行正常,那么磁盘中redo log 文件就不需要关注;如果刷新脏页发生错误,通过redo log来恢复数据。这种机制称为WAL(write-ahead logging),优先记录日志。日志文件iblogfile0与iblogfile1循环使用,定期清理。
MySQL的redo log(重做日志)是用于保证事务的持久性的一种机制。它记录了所有对数据库所做的修改操作,以便在系统故障或重启后进行恢复。下面是MySQL redo log的原理:
- 重做日志的结构:MySQL的重做日志是由一系列固定大小的日志文件组成,通常被称为redo log文件组。每个redo log文件都被划分为多个大小相等的日志块(log block)。在每个日志块中,记录了数据库的修改操作。
- 写入过程:当执行一个事务的修改操作时,MySQL首先将这些修改操作记录到内存中的重做日志缓冲区(redo log buffer),然后异步地将缓冲区中的日志写入到磁盘上的重做日志文件中。这个过程称为"write-ahead logging"(先写日志,再写磁盘)。在事务提交之前,只需要将重做日志写入到磁盘,而不需要将所有的数据修改都写入到磁盘。
- 持久化:重做日志的写入是采用顺序写(sequential write)方式,这样可以提高写入性能。写入磁盘的过程中,MySQL使用了一种叫做"write sync"的策略,即将重做日志文件的数据强制刷新到磁盘上的持久存储,以保证数据的持久性。在写入磁盘之前,MySQL还会将日志数据按照一定的顺序进行分组,以减少磁盘IO的次数,提高性能。
- 重做日志的应用:当系统发生崩溃或重启时,MySQL会通过重做日志来进行数据库的恢复。在数据库启动时,MySQL会读取最后一个完整的事务日志块,并从中恢复出未完成的事务。然后,MySQL会根据重做日志的记录顺序,重新执行日志中的操作,将数据库恢复到崩溃前的状态。
重做日志的存在使得MySQL能够在系统故障或重启后保持事务的持久性。通过将事务的修改操作先记录到重做日志中,而不是立即写入磁盘,MySQL实现了较高的写入性能。同时,通过顺序写和写入磁盘前的分组操作,也提高了重做日志的写入效率。这种机制保证了MySQL的数据一致性和可靠性。
2.2 undo log 和原子性
回滚日志,用于记录数据被修改前的信息,作用包含两个:提供回滚和MVCC(多版本并发控制)。
undo log 和redo log记录物理日志不一样,它是逻辑日志。可以理解为当delete一条记录时,undo log中会记录一条对应的insert记录,反正易然。当执行一条update语句时,它会记录一条相反的update记录。当执行rollback时,就可以从undo log日志记录中读取相应的内容并回滚。
undo log 销毁:undo log在事务执行时产生,事务提交时,并不会立即删除undo log,因为这些日志还可能用于MVCC。
undo log存储:undo log 采用段的方式进行管理和记录,存放在rollback segment回滚段中,内部包含1024个undo log segment。
MySQL的undo log(撤销日志)是用于实现事务的回滚和MVCC(多版本并发控制)的一种机制。它记录了对数据库进行修改的旧值,以便在事务回滚或读取旧版本数据时使用。下面是MySQL undo log的原理:
- 撤销日志的结构:MySQL的撤销日志是以逻辑日志的形式存在的,每个事务都有自己的撤销日志。在每个事务开始时,MySQL会为该事务分配一个撤销日志的段(undo log segment),其中包含多个撤销日志块(undo log block)。每个撤销日志块都存储了对应事务的修改操作。
- 写入过程:当执行一个事务的修改操作时,MySQL会在撤销日志中记录对应的旧值。这些修改操作首先会被写入内存中的撤销日志缓冲区(undo log buffer),然后异步地将缓冲区中的日志写入到磁盘上的撤销日志块中。与重做日志不同,撤销日志的写入是在事务执行过程中进行的,而不是在事务提交之前。
- 回滚操作:当事务需要回滚时,MySQL会根据事务的撤销日志来进行回滚操作。通过读取撤销日志中的旧值,MySQL可以将数据恢复到事务开始之前的状态。回滚操作是通过撤销事务执行过程中所做的修改来实现的。
- MVCC支持:MySQL使用撤销日志来实现MVCC(多版本并发控制)。在读取数据时,如果事务需要读取一个已经被其他事务修改的数据,MySQL会根据撤销日志中的旧值创建一个新的数据版本(数据快照),供该事务读取。这样可以实现事务之间的隔离性,避免读取到未提交的修改。
撤销日志在MySQL中起到了重要的作用,它保证了事务的回滚能力和MVCC的支持。通过记录旧值和撤销操作,撤销日志使得MySQL能够回滚事务的修改,恢复到事务开始前的状态。同时,通过撤销日志的利用,MySQL实现了MVCC,提供了高并发的数据读取和隔离性。
3 MVCC
3.1 基本概念
当前读:读取的是记录的最新版本,读取时还要保证其他并发事务不能修改当前记录,会对读取的记录加锁。比如
select ... lock in share mode ,select ... for update ,update、insert、delete
都是当前读。
当前读(Current Read)是数据库事务隔离级别中的一种读取数据的方式。在当前读中,事务读取的是最新提交的数据版本,而不考虑正在执行的事务对数据所做的修改。
在当前读中,如果某个事务在读取某个数据行时,发现该数据行已经被其他事务修改但尚未提交,当前读会等待该事务完成并获取最新的数据版本后再读取。这种读取方式保证了事务读取到的数据是最新提交的数据。
当前读的特点如下:
- 读取最新提交的数据:当前读忽略了正在执行的事务对数据所做的修改,直接读取最新提交的数据版本。
- 阻塞等待:如果当前读发现某个数据行正在被其他事务修改但尚未提交,它会等待该事务完成并获取最新的数据版本后再进行读取。这可能导致当前读的阻塞等待,直到需要读取的数据行可用。
- 读取一致性数据:当前读保证事务读取的数据是一致性的,即读取的是最新提交的数据版本,而不会读取到其他事务正在修改但尚未提交的数据。
当前读适用于一些对数据的实时性要求较高的场景,如查询实时统计数据、显示最新的库存信息等。但是需要注意,当前读可能会导致更多的阻塞等待,对并发性能产生影响。因此,在选择隔离级别和读取方式时,需要综合考虑应用的实际需求和并发性能的平衡。
快照读:简单的select(不加锁)就是快照读,读取的是数据记录的可见版本,有可能是历史版本,不加锁,是非阻塞读取。
快照读(Snapshot Read)是数据库事务隔离级别中的一种读取数据的方式。在快照读中,事务读取的是一个一致性的数据快照,即读取事务开始时数据库中的数据版本,并在整个事务过程中保持不变,不受其他事务的修改影响。
快照读的特点如下:
- 读取一致性数据快照:快照读保证事务读取的数据是在事务开始时数据库中的一致性数据版本,并在整个事务过程中保持不变。即使其他事务对数据进行了修改或提交,快照读也不会读取到这些变更。
- 不阻塞等待:快照读不会等待其他事务的锁或提交操作,它读取的是已经存在的数据版本,并不关心其他事务对数据的修改。因此,快照读不会阻塞等待其他事务的完成。
- 提供事务的一致性视图:快照读为事务提供了一个一致性的数据视图,事务可以在整个事务过程中使用该视图进行读取操作,而不会受到其他并发事务的修改干扰。
快照读适用于一些不要求读取最新数据的场景,如生成报表、数据分析等。由于快照读不会阻塞等待其他事务,因此可以提高并发性能。然而,需要注意的是,由于快照读读取的是一致性数据快照,可能会导致读取到已经过时的数据。因此,在选择隔离级别和读取方式时,需要根据应用的实际需求来确定是否使用快照读。
演示:
开启2个客户端,2个客户端都开启事务。客户端1简单读,客户端2修改一条记录,客户端再次读取数据。
不管客户端2事务修改记录未提交还是提交事务,客户端1都是快照读,读取的数据一致。如果客户端想要读取数据的最新记录,需要执行当前读。
MVCC(Multi-Version Concurrency Control)是一种用于实现并发控制的数据库技术。它在多用户并发访问数据库时,通过创建和管理多个数据版本,实现了高并发性和数据一致性的平衡。MVCC常用于支持事务隔离级别为"可重复读"的数据库系统,如MySQL的InnoDB存储引擎。
MVCC的具体实现,还需要依赖于数据库记录中的三个隐藏字段、undo log日志和readView。
3.2 隐藏字段
如何查看表中隐藏字段呢?通过ibd2sdi
命令查看表空间文件.ibd,命令使用可以参考msyql官方文档<https://dev.mysql.com/doc/refman/8.0/en/ibd2sdi.html>或者参考下面链接5.
ibd2sdi stu.ibd
// 部分字段相关内容如下所示
{
"name": "DB_TRX_ID",
"type": 10,
"is_nullable": false,
"is_zerofill": false,
"is_unsigned": false,
"is_auto_increment": false,
"is_virtual": false,
"hidden": 2,
"ordinal_position": 4,
"char_length": 6,
"numeric_precision": 0,
"numeric_scale": 0,
"numeric_scale_null": true,
"datetime_precision": 0,
"datetime_precision_null": 1,
"has_no_default": false,
"default_value_null": true,
"srs_id_null": true,
"srs_id": 0,
"default_value": "",
"default_value_utf8_null": true,
"default_value_utf8": "",
"default_option": "",
"update_option": "",
"comment": "",
"generation_expression": "",
"generation_expression_utf8": "",
"options": "",
"se_private_data": "table_id=1169;",
"engine_attribute": "",
"secondary_engine_attribute": "",
"column_key": 1,
"column_type_utf8": "",
"elements": [],
"collation_id": 63,
"is_explicit_collation": false
},
{
"name": "DB_ROLL_PTR",
"type": 9,
...
}
- DB_TRX_ID:最近修改事务ID,记录插入这条记录或者最后一次修改该记录的事务ID。
db_trx_id
是 MySQL InnoDB 存储引擎中的一个系统变量,用于表示当前事务的唯一标识符(Transaction ID)。每个事务在 InnoDB 存储引擎中都会被分配一个唯一的 db_trx_id
值。
db_trx_id
的作用是用于事务管理和并发控制。它主要用于以下方面:
- 事务标识:
db_trx_id
作为事务的标识符,用于唯一标识每个正在执行的事务。通过db_trx_id
,可以识别和区分不同的事务。 - 并发控制:
db_trx_id
在并发控制机制中起着重要的作用。通过比较不同事务的db_trx_id
值,可以确定事务的先后顺序,并根据事务的隔离级别进行相应的读取操作。 - MVCC(多版本并发控制):在 InnoDB 存储引擎中,
db_trx_id
与 MVCC 搭配使用。每个数据行都会记录其被修改的事务ID范围(最小和最大的db_trx_id
值)。通过比较事务的db_trx_id
值和数据行的事务ID范围,可以确定事务是否可以读取该数据行,以实现事务的隔离性和一致性。
总之,db_trx_id
是 InnoDB 存储引擎中用于标识事务和实现并发控制的重要变量。它在事务管理、并发控制和MVCC机制中起着关键的作用。
- DB_ROLL_PTR:回滚指针,执行该记录的上一个版本,用于配合undo log。
db_roll_ptr
是 MySQL InnoDB 存储引擎中的一个系统变量,表示回滚段指针(Rollback Segment Pointer)。它用于管理事务的回滚段。
回滚段是 InnoDB 存储引擎用于实现事务回滚操作的数据结构。当事务执行修改操作时,InnoDB会将旧数据的备份存储在回滚段中,以便在事务回滚时恢复数据。db_roll_ptr
存储了当前回滚段的指针位置,用于标识回滚段中的数据。
db_roll_ptr
的作用如下:
- 回滚段管理:
db_roll_ptr
用于管理回滚段的状态和位置。它指向当前正在使用的回滚段,以便在事务回滚时可以快速定位回滚段并进行数据恢复。 - 事务回滚:当事务需要回滚时,
db_roll_ptr
会指向回滚段中存储的旧数据,用于将数据恢复到事务开始之前的状态。 - 并发控制:
db_roll_ptr
在并发控制中起着重要的作用。通过比较不同事务的db_roll_ptr
值,可以确定事务的先后顺序,并根据事务的隔离级别进行相应的读取操作。
总之,db_roll_ptr
是 InnoDB 存储引擎中用于管理回滚段和实现事务回滚的系统变量。它在事务回滚、并发控制和MVCC机制中扮演着重要的角色。
- DB_ROW_ID:隐藏主键,当表没有指定主键的时候,系统会自动为每个表记录生成一个隐藏字段db_row_id,用来唯一标志一行。
隐藏字段 | 描述 |
---|---|
DB_TRX_ID | 最近修改事务ID,记录插入这条记录或者最后一次修改该记录的事务ID |
DB_ROLL_PTR | 回滚指针,执行该记录的上一个版本,用于配合undo log |
DB_ROW_ID | 隐藏主键,当表没有指定主键的时候,系统会自动为每个表记录生成一个隐藏字段db_row_id,用来唯一标志一行 |
3.3 undo log
回滚日志,在insert、update、delete的时候产生的便于数据回滚的日志。
当insert的时候,产生的undo log只在回滚的时候需要,在事务提交后,可被立即删除;而update、delete的到时候,产生的undo log日志,不仅在回滚的时候需要,在快照读的时候也需要,不会立即删除。
- undo log 版本链
不同事物或者相同事务对同一条记录进行修改,undo log会生成该记录的版本链表,链表表头是最新的旧记录,链表尾部是最早的旧记录。
3.4 readview
3.4.1 简介
Readview(读视图)是快照读 SQL执行时MVCC提取数据的依据,记录并维护系统当前活跃的事务(未提交)id。ReadView中包含4个核心字段:
字段 | 含义 |
---|---|
m_ids | 当前活跃事务ID集合 |
min_trx_id | 最小活跃事务id |
max_trx_id | 预分配事务ID,当前活跃事务最大id+1(事务ID自增) |
creator_trx_id | ReadView创建者事务ID |
3.4.2 版本链数据访问规则
不同的隔离级别,生成ReadView时机不同:
- READ COMMITED:在事务每一次执行快照读时生成ReadView;
- REPEATABLE READ:仅在事务第一次执行快照读时生成ReadView,后续复用该ReadView。
图片直接截图自视频,其中在事务5子在RC级别下第一次执行快照读的时候,左侧最新的记录即事务4修改那条记录应该还没有的。
在RR隔离级别下,第二次读取会复用第一次快照读生成的ReadView,即读取数据一致的。
结语
如果小伙伴什么问题或者指教,欢迎交流。
❓QQ:806797785
参考链接:
[1]MySQL数据库视频[CP/OL].2020-04-16.p138-146.
[2]Mysql事务的实现原理之Redo Log的分析
[3]mysql 物理日志之redo log(重做日志)原理和介绍
[4]mysql事务实现的原理(redo log,undo log详解)