存储引擎
InnoDB和MyISAM
| 区别 | InnodB | myisam |
|---|---|---|
| 事务 | 支持 | 不支持 |
| 索引 | 聚集索引 | 非聚集索引(见B+树部分) |
| 锁 | 最小是行锁 | 最小是表锁 |
| count | 不保存行数 | 保存,查行数很快 |
| 恢复 | 支持redo | 不支持 |
索引
索引是什么?就是数据库的目录
索引的优缺点
优点:快速检索,降低磁盘IO
缺点:索引可能失效、占用额外的存储空间
索引的分类
聚集索引:数据的物理顺序和列值的逻辑顺序相同(主键)
查询的时候,因为聚集索引 B+ 树的叶子节点存储了完整数据,直接查就行
非聚集索引:就是顺序没关系(除了聚集就全是非聚集)
查询的时候,因为非聚集索引 B+ 树的叶子节点存储的是 索引值 和 对应主键值
1、需要先在这个树找到该行的主键值
2、使用主键值,去聚集索引 B+ 树二次查找
这个过程就叫做「回表」
唯一索引:用UNIQUE INDEX变成唯一索引,唯一索引是非聚集索引的一种,作用在于主要用于保证这个索引数据的业务唯一性。
索引的数据结构
是树,因为树天生有二分查找的特性,查询速度高
索引失效
索引失效就是违反了数据库使用索引的原则,转变成了全表扫描
查询条件类
1、Like通配符前置
LIKE '%关键字这个没什 是左侧顺序匹配的,相当于词典,只给右边的字母显然不能找到索引。
!!!!2、最左前缀原则!!!!!
在使用联合索引的时候,只有带着最左边的索引成分的时候,索引才能正常使用。
索引失效的时候,查询的性能就变成了全表扫描,效率慢。
CREATE INDEX idx_col ON table (a, b, c);a是最左边
WHERE a = 1 AND b = 2可以,因为有a,索引成立
WHERE b = 2 AND c = 3不可以,因为没有a,索引失效
为什么:
比如说三级索引,firstname、lastname、age
| 索引字段 | 对应书架分类 | 作用 |
|---|---|---|
first_name (第一级) | 大区域标签 | 决定了所有书籍的宏观顺序。 |
last_name (第二级) | 区域内小隔断 | 只有在大区域(first_name)相同的书堆中,才按照 last_name 排序。 |
age (第三级) | 最小的书签 | 只有在前两级都相同的情况下,才按照 age 排序。 |
如果没有第一级的索引firstname,那通过lastname或age查询,是用不上索引的,因为第二级的索引只有在第一级索引的这个大区域下才有序,全局是无序的,所以失效了
范围查询的时候:
如果abc联合索引,在这个查询中,a 索引通过 IN 语句生效,用于多点定位。b 索引虽然生效,但它是一个范围查询。在联合索引中,一旦遇到范围查询,B+树的加速查找能力就会中断。因为 b 已经是范围,后续字段c在这个范围内是无序的,无法继续利用索引的排序特性来加速定位,因此c索引失效。
3、否定条件
比如 != not in,范围太大,使用索引成本高于直接进行全表扫描。
操作索引类
1、对索引进行运算
where DATE(time) = '2025-1-1'因为这相当于对索引列的所有数据都计算一遍,等同于全表扫描。
2、索引隐形转换
比如id是int,结果和string类型比较,内部转换,同1。
联合索引的存储方式
(first\_name, last\_name, age, id),查然后回表
联合索引的 B+树最关键的一点在于,它的所有叶子节点(也就是数据块)是严格按照从左到右的字段顺序进行物理排序的。
MYSQL索引为什么使用 B+树?
平衡二叉树AVL:AVL实现平衡主要是靠旋转,旋转是非常耗时的,所以不能用(增加和删除)
红黑树:和AVL相比,红黑树不追求完美的平衡,只是大致的平衡。对比AVL,查询略降,删除大提高
因为红黑树同时引入了颜色,当插入或删除数据时,只需要进行 O(1)次数的旋转以及变色就能保证基本的平衡,不需要像 AVL 树进行 O(lgn)次数的旋转。总的来说, 红黑树的统计性能高于 AVL。
红黑树应用:HashMap链表+红黑树解决哈希冲突 ,TreeMap 红黑树存储排序键值对
在“高且瘦”的树中,你每次比较都可能需要访问一个新的节点。如果每个节点都位于磁盘的不同页(Page)上,那么查找一个数据就可能需要很多次磁盘I/O。比如树高20,就需要最多20次I/O,这无法接受。
B树:矮胖树,一个节点可以有多个子节点,每个节点都会存储数据本身,节点内的键值是排好序的,节点之间也满足排序关系。
B+树直接看和B树的区别
数据存储:B+树的Data只存在于叶子节点中,非叶子节点只有索引。(B树什么节点都存数据)由于B+树非叶子节点不存Data,所以每个节点能存的索引更多,树更矮。
B 树的非叶子节点结构大致是:[P_1, (K_1, D_1), P_2, (K_2, D_2), ]
它在每个节点中,不仅要存储 Key(索引),还要存储 Data(数据)。B+ 树的非叶子节点结构大致是:[P_1, K_1, P_2, K_2, ]
它只存储 Key(索引)和指向子节点的指针。索引(Key): 通常是固定长度或较短的数值类型(例如,一个
BIGINT类型的主键大约是 8 字节)。数据(Data):
在聚簇索引中,数据是一整行记录,可能包含几十个甚至上百个字段,大小通常从几百字节到几 KB 不等。
Data,可能是行的全部数据(如 Innodb 的聚簇索引),也可能只是行的主键(如 Innodb 的辅助索引),或者是行所在的地址(如 MyIsam 的非聚簇索引)。
叶子结点:B+树中有叶子节点有指针,叶子节点之间通过指针链接起来形成一个有序链表(B树叶子节点是相互独立的)
因此,B+树优势:
数据存储→1、因为树的结构更矮胖,磁盘IO次数少(读写)
叶子节点→2、因为叶子节点是通过双向链表连接的,查询到一条数据就可以沿着链表的上下直接顺序查询,IO路径规律,范围查询效率更高
SELECT * FROM users WHERE age BETWEEN 20 AND 30
IO路径规律——>磁头不需要大动
叶子节点→3、因为双向链表和Data存于叶子,全表遍历效率高,只需要遍历链表就行
叶子节点→4、因为Data都存在于叶子节点里面,最差的情况就是一般情况,查询性能稳定。
SQL注入
允许攻击者将恶意的 SQL 代码插入到输入字段中,以操纵数据库。 核心原因:程序将用户输入的数据未经充分检查或处理,直接拼接到SQL语句中。
预编译(参数化查询),用户输入始终视为 字符串/数字,而非可执行代码。 MyBatis:务必使用 #{} 语法(参数化查询),禁止使用 ${}(字符串替换,等同于拼接)。
锁
表级锁和行级锁的区别
| qubie | 表级锁 | 行级锁 |
|---|---|---|
| 范围 | 锁整张表 | 锁当前字段 |
| 实现 | 简单,加锁快 | 困难,加锁慢 |
| 与引擎 | 无关 | 有关,因引擎 |
| 谁有 | MYISAM、InnoDB | InnoDB(默认行) |
InnoDB的几种表级锁
表锁
表级读锁:阻止其他的对表的写
表级写锁:阻止其他对表的读写
元数据锁MDL
作用:防止执行CRUD的时候表结构变更
数据库本身会加MDL,不用我们来操作
写锁获取优先级高于读锁
- 对一张表进行 CRUD 操作时,加的是 MDL 读锁;
- 对一张表做结构变更操作的时候,加的是 MDL 写锁;
那它在什么时候释放?-->在事务提交之后
意向锁
作用:为了快速判断表里是否有行锁
为什么要判断?因为读锁和行锁是读写互斥,写写互斥的
在加上行共享锁之前,先在表级别加上一个“意向共享锁”
在加上行独占锁之前,先在表级别加上一个“意向独占锁”
这里面谈到普通的select语句是不算里面的,这类是通过MVCC实现的一致性,无锁
InnoDB的几种行级锁
锁的兼容性分类
共享锁S:读锁,事务在读取记录的时候获取共享锁,允许多个事务同时获取锁
排它锁X:写锁,事务在修改记录的时候获取排它锁,不允许多个事务.....。
锁的范围分类
记录锁:锁定单个记录。
间隙锁:锁定一个id数字集合(范围),不包括记录本身。
前开后开
临键锁:记录锁+间隙锁,锁定一个范围,包含记录本身,主要目的是为了解决幻读问题。
前开后闭
什么时候加行级锁?
普通的 select 语句是不会对记录加锁的,MVCC实现一致性
首先记住,加锁的对象都是索引!!!,基本单位就是默认的next-key lock
唯一索引等值查询情况
- 查询的记录 存在,定位到记录之后,退化成 X记录锁
- 查询的记录 不存在,定位到第一个大于查询记录的记录之后,退化成 间隙锁,锁住这个记录和上一条记录的范围
- 思考:不沿用next-key lock的原因是因为这个场景下幻读只需要间隙锁就能解决了
为什么 id = 5 记录上的主键索引的锁不可以是 next-key lock?如果是 next-keylock,就意味着其他事务无法删除 id = 5 这条记录,但是这次的案例是查询 id = 2 的记录,只要保证前后两次查询 id = 2 的结果集相同,就能避免幻读的问题了。
唯一索引范围查询情况
先说明范围查询的整体机制:
会对每一个扫描到的索引加next-key lock,下面细分情况
实际在 Innodb 存储引擎中,会用一个特殊的记录来标识最后一条记录,该特殊的记录的名字叫supremum pseudo-record
- 大于情况:往后推,前加一个包含自己的,后面以此递归最后扫到supremum加 (xxxx, +∞]
- 大于等于情况:等值的地方变成记录锁
- 小于或小于等于情况:条件值不在表里,终止扫描记录的Next-Key锁退化成间隙锁
- 小于情况:条件值在表里,终止扫描记录的Next-Key锁退化成间隙锁
- 小于等于情况:条件值在表里,终止扫描记录的Next-Key锁不退化成间隙锁
举例:有个表有4个数据项
| id | name |
|---|---|
| 10 | A |
| 20 | B |
| 30 | C |
| 40 | D |
情况5:10满足加(Previous, 10],20满足加(10, 20],30满足加(20, 30],到边界了,终止扫描
SELECT * FROM products WHERE id <= 30;情况4:10满足加(Previous, 10],20满足加(10, 20],30不满足,退化,加(20, 30)
SELECT * FROM products WHERE id < 30;情况1:定位到30,加(20, 30],40满足加(30, 40],接着扫描,加锁(40, +∞]
SELECT * FROM products WHERE id > 20;情况2:20,临键锁退化,变成记录锁锁20,接着同情况1
SELECT * FROM products WHERE id >= 20;情况3:10满足加(Previous, 10],20满足加(10, 20],然后到30,不满足,退化,加(20, 30)
SELECT * FROM products WHERE id <(<=) 25;什么时候加表级锁?
当执行UPDATE、DELETE的语句的时候,WHERE没有命中索引或者索引失效的时候,就会对全表扫描加锁。
事务
四大特性
原子性:事务是最小的执行单位,不能分割 --- undo log保证的
隔离性:并发访问数据库的时候,事务彼此之间是不可见的(取决于事务隔离级别)
比如先后扣款,先扣款事务还没做完,后扣款事务来了看到的也是原数量
持久性:一个事务被提交之后,他对数据库的改变是永久的,即使数据库发生故障。 --- redo log保证的
一致性:执行事务前后,数据保持一致(指变化总和)
并发事务带来的问题(问题)
脏读
一个事务读取数据并且修改了这个数据,这个时候第二个事务读取了这个未提交的数据,但是第一个事务突然回滚,导致改后的数据没有到数据库里面,最后第二个事务读到了脏数据
丢失修改
一个事务在读取一个数据的时候,另一个事务读取这个数据,第一个事务修改这个数据,第二个数据也修改这个数据,但是数据最后相当于只修改了一次,丢失了一次修改
性质同脏读一样。
不可重复读
指一个事务在事务内多次读一个数据,但是在中间间隙中来了第二个事务,修改了数据,导致第一个事务在事务内前后两次读到的数据不一样
幻读
意思就是一个事务读了几条数据,在他第二次读之前中间来了第二个事务插入了几条数据,导致第一个事务再读的时候像出现了幻觉一样多了几条数据
不可重复读、幻读的区别
实际上幻读就是不可重复读特殊情况,不可重复读强调某一条数据被修改了,幻读强调对数据行数的修改上(变多或者变少)
之所以区分是因为解决他们的方案不一样
标准事务隔离级别(解决)
是用来平衡隔离性和并发性能的东西
级别越高,数据一致性越好,但并发性能可能越低。
- 读取未提交:允许读取未提交的数据变更,啥都防不住
- 读取已提交:允许读取并发事务已经提交的数据,可以阻止脏读
- 可重复读:对同一个字段的多次读取都是一样的,除非是自身修改,阻止了脏读和不可重复读,至于幻读,仍有可能(InnoDB大幅减少)
一旦事务开始,它就仿佛进入了一个“时间凝固”的快照中* - 可串行化:最高隔离,依次执行,全都防住
READ-UNCOMMITTED
READ-COMMITTED
REPEATABLE-READ
SERIALIZABLE
Mysql的默认隔离级别
Mysql InnoDB引擎 的默认隔离级别是可重复读。
InnoDB怎么解决大部分幻读的
快照读:普通的SELECT的语句,通过MVCC实现,事务启动时创建一个数据快照,后续的快照读都读取这个版本的数据,这样避免了看到其他事务插进来的行(幻读)和修改的行(不可重复读)
当前读:除了普通查询之外的操作读取到的都是最新数据而不是快照数据,InnoDB用临键锁next-key lock防止其他事物在这个范围内插入新的记录(幻读)
(像SELECT ... LOCK IN SHARE MODE(加S锁),SELECT ... FOR UPDATE,INSERT,UPDATE,DELETE(加X锁)这些操作)
正因为这样的机制,InnoDB在可重复读(RR)级别的性能,在只读或者读多写少的情况下,和读取已提交(RC)的性能差不多。在写密集的时候,RR的间隙锁可能会导致它比RC出现更多的锁等待
Mysql的隔离级别怎么实现的
由锁和 MVCC 共同实现的
读取未提交:啥也不用干(实际上InnoDB根本不支持,设置也会自动变成读取已提交)
读取已提交:通过Read View实现的,实际上是高级的版本号
可重复读:通过Read View实现的,实际上是高级的版本号
可串行化:完全依赖于锁
Read View是如何工作的?
是什么东西?
他有四个字段:
- m_ids是一个活跃事务id列表
- min_trx_id活跃事务中id最小的事务
- max_trx_id是活跃事务最大id的下一个事务的id值,也就是最大事务id+1
- creator_trx_id是创建这个Read View的事务id
活跃事务:运行中且未提交的事务
怎么解决并发的
RR可重复读实现
一个事务进行快照读的时候,会先创建一个Read View来判断在做后续的操作的时候,其他事务自己能不能可见,这里有个先后顺序,也就是说先创建快照,再执行查询。
有几种情况:(trx _id是其他事务的id)
- 如果记录的trx_id小于Read View中的min_trx_id,说明这个事务是在这view版本创建的时候就已经结束了。可见
- 如果记录的trx_id大于等于Read View中的max_trx_id,说明了这个事务是在View 快照之后才开始的事务。不可见
- 如果记录的trx_id在min_trx_id和max_trx_id之间,先判断trx_id在不在m_ids中
- 在:表示生成该版本的记录的活跃事务还没提交,所以不可见(还在忙活,可能回滚)
- 不在:表示生成该版本的记录的活跃事务已经提交,所以可见(忙活完了,间隙进来的) 在创建快照和执行查询的间隙进来的
RC读以提交实现
创建快照是每次执行SELECT都创建一个read view快照,有不可重复读的情况,因为没有解决那个间隙问题。
MVCC是什么
上面这个版本链条控制并发事务同时访问一个记录的解决方案就是MVCC,也就是:版本链(Version Chain)+读视图(Read View)。
MVCC回答概述
版本链:首先,在InnoDB中,每一行数据都有两个隐藏列:trx_id(最后修改该行的事务ID)和roll_ptr(回滚指针)。通过回滚指针,InnoDB将一行数据的多个历史版本在undo log中串联起来,形成版本链。
读视图:其次,当一个事务发起快照读时,MVCC会根据该事务的隔离级别(读已提交或可重复读)生成一个Read View。这个Read View定义了一套可见性规则,事务会用这个Read View去匹配版本链,从而找到对当前事务可见的那个特定版本的数据。”
有锁为什么还要MVCC
单纯依赖锁,性能实在太差了。互斥互斥等待等待。
“单纯使用锁机制,并发性能会很差。即使是读写锁,读和写操作之间仍然是互斥的。数据库作为高性能中间件,如果一个写操作就导致所有读操作被阻塞,这种性能损失是无法接受的。因此,InnoDB引擎引入了MVCC,其核心目的就是通过空间换时间的方式,实现读写操作的并发执行,极大地提升了数据库的并发处理能力。
读操作的时候
使用快照读取。快照读取基于事务开始的时候数据库的状态来创建,因此事务不会读取其他事务未提交的修改,具体情况:
读取的时候,事务查找符合条件的数据行,选择符合事务开始时间的版本读取,如果有多个版本,不晚于开始时间的最新版本。
写操作的时候
生成一个新的数据版本,并且把修改后的数据写入数据库,具体情况:
写的时候,事务会为要修改的数据行创建一个新的版本,并且把修改后的数据写入新版本,新版本的数据有当前事务的版本号,以便其他事务能读取相应版本的数据;旧版本的数据也留着,以便其他事务快照读。
事务提交和回滚的时候
事务提交的时候,做的修改成为数据库的最新版本,并且其他事务可见。
事务回滚的时候,做的修改撤销,并且其他事务不可见
InnoDB 对 MVCC 的实现
undolog和readview的配合:当 Read View 判断当前数据不可见时,它必须通过 Undo Log 提供的版本链,去找到一个它认为可见的历史版本。
可重复读:启动事务时生成一个 Read View,整个事务期间都用这个,记录相应的undo log用链表连起来,读只能读到启动之前的记录
读提交:事务读取的时候创建Read View
三大日志
binlog
二进制日志
Server层的日志,逻辑日志,用来全量数据备份和主从复制。
写入时机
在每完成一次更新操作之后,生成一条binlog
刷盘时机
什么时候刷盘由sync_binlog参数决定
- 0:不去强制要求,由系统自行判断何时写入磁盘;
- 1:每次事务提交后,都要将binlog写入磁盘;
- N:每N个事务,才会将binlog写入磁盘。
方案
STATEMENT:记录数据变更的SQL语句本身
- 好:日志文件体积小,一SQL记一次
- 坏:可能存在数据不一致,对于动态函数---NOW()UUID()LIMIT等依赖上下文,会导致主从数据不一致
ROW:记录数据最终被修改成什么样
- 好:数据一致性最高,绝对一致主从
坏:日志文件体积大,更新多少行就记多少
MIXED:折中判断
特点
追加写,写满一个文件,就创建一个新的文件继续写,不覆盖,存全量日志--->恢复,主从复制
注意的是只记录库、表结构改变和数据修改的日志
redolog
重做日志
存储引擎层面的,物理日志,用来保障事务的持久性和崩溃恢复
Mysql有Buffer Pool缓冲池,跟redis一样。如果在Buffer Pool中的页的修改还没持久化到磁盘中,这个时候宕机了,事务更改就消失了
引入WAL原则(预写日志),就是先写到日志,再写到磁盘上
写入时机
- 当执行一条更新语句(如
UPDATE)时,先在 Buffer Pool 中查找对应的数据页。如果不在,就从磁盘加载到 Buffer Pool。 - 接着,修改 Buffer Pool 中的这个数据页。此时,这个被修改了但还没刷回磁盘的数据页,就是所谓的 “脏页”。
- 标记脏页的同时,InnoDB把这个页面做了什么物理修改记录记录到Redo Log Buffer中
- 每一次数据变更都会产生对应的redo log
例如:在表空间xx文件、第xx页、偏移量xx的位置写入了xxx数据
刷盘时机
事务提交时,先把 Redo Log Buffer 中对应的所有日志记录,强制刷盘,写到redolog中, 此时,现在已经算是被持久化了。(由参数调控)
- trx_commit = 1:每次事务提交时,都必须立刻将 redo log 从内存刷入磁盘。(默认)
- trx_commit = 0:不保证事务提交时刷盘,而是大约每秒钟由后台线程将 redo log 从内存刷入磁盘一次。不安全
- trx_commit = 2:每次事务提交时,只把 redo log 从内存写入到操作系统的页缓存(Page Cache),不直接写入磁盘文件。然后大约每秒钟,操作系统会把它的缓存统一刷到磁盘。 折中
因为即使此时Buffer Pool 中的脏页还没有写回数据库,崩溃的时候,也可以依靠Redo log来进行重做,恢复到事务最后一次提交的状态
这就是WAL思想,先写日志,后写数据
两阶段提交
不一致问题
保障binlog和redolog的数据一致性,不一致的问题:
先写 Redo Log,后写 Binlog,但在写 Binlog 前宕机:
- Redo Log 已经持久化,数据可以恢复。
- Binlog 没有记录。
- 后果: 主库数据是完整的,但从库(通过 Binlog 复制)会缺失这条数据,导致主从数据不一致。
先写 Binlog,后写 Redo Log,但在写 Redo Log 前宕机:
- Binlog 已经记录,从库会复制这条数据。
- Redo Log 没有完全持久化,崩溃恢复时主库会回滚这条数据(WAL 思想失败)。
- 后果: 从库多出一条数据,主从数据不一致。
阶段一:Prepare 准备阶段
- 数据变更: 事务执行所有 SQL 语句,修改 Buffer Pool 中的数据页,并生成相应的 Redo Log 记录。
- Redo Log 刷盘:存储引擎将 Redo Log Buffer 中的数据刷入磁盘。
- 标记 XID: 在这条 Redo Log 记录上,会标记一个事务 ID (XID),并将其状态设置为 "Prepare"(准备状态)。
状态: Redo Log 已持久化到磁盘,但事务在 InnoDB 内部仍未最终提交。如果此时宕机,恢复时 Redo Log 会发现自己处于 "Prepare" 状态,等待 Binlog 的最终判断。
阶段二:Commit 提交阶段
Step 2.1:写入 Binlog(Server 层操作)
- 写入 Binlog:Server 层开始处理,将这个事务对应的 Binlog 事件写入到 Binlog 文件中。
- binlog刷盘: 此时,Binlog 也被强制刷盘。
- 记录 XID: 在 Binlog 事件中,同样会记录这个事务的 XID。
状态: Redo Log(Prepare 状态)和 Binlog 都已持久化到磁盘。Binlog 成为崩溃恢复的最终裁决者。
Step 2.2:写入 Redo Log Commit 标记(InnoDB 层操作)
- Redo Log 提交: 事务执行层通知 InnoDB 存储引擎,事务已完成 Binlog 写入。
- 最终标记: InnoDB 只需要在 Redo Log 上添加一个 "Commit" 标记(最终完成状态),表明该事务已彻底提交。这个操作通常只在内存中修改状态,并不强制刷盘。
状态: 事务彻底完成。数据修改对外部可见。
为什么 Step 2.2 不强制刷盘?
因为即使在 Step 2.2 标记完成前宕机,我们回到 Step 2.1 的状态:Redo Log (Prepare) + Binlog (已记录)。崩溃恢复时,MySQL 依然会根据 Binlog 来完成提交。所以,Step 2.2 的标记只是为了事务的正常结束流程,在崩溃恢复中不再是决定性的步骤。
崩溃恢复时恢复
2PC 的价值体现在崩溃恢复时。MySQL 重启时,会根据 Redo Log 和 Binlog 的状态,执行以下判断逻辑:
| Redo Log 状态 | Binlog 状态 | 恢复操作 |
|---|---|---|
| 未 Prepare | 无记录 | 回滚 :事务视为失败,Redo Log 没有任何记录,直接清理。 |
| Prepare 状态 | 有对应 XID 的记录 | 提交 :事务视为成功,强制完成 Redo Log 的 Commit 标记(以 Binlog 为准)。 |
| Prepare 状态 | 无对应 XID 的记录 | 回滚 :事务视为失败,因为 Binlog 缺失,所以从库无法复制,主库需要回滚。 |
| Committed 状态 | 有记录 | 跳过:事务已完成,正常运行。 |
undolog
回滚日志
逻辑日志,顾名思义,用来回滚的,也就是保障事务的原子性的作用。
记录修改之前的状态
Undo Log 的作用
作用一:保障事务的原子性,回滚
实现机制:
- 当一个事务执行 ROLLBACK 命令,或者事务执行过程中遭遇异常中断(如死锁被终止、应用程序崩溃)时,系统会利用 Undo Log 记录的逻辑信息,将数据恢复到事务开始前的状态。
- 回滚流程: 沿着当前数据行上的 Roll_Pointer 指针,找到最新的 Undo Log 记录,执行其中的反向操作,从而撤销本次事务的所有修改。
作用二:实现多版本并发控制 (MVCC)
见事务篇
刷盘
在事务没提交之前,先把更新前的数据放到undo log 中,详细就是:
插入:记录插入的主键值就行--------回滚的时候只删除这个主键值对应的记录就行
记录新插入行的主键,回滚的时候根据主键定位并删除
删除:记录这个数据项的所有字段---回滚的时候把内容组成的记录重新插入到表中
这里删除是逻辑删除,删除只是标记为已删除,回滚的时候再置为false
更新:记录主键值和被修改的旧值---回滚的时候把列更新为旧值就行
**1、更新的不是主键:那undolog记录的是主键以及被修改的原始值,回滚的时候根据主键找到这行,再用旧值覆盖新值**
**2、更新的是主键:InnoDB的操作是“删除旧记录+插入新纪录”,所以undolog的记录是针对旧的主键的DELECT型+针对新的主键的INSERT型**
随 Redo Log 刷盘而持久化。因为undolog本身就算是一个修改操作,修改了
异同
redo log 记录了此次事务「完成后」的数据状态,记录的是更新之后的值(持久性)
undo log记录了此次事务「开始前」的数据状态,记录的是更新之前的值(原子性)
场景思考总结
在事务执行的各个阶段的回滚
- 以
redo log是否落盘为界:在redo log的prepare阶段完成并刷盘之前宕机,事务必然回滚。 - 结合
binlog判断最终状态:如果redo log已prepare,但binlog未写入成功前宕机,事务回滚。如果binlog已写入成功后宕机,无论redo log的commit标记是否写入,事务都将被视为成功,重启后会完成提交。 - 回滚的本质:利用
undo log中记录的数据前镜像来恢复数据。
必要性
- 如果没有undolog,事务不能回滚原子性消失,MVCC也没有了
- 如果没有redolog,事务持久性消失,宕机就炸了
- 为什么不直接修改磁盘要引入redolog:
因为直接修改磁盘是随机IO,性能极差redolog转换成了顺序IO。
(因为各种表在物理存储中的位置可能相隔非常远,而redolog在日志文件中是严格按照时间排序的) - 也就是说一是减少了IO次数,
二是把IO异步化了→事务提交的时候是同步 顺序IO写到日志上,数据写盘的时候是异步 写到磁盘上。
- 没有
redo log:事务提交 $\rightarrow$ 必须立即进行一次随机 I/O,将修改写入物理数据文件。这样每一次提交都会卡在随机 I/O 上。- 有了
redo log:事务提交 $\rightarrow$ 只需立即进行一次顺序 I/O,将修改记录到redo log文件。物理数据文件的随机 I/O 操作可以延迟到数据库的后台线程去做(称为flush或checkpoint),并且可以合并很多次事务的修改一次性完成。假设有 1000 个事务在 1 秒内提交,每个事务都修改了不同表中的不同行。
- 没有
redo log:需要进行 1000 次耗时的随机 I/O 来修改数据文件。有了
redo log:只需要进行 1000 次极快的顺序 I/O 来记录日志。
- 后台线程可以在后续 10 秒内,只执行少数几次甚至 1 次大的随机 I/O(
flush操作),把这 1000 个修改后的数据页写回磁盘。结论:
redo log的作用不是消除随机 I/O,而是将随机 I/O从事务提交的“关键路径”中剥离出来,使其从同步操作(必须马上完成)变成了异步操作(可以延迟、合并、批量完成)。







