存储引擎

InnoDB和MyISAM

区别InnodBmyisam
事务支持不支持
索引聚集索引非聚集索引(见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、InnoDBInnoDB(默认行)

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

  1. 大于情况:往后推,前加一个包含自己的,后面以此递归最后扫到supremum加 (xxxx, +∞]
  2. 大于等于情况:等值的地方变成记录锁
  3. 小于或小于等于情况:条件值不在表里,终止扫描记录的Next-Key锁退化成间隙锁
  4. 小于情况:条件值在表里,终止扫描记录的Next-Key锁退化成间隙锁
  5. 小于等于情况:条件值在表里,终止扫描记录的Next-Key锁不退化成间隙锁

举例:有个表有4个数据项

idname
10A
20B
30C
40D

情况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;

什么时候加表级锁?

当执行UPDATEDELETE的语句的时候,WHERE没有命中索引或者索引失效的时候,就会对全表扫描加锁。

事务

四大特性

原子性:事务是最小的执行单位,不能分割 --- undo log保证的

隔离性:并发访问数据库的时候,事务彼此之间是不可见的(取决于事务隔离级别)

比如先后扣款,先扣款事务还没做完,后扣款事务来了看到的也是原数量

持久性:一个事务被提交之后,他对数据库的改变是永久的,即使数据库发生故障。 --- redo log保证的

一致性:执行事务前后,数据保持一致(指变化总和)

并发事务带来的问题(问题)

脏读

一个事务读取数据并且修改了这个数据,这个时候第二个事务读取了这个未提交的数据,但是第一个事务突然回滚,导致改后的数据没有到数据库里面,最后第二个事务读到了脏数据

丢失修改

一个事务在读取一个数据的时候,另一个事务读取这个数据,第一个事务修改这个数据,第二个数据也修改这个数据,但是数据最后相当于只修改了一次,丢失了一次修改

性质同脏读一样。

不可重复读

指一个事务在事务内多次读一个数据,但是在中间间隙中来了第二个事务,修改了数据,导致第一个事务在事务内前后两次读到的数据不一样

幻读

意思就是一个事务读了几条数据,在他第二次读之前中间来了第二个事务插入了几条数据,导致第一个事务再读的时候像出现了幻觉一样多了几条数据

不可重复读、幻读的区别

实际上幻读就是不可重复读特殊情况,不可重复读强调某一条数据被修改了,幻读强调对数据行数的修改上(变多或者变少)

之所以区分是因为解决他们的方案不一样

标准事务隔离级别(解决)

是用来平衡隔离性和并发性能的东西
级别越高,数据一致性越好,但并发性能可能越低。

  1. 读取未提交:允许读取未提交的数据变更,啥都防不住
  2. 读取已提交:允许读取并发事务已经提交的数据,可以阻止脏读
  3. 可重复读:对同一个字段的多次读取都是一样的,除非是自身修改,阻止了脏读和不可重复读,至于幻读,仍有可能(InnoDB大幅减少)
    一旦事务开始,它就仿佛进入了一个“时间凝固”的快照中*
  4. 可串行化:最高隔离,依次执行,全都防住

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 UPDATEINSERTUPDATEDELETE(加X锁) 这些操作)

正因为这样的机制,InnoDB在可重复读(RR)级别的性能,在只读或者读多写少的情况下,和读取已提交(RC)的性能差不多。在写密集的时候,RR的间隙锁可能会导致它比RC出现更多的锁等待

Mysql的隔离级别怎么实现的

由锁和 MVCC 共同实现的

读取未提交:啥也不用干(实际上InnoDB根本不支持,设置也会自动变成读取已提交)

读取已提交:通过Read View实现的,实际上是高级的版本号

可重复读:通过Read View实现的,实际上是高级的版本号

可串行化:完全依赖于锁

Read View是如何工作的?

是什么东西?

他有四个字段:

  1. m_ids是一个活跃事务id列表
  2. min_trx_id活跃事务中id最小的事务
  3. max_trx_id是活跃事务最大id的下一个事务的id值,也就是最大事务id+1
  4. 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中
  • 在:表示生成该版本的记录的活跃事务还没提交,所以不可见(还在忙活,可能回滚)
  • 不在:表示生成该版本的记录的活跃事务已经提交,所以可见(忙活完了,间隙进来的) 在创建快照和执行查询的间隙进来的

image-20251008212707053
image-20251008212707053

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原则(预写日志),就是先写到日志,再写到磁盘上

image-20251010110214968
image-20251010110214968

写入时机

  • 当执行一条更新语句(如 UPDATE)时,先在 Buffer Pool 中查找对应的数据页。如果不在,就从磁盘加载到 Buffer Pool。
  • 接着,修改 Buffer Pool 中的这个数据页。此时,这个被修改了但还没刷回磁盘的数据页,就是所谓的 “脏页”
  • 标记脏页的同时,InnoDB把这个页面做了什么物理修改记录记录到Redo Log Buffer中
  • 每一次数据变更都会产生对应的redo log
例如:在表空间xx文件、第xx页、偏移量xx的位置写入了xxx数据

刷盘时机

事务提交时,先把 Redo Log Buffer 中对应的所有日志记录,强制刷盘,写到redolog中, 此时,现在已经算是被持久化了。(由参数调控)

  1. trx_commit = 1:每次事务提交时,都必须立刻将 redo log 从内存刷入磁盘。(默认)
  2. trx_commit = 0:不保证事务提交时刷盘,而是大约每秒钟由后台线程将 redo log 从内存刷入磁盘一次。不安全
  3. trx_commit = 2:每次事务提交时,只把 redo log 从内存写入到操作系统的页缓存(Page Cache),不直接写入磁盘文件。然后大约每秒钟,操作系统会把它的缓存统一刷到磁盘。 折中

因为即使此时Buffer Pool 中的脏页还没有写回数据库,崩溃的时候,也可以依靠Redo log来进行重做,恢复到事务最后一次提交的状态

这就是WAL思想,先写日志,后写数据

两阶段提交

不一致问题

保障binlog和redolog的数据一致性,不一致的问题:

  1. 先写 Redo Log,后写 Binlog,但在写 Binlog 前宕机:

    • Redo Log 已经持久化,数据可以恢复。
    • Binlog 没有记录。
    • 后果: 主库数据是完整的,但从库(通过 Binlog 复制)会缺失这条数据,导致主从数据不一致。
  2. 先写 Binlog,后写 Redo Log,但在写 Redo Log 前宕机:

    • Binlog 已经记录,从库会复制这条数据。
    • Redo Log 没有完全持久化,崩溃恢复时主库会回滚这条数据(WAL 思想失败)。
    • 后果: 从库多出一条数据,主从数据不一致。

阶段一:Prepare 准备阶段

  1. 数据变更: 事务执行所有 SQL 语句,修改 Buffer Pool 中的数据页,并生成相应的 Redo Log 记录。
  2. Redo Log 刷盘:存储引擎将 Redo Log Buffer 中的数据刷入磁盘。
  3. 标记 XID: 在这条 Redo Log 记录上,会标记一个事务 ID (XID),并将其状态设置为 "Prepare"(准备状态)。
状态: Redo Log 已持久化到磁盘,但事务在 InnoDB 内部仍未最终提交。如果此时宕机,恢复时 Redo Log 会发现自己处于 "Prepare" 状态,等待 Binlog 的最终判断。

阶段二:Commit 提交阶段

Step 2.1:写入 Binlog(Server 层操作)
  1. 写入 Binlog:Server 层开始处理,将这个事务对应的 Binlog 事件写入到 Binlog 文件中。
  2. binlog刷盘: 此时,Binlog 也被强制刷盘。
  3. 记录 XID: 在 Binlog 事件中,同样会记录这个事务的 XID
状态: Redo Log(Prepare 状态)和 Binlog 都已持久化到磁盘。Binlog 成为崩溃恢复的最终裁决者。
Step 2.2:写入 Redo Log Commit 标记(InnoDB 层操作)
  1. Redo Log 提交: 事务执行层通知 InnoDB 存储引擎,事务已完成 Binlog 写入。
  2. 最终标记: 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 logprepare阶段完成并刷盘之前宕机,事务必然回滚。
  • 结合binlog判断最终状态:如果redo logprepare,但binlog未写入成功前宕机,事务回滚。如果binlog已写入成功后宕机,无论redo logcommit标记是否写入,事务都将被视为成功,重启后会完成提交。
  • 回滚的本质:利用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 操作可以延迟到数据库的后台线程去做(称为 flushcheckpoint),并且可以合并很多次事务的修改一次性完成。

假设有 1000 个事务在 1 秒内提交,每个事务都修改了不同表中的不同行。

  • 没有 redo log:需要进行 1000 次耗时的随机 I/O 来修改数据文件。
  • 有了 redo log:只需要进行 1000 次极快的顺序 I/O 来记录日志。

    • 后台线程可以在后续 10 秒内,只执行少数几次甚至 1 次大的随机 I/O(flush 操作),把这 1000 个修改后的数据页写回磁盘。

结论:redo log 的作用不是消除随机 I/O,而是将随机 I/O事务提交的“关键路径”中剥离出来,使其从同步操作(必须马上完成)变成了异步操作(可以延迟、合并、批量完成)。