首页  /  数据库  /  正文

InnoDB 存储引擎与事务原理

数据库 2026-10-02📖 20 分钟👁 —
🐬 数据库 · MySQL第 9 / 12 页123456789101112📖 完整导航 →

InnoDB 这块我花的时间最多,因为它讲的是引擎内部怎么运转,以理解为主,不是背参数。我的经验是:不要死记那些大小数值,重要的是能在脑子里建立画面——内存里有什么、磁盘上有什么、数据怎么流动,而且看完能自己讲出来才算过。

按小节来说,逻辑存储结构(表空间→段→区→页→行)理解层次就够了;内存/磁盘架构最重要,Buffer Pool、Log Buffer、redo log、undo log、doublewrite 这些要能说出谁负责干什么,后面监控和调参全靠它们;后台线程知道几个核心线程在干嘛就行;事务原理(redo / undo)是运维的命根子,得能用自己的话解释清楚。


一、逻辑存储结构

磁盘操作 IO 的本质是对表空间文件操作,其最小单元被分为页,所以磁盘操作几乎都是对页的操作。

表空间 - 段 - 区 - 页 - 行(这里的页和索引查询的叶子节点毫无关系,只是把 ibd 文件划分了多个层次)。

表空间、段、区、页、行是怎么一层套一层的

1KB=1024B

**1M=1024KB=1024\*1024B**

不需要记住具体大小。

MySQL 的数据文件在 /var/lib/mysql,这台机器上挂载到了 /opt/mysql/data:

root@VM-0-12-ubuntu:/opt/mysql/data# ll
total 107520
drwxr-xr-x 10 lxd  root       4096 Aug 22 20:41  ./
drwxr-xr-x  5 root root       4096 Aug 14 18:14  ../
drwxr-x---  2 lxd  docker     4096 Aug 21 14:16  adpart/
-rw-r-----  1 lxd  docker       56 Aug 14 18:33  auto.cnf
-rw-r-----  1 lxd  docker  2996555 Aug 14 18:33  binlog.000001
-rw-r-----  1 lxd  docker    36540 Aug 22 20:41  binlog.000002
-rw-r-----  1 lxd  docker     1120 Aug 24 17:47  binlog.000003
-rw-r-----  1 lxd  docker       48 Aug 22 20:41  binlog.index
-rw-------  1 lxd  docker     1709 Aug 14 18:33  ca-key.pem
-rw-r--r--  1 lxd  docker     1112 Aug 14 18:33  ca.pem
-rw-r--r--  1 lxd  docker     1112 Aug 14 18:33  client-cert.pem
-rw-------  1 lxd  docker     1705 Aug 14 18:33  client-key.pem
-rw-r-----  1 lxd  docker  4194304 Aug 24 21:11 '#ib_16384_0.dblwr'
-rw-r-----  1 lxd  docker 12582912 Aug 21 15:12 '#ib_16384_1.dblwr'
-rw-r-----  1 lxd  docker     5457 Aug 22 20:41  ib_buffer_pool
-rw-r-----  1 lxd  docker 12582912 Aug 24 21:09  ibdata1
-rw-r-----  1 lxd  docker 12582912 Aug 22 20:41  ibtmp1
drwxr-x---  2 lxd  docker     4096 Aug 22 20:41 '#innodb_redo'/
drwxr-x---  2 lxd  docker     4096 Aug 22 20:41 '#innodb_temp'/
drwxr-x---  2 lxd  docker     4096 Aug 18 21:51  itcast/
drwxr-x---  2 lxd  docker     4096 Aug 14 18:33  mysql/

# 表空间文件
-rw-r-----  1 lxd  docker 31457280 Aug 24 17:47  mysql.ibd
-rw-r-----  1 lxd  docker      466 Aug 24 18:57  mysql-slow.log
lrwxrwxrwx  1 lxd  docker       27 Aug 22 20:41  mysql.sock -> /var/run/mysqld/mysqld.sock
-rw-r-----  1 lxd  docker      132 Aug 14 18:33  mysql_upgrade_history
drwxr-x---  2 lxd  docker     4096 Aug 14 18:33  performance_schema/
-rw-------  1 lxd  docker     1705 Aug 14 18:33  private_key.pem
-rw-r--r--  1 lxd  docker      452 Aug 14 18:33  public_key.pem
-rw-r--r--  1 lxd  docker     1112 Aug 14 18:33  server-cert.pem
-rw-------  1 lxd  docker     1705 Aug 14 18:33  server-key.pem
drwxr-x---  2 lxd  docker     4096 Aug 14 18:33  sys/
drwxr-x---  2 lxd  docker     4096 Aug 16 13:46  test/
-rw-r-----  1 lxd  docker 16777216 Aug 24 17:49  undo_001
-rw-r-----  1 lxd  docker 16777216 Aug 24 21:11  undo_002


# 进入到具体的库,有很多表空间文件
root@VM-0-12-ubuntu:/opt/mysql/data/itcast# ll
total 1448
drwxr-x---  2 lxd docker   4096 Aug 18 21:51 ./
drwxr-xr-x 10 lxd root     4096 Aug 22 20:41 ../
-rw-r-----  1 lxd docker 114688 Aug 19 13:43 account.ibd
-rw-r-----  1 lxd docker 114688 Aug 17 22:10 course.ibd
-rw-r-----  1 lxd docker 131072 Aug 17 18:58 dept.ibd
-rw-r-----  1 lxd docker 131072 Aug 17 21:54 emp.ibd
-rw-r-----  1 lxd docker 114688 Aug 17 18:27 emp_test.ibd
-rw-r-----  1 lxd docker 114688 Aug 24 21:09 score.ibd
-rw-r-----  1 lxd docker 147456 Aug 17 22:10 student_course.ibd
-rw-r-----  1 lxd docker 114688 Aug 17 22:10 student.ibd
-rw-r-----  1 lxd docker 131072 Aug 17 22:23 tb_user_edu.ibd
-rw-r-----  1 lxd docker 114688 Aug 17 22:23 tb_user.ibd
-rw-r-----  1 lxd docker 114688 Aug 16 17:00 test_emp.ibd
-rw-r-----  1 lxd docker 131072 Aug 17 17:44 user.ibd

二、架构

1. 内存结构

先看架构图,左边是内存结构,右边是磁盘结构:

InnoDB 架构图,左边内存结构、右边磁盘结构

修改 SQL 操作的是缓冲池,不是磁盘:没有数据就从磁盘加载并缓存,在缓冲池里改,变成脏页再写回磁盘。

Buffer Pool 是主内存里的一块区域,里面的 Page 分 free、clean、dirty 三种

缓冲池和更改缓冲区都是降低 IO 的,只不过非唯一二级索引随机 IO 代价太高了,才需要单独设立一个更改缓冲区。

Change Buffer 针对非唯一二级索引,把变更先攒着,等数据被读时再合并

针对缓冲池的查询,如果系统判断哈希查询效率高于索引查询,就用哈希。

Adaptive Hash Index 自适应哈希索引,系统自动建,不用人工干预

日志缓冲区,后面会详细介绍。

Log Buffer 用来存要写到磁盘的日志数据,默认 16MB,两个参数管大小和刷盘时机
show variables like 'innodb_log_buffer_size';
innodb_log_buffer_size 查出来是 67108864
show variables like 'innodb_flush_log_at_trx_commit';
innodb_flush_log_at_trx_commit 查出来是 1

2. 磁盘结构

磁盘操作 IO 的本质是对表空间文件操作,其最小单元被分为页,所以磁盘操作几乎都是对页的操作。

root@VM-0-12-ubuntu:/opt/mysql/data# ll
total 107520
drwxr-xr-x 10 lxd  root       4096 Aug 22 20:41  ./
drwxr-xr-x  5 root root       4096 Aug 14 18:14  ../
drwxr-x---  2 lxd  docker     4096 Aug 21 14:16  adpart/
-rw-r-----  1 lxd  docker       56 Aug 14 18:33  auto.cnf
-rw-r-----  1 lxd  docker  2996555 Aug 14 18:33  binlog.000001
-rw-r-----  1 lxd  docker    36540 Aug 22 20:41  binlog.000002
-rw-r-----  1 lxd  docker     1120 Aug 24 17:47  binlog.000003
-rw-r-----  1 lxd  docker       48 Aug 22 20:41  binlog.index
-rw-------  1 lxd  docker     1709 Aug 14 18:33  ca-key.pem
-rw-r--r--  1 lxd  docker     1112 Aug 14 18:33  ca.pem
-rw-r--r--  1 lxd  docker     1112 Aug 14 18:33  client-cert.pem
-rw-------  1 lxd  docker     1705 Aug 14 18:33  client-key.pem
-rw-r-----  1 lxd  docker  4194304 Aug 24 21:11 '#ib_16384_0.dblwr'
-rw-r-----  1 lxd  docker 12582912 Aug 21 15:12 '#ib_16384_1.dblwr'
-rw-r-----  1 lxd  docker     5457 Aug 22 20:41  ib_buffer_pool
-rw-r-----  1 lxd  docker 12582912 Aug 24 21:09  ibdata1
-rw-r-----  1 lxd  docker 12582912 Aug 22 20:41  ibtmp1
drwxr-x---  2 lxd  docker     4096 Aug 22 20:41 '#innodb_redo'/
drwxr-x---  2 lxd  docker     4096 Aug 22 20:41 '#innodb_temp'/
drwxr-x---  2 lxd  docker     4096 Aug 18 21:51  itcast/
drwxr-x---  2 lxd  docker     4096 Aug 14 18:33  mysql/
-rw-r-----  1 lxd  docker 31457280 Aug 24 17:47  mysql.ibd
-rw-r-----  1 lxd  docker      466 Aug 24 18:57  mysql-slow.log
lrwxrwxrwx  1 lxd  docker       27 Aug 22 20:41  mysql.sock -> /var/run/mysqld/mysqld.sock
-rw-r-----  1 lxd  docker      132 Aug 14 18:33  mysql_upgrade_history
drwxr-x---  2 lxd  docker     4096 Aug 14 18:33  performance_schema/
-rw-------  1 lxd  docker     1705 Aug 14 18:33  private_key.pem
-rw-r--r--  1 lxd  docker      452 Aug 14 18:33  public_key.pem
-rw-r--r--  1 lxd  docker     1112 Aug 14 18:33  server-cert.pem
-rw-------  1 lxd  docker     1705 Aug 14 18:33  server-key.pem
drwxr-x---  2 lxd  docker     4096 Aug 14 18:33  sys/
drwxr-x---  2 lxd  docker     4096 Aug 16 13:46  test/
-rw-r-----  1 lxd  docker 16777216 Aug 24 17:49  undo_001
-rw-r-----  1 lxd  docker 16777216 Aug 24 21:11  undo_002
系统表空间和独立表空间,每个表一个 ibd 文件
# 系统表空间 /opt/mysql/data 下,即 /var/lib/mysql 下
-rw-r-----  1 lxd  docker 12582912 Aug 24 21:09  ibdata1

# 独立表空间文件(每个库内的ibd和mysql.ibd)
drwxr-x---  2 lxd  docker     4096 Aug 21 14:16  adpart/
drwxr-x---  2 lxd  docker     4096 Aug 18 21:51  itcast/
-rw-r-----  1 lxd  docker 31457280 Aug 24 17:47  mysql.ibd
通用表空间、撤销表空间和临时表空间
# undo 撤销表空间
-rw-r-----  1 lxd  docker 16777216 Aug 24 17:49  undo_001
-rw-r-----  1 lxd  docker 16777216 Aug 24 21:11  undo_002

# 临时表空间
-rw-r-----  1 lxd  docker 12582912 Aug 22 20:41  ibtmp1
双写缓冲区和 redo log 文件
# redo log 重做日志
drwxr-x---  2 lxd  docker     4096 Aug 22 20:41 '#innodb_redo'/

# 双写缓冲区:存放脏页的位置
-rw-r-----  1 lxd  docker  4194304 Aug 24 21:11 '#ib_16384_0.dblwr'
-rw-r-----  1 lxd  docker 12582912 Aug 21 15:12 '#ib_16384_1.dblwr'

3. 后台线程

内存数据刷新到磁盘,中间要经过操作系统的这一层:

架构图里红框圈的就是操作系统缓存这一层,内存结构通过它落到磁盘

涉及到的线程有这么几个:

后台线程的分工,Master、IO、Purge、Page Cleaner

总结一下:InnoDB 引擎的体系结构就是,业务操作(增删改查)操作的是缓冲区,缓冲区没有数据就把磁盘数据加载到这里再操作。缓冲区的数据会按一定频率通过后台线程刷新到磁盘,在磁盘上做数据持久化(数据、索引等)。


三、事务原理

1. 介绍

事务的定义和 ACID 四个特性

2. redolog 和 undo log

原子性、一致性、持久性靠 redo log 和 undo log,隔离性靠锁和 MVCC

redo log

主要用于脏页刷新到磁盘发生错误时进行数据恢复,保证事务持久性。

redo log 的原理,改的是 Buffer Pool,redo log buffer 先落盘,走 WAL
  1. 进行操作
  2. 缓冲区如果没东西,会从磁盘读,然后进行增删改查变为脏页
  3. 记录数据页变化到 redo log
  4. 变化日志写入磁盘中(顺序磁盘 IO,性能高)
  5. 变更数据写入磁盘(随机磁盘 IO,性能低)

每隔一段时间就会清理不需要的 redo log 日志:

ib_logfile0 和 ib_logfile1 是循环写的

redo log 文件大小固定,自动删除最久的数据。

磁盘操作 IO 的本质是对表空间文件操作,其最小单元被分为页,所以磁盘操作几乎都是对页的操作。

重做缓冲文件在缓冲区,没办法演示,了解即可。

# redo log日志位置
drwxr-x---  2 lxd  docker     4096 Aug 22 20:41 '#innodb_redo'/

root@VM-0-12-ubuntu:/opt/mysql/data/#innodb_redo# ll
total 102408
drwxr-x---  2 lxd docker    4096 Aug 22 20:41  ./
drwxr-xr-x 10 lxd root      4096 Aug 22 20:41  ../
-rw-r-----  1 lxd docker 3276800 Aug 22 20:41 '#ib_redo10_tmp'
-rw-r-----  1 lxd docker 3276800 Aug 22 20:41 '#ib_redo11_tmp'
-rw-r-----  1 lxd docker 3276800 Aug 22 20:41 '#ib_redo12_tmp'
-rw-r-----  1 lxd docker 3276800 Aug 22 20:41 '#ib_redo13_tmp'
-rw-r-----  1 lxd docker 3276800 Aug 22 20:41 '#ib_redo14_tmp'
-rw-r-----  1 lxd docker 3276800 Aug 22 20:41 '#ib_redo15_tmp'
-rw-r-----  1 lxd docker 3276800 Aug 22 20:41 '#ib_redo16_tmp'
-rw-r-----  1 lxd docker 3276800 Aug 22 20:41 '#ib_redo17_tmp'
-rw-r-----  1 lxd docker 3276800 Aug 22 20:41 '#ib_redo18_tmp'
-rw-r-----  1 lxd docker 3276800 Aug 22 20:41 '#ib_redo19_tmp'
-rw-r-----  1 lxd docker 3276800 Aug 22 20:41 '#ib_redo20_tmp'
-rw-r-----  1 lxd docker 3276800 Aug 22 20:41 '#ib_redo21_tmp'
-rw-r-----  1 lxd docker 3276800 Aug 22 20:41 '#ib_redo22_tmp'
-rw-r-----  1 lxd docker 3276800 Aug 22 20:41 '#ib_redo23_tmp'
-rw-r-----  1 lxd docker 3276800 Aug 22 20:41 '#ib_redo24_tmp'
-rw-r-----  1 lxd docker 3276800 Aug 22 20:41 '#ib_redo25_tmp'
-rw-r-----  1 lxd docker 3276800 Aug 22 20:41 '#ib_redo26_tmp'
-rw-r-----  1 lxd docker 3276800 Aug 22 20:41 '#ib_redo27_tmp'
-rw-r-----  1 lxd docker 3276800 Aug 22 20:41 '#ib_redo28_tmp'
-rw-r-----  1 lxd docker 3276800 Aug 22 20:41 '#ib_redo29_tmp'
-rw-r-----  1 lxd docker 3276800 Aug 22 20:41 '#ib_redo30_tmp'
-rw-r-----  1 lxd docker 3276800 Aug 22 20:41 '#ib_redo31_tmp'
-rw-r-----  1 lxd docker 3276800 Aug 22 20:41 '#ib_redo32_tmp'
-rw-r-----  1 lxd docker 3276800 Aug 22 20:41 '#ib_redo33_tmp'
-rw-r-----  1 lxd docker 3276800 Aug 22 20:41 '#ib_redo34_tmp'
-rw-r-----  1 lxd docker 3276800 Aug 22 20:41 '#ib_redo35_tmp'
-rw-r-----  1 lxd docker 3276800 Aug 22 20:41 '#ib_redo36_tmp'
-rw-r-----  1 lxd docker 3276800 Aug 22 20:41 '#ib_redo37_tmp'
-rw-r-----  1 lxd docker 3276800 Aug 22 20:41 '#ib_redo38_tmp'
-rw-r-----  1 lxd docker 3276800 Aug 22 20:41 '#ib_redo39_tmp'
-rw-r-----  1 lxd docker 3276800 Aug 22 20:41 '#ib_redo40_tmp'

# 正式的redo log文件,大小完全固定:重做日志文件
-rw-r-----  1 lxd docker 3276800 Aug 24 21:11 '#ib_redo9'

undo log

保证事务的原子性,还参与了 MVCC,是事务隔离性的一部分(MVCC + 锁)。

undo log 提供回滚和 MVCC,是逻辑日志,存在回滚段里

四、MVCC

MVCC 讲的就是读(快照读)数据的原理。

1. 基本概念

先复习事务隔离级别:

隔离级别脏读不可重复读幻读
read uncommitted 读未提交√√√
read committed读已提交(Redis 默认)×√√
repeatable read可重复读(MySQL 默认)××√
serializable串行化×××

MySQL 默认是可重复读,只能读取到开启事务时的快照。

当前读读的是记录的最新版本,读取时还要保证其他并发事务不能修改当前记录,所以会对读取的记录加锁:

当前读的定义,lock in share mode 和 for update 都算

操作演示:

# 开启事务 1
begin;
select * from stu;

# 开启事务 2
begin;
update stu set name = 'jsp' where id = 1;
commit;

# 在事务 1
select * from stu;
# 此时读取到和第三行一样的数据,因为mysql默认隔离界别是可重复读

# 此时加上锁进行当前读
# 在事务 1
select * from stu lock in share mode;
# 此时读取了最新数据

快照读就是简单的 select(不加锁),读的是记录数据的可见版本,有可能是历史数据,不加锁、非阻塞读:

快照读在三种隔离级别下的差别

快照读就是上面没加锁读到的情况,可重复读。日常查询用快照读(MVCC),不加锁、高性能;写入和显式加锁查询用当前读(锁机制),保证数据一致性。

MVCC 全称 Multi-Version Concurrency Control,靠三个隐藏字段、undo log 和 ReadView 实现

2. 隐藏字段

表结构的三个隐藏字段 DB_TRX_ID、DB_ROLL_PTR、DB_ROW_ID

查看表空间文件的详细信息:

ibd2sdi [表空间文件名]
# 需要一个软件
sudo apt update && sudo apt install -y mysql-server-core-8.0

# 截取部分信息
root@VM-0-12-ubuntu:/opt/mysql/data/itcast# ibd2sdi score.ibd 

    "dd_object_type": "Table",
    "dd_object": {
        "name": "score",
        "mysql_version_id": 260700,
        "created": 20260824094307,
        "last_altered": 20260824094307,
        "hidden": 1,
        "options": "avg_row_length=0;encrypt_type=N;key_block_size=0;keys_disabled=0;pack_record=1;stats_auto_recalc=0;stats_sample_pages=0;",
        "columns": [
        # 这里存放了各字段的详细信息


# 最后一次操作事务的ID
            {
                "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": 8,
                "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": "physical_pos=1;table_id=1077;",
                "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,
                "is_nullable": false,
                "is_zerofill": false,
                "is_unsigned": false,
                "is_auto_increment": false,
                "is_virtual": false,
                "hidden": 2,
                "ordinal_position": 9,
                "char_length": 7,
                "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": "physical_pos=2;table_id=1077;",
                "engine_attribute": "",
                "secondary_engine_attribute": "",
                "column_key": 1,
                "column_type_utf8": "",
                "elements": [],
                "collation_id": 63,
                "is_explicit_collation": false
            }


# 没有主键,自动生成ROW-ID
            {
                "name": "DB_ROW_ID",
                "type": 10,
                "is_nullable": false,
                "is_zerofill": false,
                "is_unsigned": false,
                "is_auto_increment": false,
                "is_virtual": false,
                "hidden": 2,
                "ordinal_position": 7,
                "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": "physical_pos=0;table_id=1077;",
                "engine_attribute": "",
                "secondary_engine_attribute": "",
                "column_key": 1,
                "column_type_utf8": "",
                "elements": [],
                "collation_id": 63,
                "is_explicit_collation": false
            },

3. undo log 版本链和 readview 介绍

undo log 版本链

回滚日志在 insert、update、delete 的时候产生,是便于数据回滚的日志:

insert 产生的 undo log 提交后可以立即删,update/delete 的要留给快照读
不同事务改同一条记录,undo log 会串成一条版本链

不同事务或相同事务对同一条记录进行修改,会导致该记录的 undolog 生成一条记录版本链表,链表的头部是最新的旧记录,链表尾部是最早的旧记录。

readview 读视图

ReadView(读视图)是快照读 SQL 执行时 MVCC 提取数据的依据,记录并维护系统当前活跃的事务(未提交的)id。它包含四个核心字段:

ReadView 的四个核心字段 m_ids、min_trx_id、max_trx_id、creator_trx_id
字段含义
m_ids当前活跃的事务ID集合
min_trx_id最小活跃事务ID
max_trx_id预分配事务ID,当前最大事务ID+1(因为事务ID是自增的)
creator_trx_idReadView创建者的事务ID

版本链数据访问规则:

版本链的访问规则,四条判断决定能不能读到这个版本

不同的隔离级别,生成 ReadView 的时机不同:

RC 每次快照读都生成 ReadView,RR 只在第一次生成

4. 原理分析(RC 隔离级别——读已提交)

读已提交:每次快照读都生成 readview。

RC 下事务 5 两次查询各生成一个 ReadView
ReadView 里就是那四个字段的值

这几个键分别代表:

拿这套规则去比对版本链,只有版本 2 是满足的,快照读就返回这条记录:

RC 下拿 ReadView 去匹配版本链,只有 2 满足

5. 原理分析(RR 隔离级别——可重复读)

还是按生成的 readview 匹配规则来,只不过 RR 只返回一次 readview;既然 readview 都一样了,那返回的数据肯定是一样的。

RR 下只有第一次快照读生成 ReadView,后面全部复用

6. 总结

MVCC 就是隐藏字段、undo log 版本链、ReadView 三样加锁

MVCC 只负责隔离性,而 redo log 和 undo log 负责一致性。

ACID 特性含义InnoDB 实现机制MVCC 在其中扮演的角色
原子性(Atomicity)事务要么全部成功,要么全部回滚Undo Log(回滚时逆向恢复)无关(MVCC 不负责回滚)
一致性(Consistency)事务前后数据状态合法(如约束、触发器)由应用层 + 数据库约束(主键/外键/唯一)共同保证无关(MVCC 不负责数据合法性)
隔离性(Isolation)并发事务之间互不干扰MVCC + 锁机制(行锁/间隙锁)核心实现
持久性(Durability)事务提交后数据永久保存Redo Log(崩溃恢复重做)无关(MVCC 不负责持久化)

MVCC 和锁如何实现事务隔离性:

操作类型使用的技术是否加锁目的
普通 SELECT(快照读)仅 MVCC不加锁读取历史版本,不阻塞写操作
写操作(INSERT/UPDATE/DELETE) 和 SELECT ... FOR UPDATE锁机制加锁(行锁/间隙锁)阻止并发修改同一行,保证写-写隔离
读操作遇到写冲突(如可重复读)MVCC + 锁不加锁,但通过 MVCC 读到旧版本读不阻塞写,也不被写阻塞

关键点:

InnoDB 这块的小结,逻辑存储结构、架构、事务原理、MVCC