Mysql八股
基础篇
MySQL中查询执行过程

执行一条SQL会先通过连接器建立链接,检验用户身份管理连接,接着会查询缓存,命中缓存直接返回,查询缓存会将执行sql 的哈希值和结果集放入缓存中,但是有点鸡肋了,因为查询大小写保持一致,结果集超过了设置的缓存大小都会缓存失败,并且只要表更新就会自动清除缓存,所以在8.0以后就删除了。接着就会进入解析器,这一步会语法分析,判断是否写错了语句。下一步进入执行器,这里会判断表或者字段是否存在,并会经过mysql的优化器,索引优化就在这里,然后去存储引擎拿对应的数据。
varchar(n) 中 n 最大取值为多少?
首先mysql规定了除了text和blobs这种大对象类型以外,其他所有列加起来最 多存储65535个字节。varchar中的n代表的是最大存储的字符数量,而不是字节数,所以根据你选择的字符集是有关系的,假如选择。ASCII作为字符集,一个字符占用一个字节。首先一行存储的数据不只有真实数据,mysql默认的dynamic行格式还有对应的null标识和真实数据占用的字节数。假如现在一行只有一列为varchar类型,没有设置not null,那么null标识会占用一个字节0x01,因为varchar为可变字符,要算变长字段长度列表长度,这个列表长度规定允许最大存储字节数大于255字节,会用2字节表示变长字段长度列表。小于用1字节表示,因为一行只有一列,所以1X2占用2字节,加上允许null,一共占用3字节,65535-3=65532。所以在使用ASCII字符集和未设置非空的前提下n最大取值65532。如果用utf-8,占用3字节,n最大为65532/3=21844。多字段计算如下:

行溢出
如果一个数据页存不了一条记录,InnoDB 存储引擎会自动将溢出的数据存放到「溢出页」中。不同的行格式处理行溢出的方式不同,compact会在真实数据处保存该列的部分数据,把剩下的数据放溢出页中,并在真实数据处记录20字节的溢出页的地址。而mysql5.7以后默认使用的dynamic行格式,在真实数据处就不会保存该列的数据而且直接通过20字节的地址指向溢出页。真实数据都在溢出页中。
Explain关键字参数

- 先看
type:若为ALL或index→ 未有效使用索引。 - 再看
key:若为具体索引名 → 索引生效;若为NULL→ 索引未生效。 key_len可以判断使用索引的字节数- 结合
possible_keys:若为NULL→ 无可用索引,需新建索引。
| 取值 | 含义 | 是否使用索引 |
| system | 表中只有一行数据(如系统表),是 const 的特例 | 是(最优) |
| const | 通过主键或唯一索引查询,最多返回一行数据(如 WHERE id = 1) | 是 |
| eq_ref | 多表连接时,被连接表通过主键或唯一索引匹配,每行只匹配一次 | 是 |
| ref | 非唯一索引匹配,可能返回多行(如普通索引查询 WHERE name = ‘张三’) | 是 |
| range | 索引范围扫描(如 WHERE id > 10 AND id < 100、IN、BETWEEN) | 是 |
| index | 扫描整个索引树(全索引扫描,比全表扫描略好,但仍低效) | 是(但低效) |
| ALL | 全表扫描(遍历整个表找数据,无索引可用) | 否(最差) |
索引篇
索引原理
Mysql默认的存储引擎是InnoDB,InnoDB的数据结构是B+Tree,是一种分层的树形结构。B+ Tree中每一个父节点的每个元素,是其对应左子树的 “最大值”、右子树的 "最小值,相邻节点之间还有双向指针,最底层的叶子节点保持有序并存储数据。当我们通过索引查找数据的时候,只需要按照B+Tree的层级,向下查询即可,一般B+Tree就三层。所以效率非常快,只用三次IO,而索引又分聚簇索引和非聚簇索引,聚簇索引中的叶子节点就是存储的整行数据和键值,而非聚簇索引存储了聚簇索引的键值和索引列的值,所以使用非聚簇索引的时候,如果要查询整行数据,需要进行一次回表的操作,拿着索引列的值和键值再找一次对应的整行数据。
索引类型
每个表都会存在一个聚簇索引,如果没有设置聚簇索引,默认的主键就会当成聚簇索引,如果一个表没有主键,那么唯一索引就会是聚簇索引,如果都没有,Mysql会自动生成一个隐藏字段作为聚簇索引。当我们查询多个列的时候,添加过滤条件的时候,比如where,having,会根据是否有索引,判断是否走更高效的索引。Mysql有优化器的,比如select a,b,c from table。即使abc有覆盖索引,也不会走索引,因为没有筛选条件,不管走不走索引都至少会全表扫描,而且走索引是会走索引树,还要增加IO操作,效率更低,如果走覆盖索引还有可能会触发回表的操作。那其实Mysql中索引大概分的类型其实就是聚簇索引和非聚簇索引,一个表只有一个聚簇索引,通常情况下聚簇索引一般是主键,非聚簇索引的就比较多了,组合索引,哈希索引,全文索引,等等
为什么一般是三层
B+Tree 是多路平衡查找树,如果每个节点存储的键值越多,层级就会越少,同样查询消耗的IO也会越少,每个节点存储的键值由磁盘块决定,假如是最常见的4KB的磁盘块,假设索引关键字为Bigint 8字节,加上指针8字节,一共16字节,而一个磁盘块4KB,4096个字节,单个节点存储的关键字+指针就能存储256块,又因为子节点数为关键字数+1,所以假设一块根节点中,能存储257个子节点,257个子节点每一个又能存储257个子节点,那最后一层的每个叶子节点存储的关键字+指针又有256个。所以能存储的行数据有257*257*256大约1600万行,如果换成8KB的磁盘块,再乘2的三次方,就已经上亿了,就算数据量非常大,再增加一层就可以再乘512,将近700亿的数据,能应对99%的场景了。拿三层举例子,从根节点到非叶子节点再到叶子节点,一共三次IO,延迟也比较低,效率也非常高,除去上十亿级别以上的数据,三层结构完全够用,并且效率最好。
MySQL索引结构
按照索引结构分可分为B+树索引、哈希索引、全文索引和空间索引。比较常用的就是B+树索引,在innodb存储引擎下,只要3-4层结构,就可以存储千万行数据。
- 哈希索引仅适合等值查询,不支持范围和排序
- 全文索引专为文本检索设计,依赖分词和倒排索引
- 空间索引用于地理数据,应用场景有限
MySQL索引下推
1 | select * from tb_user where id > 20 and age = 17; |
上面的语句会导致reward不走联合索引,age走联合索引。在mysql5.6之前不使用索引下推,该语句会执行过程是这样的,innodb会定位到二级索引中age大于20的一条数据,并拿到主键的值,然后进行回表操作找数据,判断该数据的reward是否等于10000,不等于就跳过,等于就返回客户端,如此往复。每次都会回表再判断reward是否满足条件。5.6以后引入的索引下推,会再联合索引中就进行判断,比如一条数据满足了范围查询的age,会直接判断联合索引中reward是否满足条件,不满足就跳过,满足了才会回表查询数据。大大减少了回表次数,减少IO,提高查询效率。
为什么Mysql建议一张表数据不超过2000W行
首先Mysql搜索的过程中,最消耗性能的操作就是磁盘的IO,所谓的2000w行也是一个大致的数据量的值,为了将B+树的层数控制在3层以内的一种建议,如果4层每次查询都要多一次IO操作,那这个计算过程是什么样的?innodo的索引结构是B+树,每层的每个节点都是一个数据页,一个数据页大小说16KB,数据页中会存储一些数据,那我们知道B+树是分叶子节点和非叶子节点的,首先说一下叶子节点,叶子节点中会存储行数据+数据页指针,聚簇索引会存储索引值和该行所有数据,而非聚簇索引存储了非聚簇索引值和对应的聚簇索引的值。我们拿聚簇索引举例,假如一行的数据大约为1KB,那么16KB的数据页除去页头和页尾信息以及叶子节点双向链表的指针,假设还剩下15KB,那么叶子节点一个数据页可以存储15行。现在来说非叶子节点,非叶子节点只存储索引键和页指针,像目录一样,那么索引键假设是int类型占用4字节,而指针指向页号,目前数据量起来的情况一般是64位页号也就是8字节,那么一组数据就是12字节,同样的把数据页去除头尾信息,依然按15KB存储数据来算,15*1024/12=1280,按三层来算,根节点存1280组,二层也是非叶子节点存1280组,叶子节点个15行,约等于2500w行,当然要给部分容错,可能某些行数据超1kb的情况,等等。所以为了控制在3次IO,一般不超过2000w行,如果四层的话,按照刚刚的推理可以存储亿级别的数据量了。
分页问题优化
假如我要显示1000条数据,但是每页只展示10条,对应的sql语句
1 | select * from user order by id limit 10; /*第一页数据*/ |
先看内部的执行过程,假设id是主键,server层会调用innodo的接口,从主键索引中获取对应的数据0-10行数据,并返回给server层,但是如果第100页,offset设置的990,其实server层调用的查询接口,其实会查询990+10页就是1000行的数据,最后返回给server层之后将offset之前的990行数据挨个抛弃,如果这个offset设置成1000w,那这个多余的操作极其消耗性能。对于非主键索引来说,如果查询了1000w行,又要回表,去主键索引查数据,最后再丢弃,更加浪费性能,甚至经过这种操作还不如全表扫描来的快,如果出现这种情况sql优化器也会自动进行全表扫描。
那么对于这种问题怎么解决。如果从sql语句执行来优化的话。先从主键索引来说,比如要找1000w,10。因为每次又要查到全部的行数据,一整行数据是比较大的,可以先查出所需要的id
1 | select id from user order by id limit 1000w,1; // 作为where条件 |
这样比较消耗性能的分页操作只查了id,比之前查整行数据快一些。实测能快一半左右吧,取决于你的行数据大小。
如果是非主键索引的话。比主键索引多了回表的操作么。那可以优化这个回表操作,比如按照名字排序,先通过分页操作拿到需要的主键索引的值,因为非主键索引存了主键索引值不需要回表,再根据主键索引去匹配行数据
1 | select id from user order by user_name limit 1000w,10 |
深分页
如果你是想取出全表的数据
可以通过start id的方式分批查询,比如页十条
1 | select * from user where id> start_id order by id limit 10; |
每次把这次查询的最大id当成下次的start_id 这样操作每次都通过主键索引定位要查询的范围,不管便利多少行都可行。查询稳定。抖音也是类似的操作,你查看某个视频的时候会同时查出后面的几条视频,如果你刷的够快,到某个视频的再往下就会看到刷新操作,刷的慢无感知是因为看视频的时候,他会从当前视频当startid(推测 俗称瀑布流)。
事务篇
事务
对于事务来说不管是Spring还是Mysql中的事务都遵循ACID特性,原子性就是比如当A给B转账,A扣钱,B加钱,A扣钱了B没收到钱,或者B收到,A没扣,这种情况MySQL会将数据回滚。第二个是一致性,事务执行前后,保持一致性,数据不被破坏。第三个是隔离性,当多个事务并发执行的时候,事务之间互不影响。最后一个是持久性,当数据库出现断电 崩溃等问题的时候,数据不会丢失。
事务隔离
之前说过事务的特性么,事务的隔离性其实就是在并发的情况下,事务之间不受影响。并发场景下会出现一些问题,首先是脏读,就是读到了一些脏数据。比如事务A读到了事务B未提交的数据,然后事务B又回滚了,导致事务A读到了无效数据。第二个是不可重复读,事务A读了两次数据,比如余额都是100,但是在两次读取数据之间,事务B修改了余额为50,并且提交了。这样事务A读到的是50。第三个就是幻读的问题。事务 A 按条件查询,期间事务 B 插入符合条件的新数据并提交,导致 A 再次查询时多了 “新记录”,行数变了。针对这三个问题,Mysql也有四种隔离级别,首先是读未提交,一个事务可以读到另一个事务未提交的数据,它并没有解决并发场景下事务的安全性,但是它基本不加锁,性能最高,但数据可靠性最差。第二个是读已提交,一个事务只能读取另一个事物提交的修改,它解决了幻读的问题,存在不可重复的问题。第三个是可重复读,它的定义就是一个事务多次读取同一个数据结果始终保持一致性,避免了脏读和不可重复读的问题,但是对于幻读的情况,在InnoDB通过临键锁解决了幻读的问题,最后是安全性最高的串行化,解决了所有的事务并发安全问题,因为他给全表加锁了,这样数据的读写都保证串行,但是效率比较差。 InnoDB 存储引擎中是通过MVCC完成的读已提交和可重复读的事务隔离级别。
MVCC
解决脏读,不可重复读和幻读,可以通过加锁的方式解决,但是写加锁,会阻塞读操作,效率低,InnoDB采用了MVCC这种无锁的机制来解决这个问题。MVCC 的实现依赖三个核心组件:隐藏列、undo 日志、Read View(读视图)。首先说一下隐藏列,InnoDB给每一行都添加了三列隐藏列,这三列都有不同的作用,首先是第一个记录最近一次修改该行的事务ID,第二个是回滚指针,指向该行数据存储在undo log的历史版本,形成一个版本链。第三个是DB_ROW_ID,这个就是如果某个表没有主键也没有唯一索引,自动将该列作为隐藏的主键。然后是undo log,当事务修改的时候,会更新隐藏列中的最近修改事务ID,和指针指向之前的事务数据,而旧版本的数据都写入了undo log,形成类似于单向[[链表]]的版本链,方便回滚。而Read View读视图,一般是和隐藏列中最近事务ID进行判断,read View中也有几个参数,当前活跃的事务列表,最小事务ID,下一个会分配的事务ID,这个比当前的事务ID都大。当前事务自己的ID。通过这四个参数和最近修改的事务ID,进行比较,从版本链中筛选出可见的版本。达到需要的事务隔离级别,之前说MVCC实现了RC和RR
RC是怎么做的?RC解决了脏读的问题,RC的事务隔离级别下每次select的时候都会生成一个read view,这样只能看到在当前select之前提交过的事务,这样未提交的事务数据就不可见。解决了脏读,但是还是会有不可重复读的问题,事务A第一次生成了view1视图,事务B修改了数据并提交,事务A第二次生成了view2视图,事务B的数据是可见的,这样查询到的就是事务B修改的数据。对于像银行这种要求可重复读的场景下就有问题了,但是我认为在电商这种场景下RC是可以的,并且符合业务。
RR解决了不可重复读的问题。事务启动后,第一次执行 SELECT 时生成 Read View,之后所有查询都复用这个 Read View,不再更新。同一事务中,无论其他事务是否提交,始终看到的是 “事务启动时的快照”。比如事务A第一次生成view1视图,事务B修改并提交了数据,但是对于版本链来说最近的一次视图依然是view1,事务B更新的数据对于它来说是不可见的。两次查询结果就会一致。刚刚说的快照读,对于 “当前读”,是保证 “数据一致性” 的关键:通过加锁读最新版本,确保后续修改的安全性,适合需要精确操作最新数据的场景(如订单支付、库存扣减)。通过临键锁阻止其他事务插入新记录,从物理上避免行数变化。解决了幻读的问题。
MVCC其实是读的时候不加锁,通过版本链和Read view实现的无锁读,而写的时候还是会加行锁的。
read View可见性判断
Read View 是事务执行快照读时生成的「可见性规则」,包含 3 个核心属性:
m_ids:当前活跃的事务 ID 列表(未提交的事务);min_trx_id:m_ids中的最小事务 ID;max_trx_id:系统下一个要分配的事务 ID(大于所有已分配的事务 ID);creator_trx_id:生成 Read View 的事务 ID。
可见性判断规则:
对于一行数据的版本 trx_id(最后修改事务 ID):
- 若
trx_id < min_trx_id:该版本已提交,可见; - 若
trx_id >= max_trx_id:该版本是未来事务修改的,不可见; - 若
min_trx_id ≤ trx_id < max_trx_id:- 若
trx_id在m_ids中(事务未提交),不可见; - 否则(事务已提交),可见;
- 若
- 若
trx_id = creator_trx_id:当前事务自己修改的,可见。
幻读
首先什么是幻读,比如事务A执行了一次where查询命令发现只有3行,然后事务B插入了一条数据,事务A再次查询发现变成了4行。这就是幻读,前后两次执行发现行数不同,对于事务A来说是不是出现了幻觉了。
快照读和当前读
针对快照读(普通 select 语句),是通过 MVCC 方式解决了幻读,因为可重复读隔离级别下,事务执行过程中看到的数据,一直跟这个事务启动时看到的数据是一致的,即使中途有其他事务插入了一条数据,是查询不出来这条数据的,所以就很好了避免幻读问题。
针对当前读(select … for update 等语句),是通过 next-keylock(记录锁+间隙锁) 方式解决了幻读,因为当执行 select …for update 语句的时候,会加上 next-key lock,如果有其他事务在 next-keylock 锁范围内插入了一条记录,那么这个插入语句就会被阻塞,无法成功插入,所以就很好了避免幻读问题。
为什么不能完全解决
上面两种方式为什么不能完全解决幻读问题呢。举出两个场景。
- 事务A select 查询快照读,然后事务 B更新了数据,事务A又执行了当前读,两次读的不一样。这种情况事务开启后优先进行当前读防止其他事务在未提交前插入数据。
- 事务A 快照片读id为7的数据,发现没有,事务B插入id为7的数据,然后事务A执行update id=7,更新成功,然后查到了id为7的数据。update会先执行当前读,读到了事务B的数据,后续事务A不管当前读还是快照读都会读到更新的数据(事务id更新了),导致出现幻读问题。
并发事务
下面是事务A和事务B,并发情况下在不同隔离级别下V1-V3的值。

在不同隔离级别下,事务 A 执行过程中查询到的余额可能会不同:
在「读未提交」 隔离级别下,事务 B 修改余额后,虽然没有提交事务,但是此时的余额已经可以被事务 A 看见了,于是事务 A 中余额 V1 查询的值是 200 万,余额 V2、V3 自然也是 200 万了;
在「读提交」隔离级别下,事务 B 修改余额后,因为没有提交事务,所以事务 A 中余额 V1 的值还是 100 万,等事务 B 提交完后,最新的余额数据才能被事务 A 看见,因此额 V2、V3 都是 200 万;
在「可重复读」隔离级别下,事务 A 只能看见启动事务时的数据,所以余额 V1、余额 V2 的值都是 100 万,当事务 A 提交事务后,就能看见最新的余额数据了,所以余额 V3 的值是 200 万;
在「串行化」隔离级别下,事务 B 在执行将余额 100 万修改为 200 万时,由于此前事务 A 执行了读操作,这样就发生了读写冲突,于是就会被锁住,直到事务 A 提交后,事务 B 才可以继续执行,所以从 A 的角度看,余额 V1、V2 的值是 100 万,余额 V3 的值是 200万。
讲一下Read View
读视图

锁篇
MySQL的锁

根据锁的范围,分为行锁和表锁,行锁中又有临键锁,记录锁和间隙锁,主要的使用用途是在排它锁,是否走索引时决定要不要退化。根据读写权限和并发的兼容性,分成共享锁和排他锁,共享锁只对读权限加锁,也称之为读锁,并且不同事务可以同时对不同行加读锁。而排他锁也称为写锁,会对读写权限都加锁,并且加锁后拒绝其他任何锁。对于读锁无论是否走了索引,都只会对某一行加读锁。其他事务就对该行不可读。对于排他锁来说又分为是否走索引,走的是聚簇还是非聚簇,上不一样的锁范围。如果没走索引,会直接上表锁。如果走了聚簇索引,又分为等值查询和范围查询,等值查询如果查到了就该行的记录锁,也就是对应的聚簇索引行。如果没查到,比如查询的聚簇索引主键id是3,就会id的上一条和下一条,加间隙锁,比如上一条是2下一条是4,上左开右开的开区间,(2,4)上锁。如果是范围查询,查询到了会对范围上记录锁和间隙锁,没查到的话上间隙锁。如果走非聚簇索引,等值查询命中时:会对非聚簇索引记录加临键锁,同时对对应的聚簇索引记录加记录锁,没查到会退化成间隙锁,而范围查询无论是否查到都不会退化。为了事务隔离性可以选择加锁。
全局锁
全局锁通过flush tables with read lock 会锁定整个 MySQL 实例,将整个数据库全部处于只读状态,一般数据备份的时候会使用。防止在备份的时候,有数据修改导致备份后数据不一致问题。比如在备份完用户表以后,余额已经备份好了,这个时候去买了东西,商品扣了库存,才备份库存,但是余额因为已经先备份了,就会出现备份后的数据库,余额没变库存减少的情况。
如何解决呢?开启全局锁将数据库设置为只读状态,无法修改,但是这样在备份的时候整个MySQL实例全部都用不了,线上环境是要命的,有什么办法解决呢?能边备份边可用?
利用可重复读的隔离级别,mysql中使用的innodb存储引擎刚好支持,原理就是我们知道可重复读这种事务隔离级别,是事务第一次启动的时候,会创建一个readView,后续每次读都是通过readView判断能读到的数据。在备份前开启事务,设置可重复读的隔离级别,这样即使后面其他事务对数据修改,对备份的事务来说读的依旧是开启事务时候的数据。
备份数据库的工具是mysqldump,备份的时候加上参数–single-transaction开启事务,mysql要设置好可重复的隔离级别。
意向锁
意向锁的作用是解决S锁和X锁,加锁时需要全表扫描是否冲突这个浪费性能的操作,产生出的表级锁,表级锁请加锁求时需要全表扫描行每一个行锁状态。比如这个表有3000w行,对某一行加了写锁,如果没有意向锁,这个时候要对这个表加表级别的写锁(写写冲突),就会一行一行的判断是否某一行有写锁,冲突了,检查完没有了才能加上锁。3000w行扫过去非常慢,而且加锁阻塞影响业务。
那如果使用在对行加锁的时候,对整个表添加一个意向标记,说这个表某一行已经被加了什么锁,比如对id=100这行加了X锁,就给这个表加标记告诉别人这个表就不能再加表级的X锁和S锁了。为了避免加表级的S锁和X锁,会逐行扫描冲突的情况,这个标记就是意向锁。意向锁有意向X锁(IX)和意向S锁(IS)。加行S锁的时候,同时对表加了意向S锁;加行X锁的时候,同时对表加了意向X锁。就可以防止逐行扫描的浪费,依旧空间换时间,这个空间开销非常小。
S 锁 / X 锁冲突总表(表级 vs 表级、表级 vs 行级、行级 vs 行级)
| 新请求的锁 \ 已持有的锁 | 表级 S 锁(全表共享) | 表级 X 锁(全表排他) | 行级 S 锁(某行共享) | 行级 X 锁(某行排他) |
|---|---|---|---|---|
| 表级 S 锁(全表共享) | Y(兼容) | N(冲突) | Y(兼容) | N(冲突) |
| 表级 X 锁(全表排他) | N(冲突) | N(冲突) | N(冲突) | N(冲突) |
| 行级 S 锁(同一行共享) | Y(兼容) | N(冲突) | Y(兼容) | N(冲突) |
| 行级 X 锁(同一行排他) | N(冲突) | N(冲突) | N(冲突) | N(冲突) |
| 行级 S 锁(不同行共享) | Y(兼容) | N(冲突) | Y(兼容) | Y(兼容) |
| 行级 X 锁(不同行排他) | Y(兼容) | N(冲突) | Y(兼容) | Y(兼容) |
行锁
这里其实并不是很重要的考点,但是自己研究了一下,行锁解决了幻读问题么,而且在5.5之前的MYISAM是没有行锁这个细颗粒度的,而innodb支持行锁,innodb也通过行锁+MVCC解决了大部分的幻读问题,行锁按锁的范围分了记录锁 间隙锁 临键锁,记录锁就是记录某行比如id=2,这一行的数据,遵守X锁和S锁的冲突。间隙锁是锁的范围,比如id(3,6),就锁4 5这两行,如果没有4 5行数据也不能添加,而临键锁是前闭后开的区间,他其实是记录锁+间隙锁组合而成的,在一些情况下会退化成记录锁或者间隙锁。
https://xiaolincoding.com/mysql/lock/how_to_lock.html#唯一索引-主键索引-等值查询
这里说了怎么退化的,讲的特别清楚和举例子了,通过锁退化,等值查询,范围查询解决幻读。
日志篇

- undo log(回滚日志):由 InnoDB 存储引擎层生成
- 核心作用:实现事务的原子性,支持事务回滚(事务执行失败时回退到修改前状态),同时是 MVCC(多版本并发控制)的基础。
- redo log(重做日志):由 InnoDB 存储引擎层生成
- 核心作用:实现事务的持久性,在系统故障(如掉电)时保障已提交事务的修改不丢失,用于故障恢复。
- binlog(归档日志):由 MySQL Server 层生成
- 核心作用:用于数据备份和主从复制,记录数据库所有增删改操作,是 MySQL 分布式架构中数据同步的关键日志。
undo log
https://xiaolincoding.com/mysql/log/how_update.html#为什么需要-undo-log
保证了事务的原子性和MVCC机制,当一个事务开启以后,undolog会记录修改前旧版本的数据,当事务未提交的时候出现异常或者崩溃,可以将数据恢复到旧版本。通过这样的方式实现了事务隔离级别的原子性。我们知道每一个数据行会有三个隐藏列,隐藏主键,事务id,回滚指针,undolog就是通过事务id和回滚指针,构成了一个版本链,通过不同的版本结合readView读视图实现了mvcc的可重复读和读已提交,通过在不同的时机生成readView,通过找不同的版本满足不同的可见性,实现了快照读的不同的隔离级别。
buffer pool
https://xiaolincoding.com/mysql/log/how_update.html#为什么需要-buffer-pool
是innodb设计的缓存池,他的目的是当数据发生修改以后,会将数据放入缓存池中,下次查询的时候如果缓存池中命中了直接返回就好,提高读写性能,同时innodb最小单位是分割成数据页么,缓存池存储的是缓存页,刚开始是空闲的,慢慢缓存进入数据,缓存页中有数据页和索引页,还有个undo页,在构成undolog版本链的时候,会将undolog写入bufferpool。
redo log
https://xiaolincoding.com/mysql/log/how_update.html#为什么需要-redo-log

之前为了提高读写操作,将数据放入了buffer pool,但是缓存池是基于内存的,一般发生mysql意外关闭或者断电的情况,数据就不可避免的丢失了。所以有了redo log来记录数据页做了什么修改,当server层执行sql将数据页加载到缓存池,缓存池更新数据页,这个数据页就变成了脏页,然后redolog记录这个数据页的修改,最后有一个后台线程定期将脏页刷新到磁盘。redolog是物理日志,记录了某个数据页做了某些修改,比如对某个表的某个数据页某偏移量做了什么更新。
bin log
https://xiaolincoding.com/mysql/log/how_update.html#为什么需要-binlog
事务执行过程中,当mysql完成一条更新操作的时候,会同时写入server层的binlog cahce,当事务提交的时候,就将该事务产生的所有binlog写入binlog日志,他记录了所有数据修改和数据库表的结构变更。所以当数据库被误删除了可以通过binlog恢复,同时mysql也是靠binlog实现的主从复制。
update 语句执行过程
以具体更新记录 UPDATE t_user SET name = 'xiaolin' WHERE id = 1; 为例,流程如下:
- 执行器负责具体执行,调用存储引擎接口,通过主键索引树搜索获取
id = 1这一行记录:- 如果
id=1所在数据页在 buffer pool 中,直接返回给执行器更新; - 如果记录不在 buffer pool,将数据页从磁盘读入 buffer pool,返回记录给执行器。
- 如果
- 执行器得到聚簇索引记录后,判断更新前和更新后的记录是否一致:
- 若一致,不进行后续更新流程;
- 若不一致,将更新前和更新后的记录作为参数传给 InnoDB 层,执行更新操作。
- 开启事务,InnoDB 层更新记录前,先记录 undo log(保存被更新列的旧值)。undo log 写入 Buffer Pool 中的 Undo 页面。
- InnoDB 层更新记录:先更新内存(标记为脏页),再将记录写入 redo log(此时更新完成)。基于 WAL 技术,不会立即将脏页写入磁盘,后续由后台线程择机刷盘。
- 至此,一条记录更新完成。
- 更新语句执行完成后,记录对应的 binlog 到 binlog cache,事务提交时统一刷新到硬盘的 binlog 文件。
- 事务提交,进入 “两阶段提交” 流程(后续讲解)。
两阶段提交
两阶段提交是为了防止redo log和binlog刷盘时机不同,中间出现意外关机和崩溃的情况导致,主从数据不一致。比如binlog刷盘了修改了的数据,redolog还没刷盘,重启以后,redolog是旧数据,binlog是新数据到从数据库,主从不一致。同理redolog刷盘了修改以后的数据,binlog还没刷盘,重启以后,主数据库是新数据,从数据库是旧数据。主从不一致。两阶段提交是分为准备阶段和提交阶段,在准备阶段先将redolog数据事务状态设置为准备,刷新到硬盘。提交阶段将binlog刷新到磁盘,调用事务commit将redolog状态改成commit。
当发现redo log是prepare状态,binlog未落盘则回滚数据。
当场景是「binlog 已落盘,redo log 仍为 prepare 状态,还没执行 commit 就崩溃」时,主库重启后会做这样的判断:
- 扫描 redo log,发现有 “状态为 prepare、但未 commit” 的事务;
- 去检查对应的 binlog—— 如果 binlog 已经完整存在(因为之前已经落盘),就自动把这个事务的 redo log 状态改成 “commit”(相当于补做了 commit 操作);
- 最终主库的事务会处于 “已提交” 状态,数据是新的。
内存篇
内存只有buffer pool缓存池,前面也说了,mysql执行更新语句会将数据放入缓存池中,下次查询就不做频繁的IO了,同时防止重启导致内存未刷盘数据丢失,通过WAL,先写入redolog再入磁盘。这里内存篇小林coding讲的非常好,我比较感兴趣的是对于LRU算法的优化,LRU算法(提高缓存命中率)就是通过[[链表]],将经常使用的移动到头节点,如果长度优先,末尾淘汰不经常使用的数据。因为mysql设置了一个预读机制,就是比如我缓存了数据页5,他会把相邻的两个数据页3 6页缓存进来,很多时候会读到相邻数据页么,就不同频繁的缓存了。但是这样也会有问题,就是提前加载进来的数据页可能会不经常访问,会放到数据头,把访问过的数据挤出去。解决这个问题mysql使用了old young LRU算法,就是把一个链表拆成两部分,old区域和young区域,预读的数据页放old区域,old区域在末尾,只有被访问过才移动到young区域的头部,解决了预读实效的问题。
链接如下真的写的很好。
https://xiaolincoding.com/mysql/buffer_pool/buffer_pool.html

实战
B+树如果索引查询4次和查询5次效率有显著差异的原因?
提到一点从4次到5次比3次-4次差距更大,首先是缓存命中率会下降,这样我觉得是差距最大的地方,inndodb的bufferpool会缓存数据页,前两层节点因为会频繁的访问,会到内存中,内存的
读写纳秒级,磁盘大概是1-10毫秒差距很大。所以三层快的原因不只是磁盘io少了,前两层都走内存,最后找叶子节点当然非常快。而且叶子节点被频繁访问的也会缓存到缓存池里。又因为缓存池的内存限制,如果是四层和五层,必然不会走缓存。第二是磁盘io增加了,假设磁盘读写时间大概5ms,5层是25ms,4层20ms。看似很少,但是延迟变长了20%,计算qps,4层50次,5层40次,差了25%。接着在并发场景下,mysql不同隔离级别,以及上读写锁的时候,需要等查询完释放锁,多一次io。等待的时间也会增加,在并发非常大的情况和查询非常多的情况,性能下降还是很严重的。
聊聊数据库的隔离级别
数据库的隔离级别,是在并发场景下,多个事务读写会出现的几个问题的一种解决方式。脏读,不可重复读和幻读。数据库也为了解决这三个问题定义了四个不同的隔离级别,强度从低到高为,读未提交,读已提交,可重复读,和串行化。
聊一下InnoDB的索引内存结构
[[Mysql八股#索引篇]]
InnoDB的索引在磁盘上以B+树存储,每个节点对应的一个索引页,这些页都会被缓存一份到内存中的缓冲池,索引页内包含页头,页尾,行数据,页目录。页头有上下页的指针,行数据之间也有指针,这样从根节点找数据,能通过上层的数据页的指针,指向下层对应范围内的目录,直到找到对应的数据页。
B+树与B树在数据结构上的区别是什么
B+的非叶子节点只存索引值和指向子节点的指针,叶子节点存行数据和索引值。叶子节点通过双向链表连接。B树每个节点都会存储行数据,所以每个对于非叶子节点数据页能存储的行数据就很少,会导致数据量较大的情况下,层级会拉的很高,做多次磁盘io。
B+树的只在叶子节点存储数据的好处是什么?
能控制B+树的层级,非叶子节点能存储更多的索引值和指针,比如索引值是int类型的,每个数据页16KB的情况,每行加上指针和数据头,大概十几个字节,这样每个非叶子节点能存大概1200个索引值和指针,根节点1200个,第二层就有1200 * 1200。第三层假设一行1kb的情况,第三层一个叶子节点能存16行,算下来三层能存大概2000行数据。也满足大多数我们的使用情况,好处能控制磁盘io次数,磁盘节点io是比较费时的,磁盘 IO 也是数据库性能瓶颈,这样会优化性能。并且查询稳定,查询都是从根节点-叶子节点,通过索引值和双向链表找数据。插入和删除操作也只操作叶子节点。
为什么B+树层数低了效率会提高
数据库的性能瓶颈是磁盘IO,内存读写比磁盘快啊,将查找数据放在叶子节点层做内存读写通过双向链表找对应的行数据快的非常多。如果层树变高了,比如从3次到4次,这样每次查询都是4次,所有的对该表的查询都会慢一次磁盘io。
B+树如果索引查询4次和查询5次效率有显著差异的原因?
提到一点从4次到5次比3次-4次差距更大,首先是缓存命中率会下降,这样我觉得是差距最大的地方,inndodb的bufferpool会缓存数据页,前两层节点因为会频繁的访问,会到内存中,内存的
读写纳秒级,磁盘大概是1-10毫秒差距很大。所以三层快的原因不只是磁盘io少了,前两层都走内存,最后找叶子节点当然非常快。而且叶子节点被频繁访问的也会缓存到缓存池里。又因为缓存池的内存限制,如果是四层和五层,必然不会走缓存。第二是磁盘io增加了,假设磁盘读写时间大概5ms,5层是25ms,4层20ms。看似很少,但是延迟变长了20%,计算qps,4层50次,5层40次,差了25%。接着在并发场景下,mysql不同隔离级别,以及上读写锁的时候,需要等查询完释放锁,多一次io。等待的时间也会增加,在并发非常大的情况和查询非常多的情况,性能下降还是很严重的。
一般SQL优化就是怎么优化?
首先就是索引优化,查询的时候走索引,开启慢查询日志,排除一些不走索引的sql,建立索引也好,或者有索引失效的场景,针对性的修改SQL。第二就是写法的优化,避免*查询无效字段,多表关联,或者分页查询出现深分页的情况。然后就是看数据量选择分库分表,水平分表,垂直分库。最后就是数据库调优,调节缓存池参数内存大小,连接数等等。
你刚刚提到没走索引,什么情况下会不走索引?
违反了覆盖索引的最左匹配原则,索引列被函数/表达式/隐式转换操作了,或者like匹配走%在前了,OR关联非索引字段,where过滤的时候使用了not in !=这种非等于的场景,join关联的时候字段之间没有索引,或者有隐式转换。索引优化器觉得走索引效率低的情况,就会选择走全表扫描,深分页过长,也会导致索引失效。
索引为什么能提高查询效率?数据结构是什么样的?
B+数 数据页 数据行 指针 双向列表 聚簇 非聚簇索引、、、
覆盖索引是什么?如何避免回表查询?
当创建的索引包含查询所需的所有必要字段(SELECT 列、WHERE 条件列、ORDER BY/GROUP BY 列等),MySQL 可以直接从索引中获取所有需要的数据,无需回查聚簇索引,这个索引就称为覆盖索引。
mysql 索引有哪些
- 按数据结构分类(底层实现):这是最基础的,InnoDB 和 MyISAM 的核心结构不同,比如 B + 树索引、哈希索引、全文索引、R 树索引(空间索引)。这里要重点讲 B + 树索引,因为是主流,哈希索引的适用场景和限制,全文索引的用途,R 树的空间索引场景。
- 功能 / 约束特性(业务层面常用):主键索引、唯一索引、普通索引、前缀索引。
- 物理存储:聚簇索引(Clustered Index)、非聚簇索引(Secondary Index / 辅助索引)
- 索引列数量 / 类型:单列索引、联合索引(复合索引)、覆盖索引(不是独立结构,是设计思路)。
说一下索引覆盖和索引下推
索引覆盖就是为了防止回表的操作,将select查询的字段和where过滤的字段,都建立联合索引,这样就不用回表了,因为要查询的数据都在非聚簇索引的数据行中了。索引下推是 MySQL 5.6+ 引入的优化策略:存储引擎层(InnoDB)在遍历非聚簇索引时,提前过滤掉不满足索引中非前缀列条件的数据,比如select from table where id>10 and age =20,在之前的版本会先找到id>10的所有主键索引,再回表查询找出age=20的数据,假如id>10的有1000个主键索引,就要回表查询1000条数据,再过滤。而引入索引下推,就只会回表找age=20,id>10的行数据。可能只有100条,减少了回表行数,提高了查询性能。
为什么联合索引不使用最左匹配可能会失效?、
以联合索引 idx_a_b_c (a, b, c) 为例,InnoDB 的 B+ 树会按照「先按 a 排序,a 相同再按 b 排序,b 相同再按 c 排序」的规则构建索引:
- 叶子节点的索引项是
(a值, b值, c值, 主键值)(InnoDB 辅助索引的叶子节点存主键); - 整个索引的有序性是「前缀有序」:a 是全局有序的,b 仅在相同 a 内有序,c 仅在相同 a+b 内有序。
联合索引的 “最左匹配原则”,本质是 B+ 树的前缀有序特性决定的,这个最左匹配并不是强制要按照建立索引的顺序来where过滤,而是比如创建了abc的联合索引,如果你单独查B就不行,没有B索引是建立在A索引树的基础上的,但是如果查询cba,还是可以走索引,索引优化器会自动优化顺序。
你知道什么是回表吗?
如何where条件过滤的是非聚簇索引,非聚簇索引的叶子节点存储的是主键索引的值和非聚簇索引,通过非聚簇索引查到主键以后,再通过主键值去查询主键索引,最终获取行数据,这个过程就是回表。
慢 sql 怎么排查
mysql要先开启慢查询日志啊,然后可以找到对应的慢sql,通过explain关键字,判断怎么优化这条sql语句。explain中有几个关键的列,type看访问类型是不是全表扫描了,row看行数是不是过长,key看如果是null说明没走索引,如果不为null,但是possiable_key写了索引,可能索引失效了,额外信息看是不是有临时表。分析完了以后,就可以看是不是索引失效了,或者join连接不规范,
深分页问题要怎么去优化
游标分页 ★★★★★ 顺序分页、主键 / 唯一索引排序 性能最优,实现简单
覆盖索引 + 子查询 ★★★★☆ 跳页、复杂过滤 / 排序 支持跳页,性能较好
深分页问题优化
假如我要显示1000条数据,但是每页只展示10条,对应的sql语句
1 | select * from user order by id limit 10; /*第一页数据*/ |
先看内部的执行过程,假设id是主键,server层会调用innodo的接口,从主键索引中获取对应的数据0-10行数据,并返回给server层,但是如果第100页,offset设置的990,其实server层调用的查询接口,其实会查询990+10页就是1000行的数据,最后返回给server层之后将offset之前的990行数据挨个抛弃,如果这个offset设置成1000w,那这个多余的操作极其消耗性能。对于非主键索引来说,如果查询了1000w行,又要回表,去主键索引查数据,最后再丢弃,更加浪费性能,甚至经过这种操作还不如全表扫描来的快,如果出现这种情况sql优化器也会自动进行全表扫描。
那么对于这种问题怎么解决。如果从sql语句执行来优化的话。先从主键索引来说,比如要找1000w,10。因为每次又要查到全部的行数据,一整行数据是比较大的,可以先查出所需要的id
1 | select id from user order by id limit 1000w,1; // 作为where条件 |
这样比较消耗性能的分页操作只查了id,比之前查整行数据快一些。实测能快一半左右吧,取决于你的行数据大小。
如果是非主键索引的话。比主键索引多了回表的操作么。那可以优化这个回表操作,比如按照名字排序,先通过分页操作拿到需要的主键索引的值,因为非主键索引存了主键索引值不需要回表,再根据主键索引去匹配行数据
1 | select id from user order by user_name limit 1000w,10 |
来讲讲事务的作用是什么
事务本质上就是数据库为了保证一组操作的 “整体性” 而设计的核心机制,事务我的理解就是一个大箱子,把所有的操作放入这个箱子里面,提交的时候把箱子整体提交,如果失败,整个箱子都失败。那事务的ACID特性,原子性就是刚刚说的箱子内的操作不能分割,一致性是执行前后数据要符合业务规则,总数不变,隔离性,就是多个箱子提交互相隔离互不干扰,持久性就是事务提交以后,即使数据库重启数据也还在磁盘。
聊聊数据库的隔离级别
数据库的隔离级别,是在并发场景下,多个事务读写会出现的几个问题的一种解决方式。脏读,不可重复读和幻读。数据库也为了解决这三个问题定义了四个不同的隔离级别,强度从低到高为,读未提交,读已提交,可重复读,和串行化。
RR和RC的区别是什么
RR是可重复读它和RC读已提交的区别,首先是不同的事务隔离级别,RR支持可重复读,RC不支持。如果是在innodb中,它的实现方式也不同,我们知道innodb实现RR和RC是通过MVCC和锁实现的,RR在MVCC中生成读视图的情况和RC不一样,RC是每次查询都会生成一个读视图,但是RR只有在事务开启的时候生成了读视图,这是快照读。在当前读的时候innodb通过临建锁的方式在RR隔离级别,解决了大部分的幻读问题,而RC解决不了幻读的问题。性能方面肯定是RC更好,安全性RR更好。看不同的业务作取舍。
具体讲一下不可重复读是什么意思
同一事务内,多次执行完全相同的查询语句,得到的结果却不一致。比如你在RC的事务隔离级别下,事务A查询id为1的数据,发现库存是100。然后事务B修改了id为1的数据,将库存改成了200。这个时候事务A再查的时候发现库存变成200了。这就是不可重复读。
RR是怎么解决不可重复读的问题的
RR隔离级别下,在MVCC中,事务只会在第一次快照读的时候生成读视图,后续的快照读也都是读这个视图的数据。比如事务A,先查询了id=1的数据,这个时候会生成一个read view。事务B再修改id=1的数据,并提交,对于事务A而言,读取的依然是原本的read view。如果要读更新的数据,就要用当前读才行。
具体讲一下快照读底层是怎么实现的
MVCC+read view + undo log版本链,在事务开启以后,普通读就是快照读,会生成read view,根据mysql每行会有三个隐藏字段,重要的两个,一个最后修改事务 ID,一个版本链指针。再通过read view生成以后自带的四个属性,活跃事务列表,当前最小事务id,下一个生成的事务id和当前事务id。通过可见性的规则来决定此次快照读能读到哪个版本的数据。
为什么RR级别的效率要比RC低
RR 为了实现可重复读、无幻读,引入了间隙锁(扩大锁范围)和长时版本链(增加资源开销),导致锁冲突增多、查询 / 写操作的额外开销上升;
RR与RC的差别体现在具体的业务场景,可以举一个很实际的例子嘛
比如在电商库存扣减这个场景吧,假如此时是RC隔离级别下,事务A,也就是用户A读的时候发现还有100个库存,然后事务B修改了。事务再查询的时候发现库存咋就只剩99个了。这是不可重复读问题,然后当查询库存大于0的商品时候,两次查询发现多了其他商品,但是查询筛选条件又没变,这就出现了幻读的问题。而对于RR而言,这两个场景查几次都是一样。
讲一下数据库的锁,设置锁的语法、
数据库的锁按范围分表级锁和行级锁,行级锁。行级锁(无索引时,行锁会升级为表锁)又有行锁,间隙锁和临键锁。按照功能分X锁和S锁,对于共享锁(写锁)来说,加锁的时候通过select * from table where id =100 for share。事务加完行级写锁,其他事务无法再加写锁,同时会给表增加一个意向共享锁。对于排他锁而言,加锁的时候通过for update,这个时候会对表加意向排他锁,其他锁无法对该行加写锁和读锁。
MySQL的隔离级别有哪些? 每种隔离级别有什么区别。
我们知道在并发的场景下,为了解决事务会出现的脏读,不可重复读、幻读问题,所以SQL标准有
读未提交,读已提交,可重复读,串行化这几种。- 隔离级别越高,数据一致性越强,但性能越低;对于mysql而言,从存储引擎换成innodb以后这四种隔离级别都支持,读未提交有脏读的问题,会读到其他事务没有提交的数据,读已提交解决了脏读问题。但是存在不可重复读和幻读的问题,而可重复读的隔离级别解决了不可重复读和部分的幻读问题,串行化就是将并发的事务转换成串行了,所以不存在上面的并发问题了,但是性能很差。
MySQL 怎么解决幻读或者脏读的问题。
通过MVCC+锁。脏读用RC,幻读用RR。
无法解决幻读的情况
场景 1:先快照读,后当前读,出现幻读
场景 2:唯一索引等值查询的当前读,间隙可插入数据(衍生幻读)
InnoDB 的 Next-Key Lock 仅在范围查询、非唯一索引等值查询时触发(锁定记录 + 间隙);而唯一索引等值查询时,InnoDB 会 “优化” 为仅加记录锁(不锁定间隙),因此其他事务可插入间隙数据,后续范围当前读会看到新数据,出现幻读。
innodb 事务更新的整个流程
以执行 UPDATE user SET name = 'new_name' WHERE id = 1 为例:
- 加载数据:Server 层调用引擎接口,引擎判断数据页是否在 Buffer Pool 中,若不在则从磁盘加载到内存。
- 写入 Undo Log:在修改内存数据之前,将旧值写入 Undo Log,用于事务回滚和 MVCC(多版本并发控制)。
- 更新内存:在 Buffer Pool 中更新数据行,此时内存页变为脏页。
- 写入 Redo Log (Prepare):将修改操作写入 Redo Log(重做日志)缓冲区,并根据策略(innodb_flush_log_at_trx_commit)刷入磁盘。此时 Redo Log 处于 Prepare 状态。
- 写入 Binlog:Server 层将 SQL 语句或记录逻辑写入 Binlog(归档日志),并刷盘。
- 提交事务 (Commit):Binlog 写入成功后,引擎将 Redo Log 标记为 Commit 状态,修改完成。
说一下此时二阶段提交的流程
二阶段提交 (2PC) 是为了保证 Redo Log(引擎层)和 Binlog(Server 层)的一致性。防止数据库崩溃恢复后,主库数据(由 Redo Log 恢复)和 Binlog(用于归档和主从复制)数据不一致。
- Prepare 阶段:
- InnoDB 将事务的 Redo Log 写入磁盘,并将状态标记为 prepare。
- 此时事务并未完全提交,但数据已在 Redo Log 中持久化。
- Commit 阶段:
- Server 层生成 SQL 语句的 Binlog,并写入磁盘。
- Binlog 写入成功后,Server 调用引擎的提交接口,InnoDB 将 Redo Log 状态设置为 commit,事务结束。
崩溃恢复逻辑:
- 如果 Crash 在 Binlog 写入前(Redo Log 是 prepare):Binlog 为空,引擎判断 Binlog 不完整,回滚事务。
- 如果 Crash 在 Binlog 写入后(Redo Log 是 prepare):Binlog 完整,引擎判断 Binlog 完整,提交事务。
如果此时有主从复制二阶段又会怎么样
在主从复制架构下,二阶段提交的流程会配合 半同步复制 (Semi-Sync Replication) 来保证主从数据一致性。
- 用户提交事务。
- 主库写入 Redo Log (Prepare)。
- 主库写入 Binlog。
- 主库等待 ACK(关键点):
- 主库将 Binlog 发送给从库。
- 从库收到 Binlog 并写入 Relay Log 后,返回一个 ACK 确认包给主库。
- 主库至少收到一个从库的 ACK(或者超时),才继续执行。
- 主库提交事务 (Commit):引擎层提交,返回成功给客户端。
异常情况:
- 如果主库在等待 ACK 时宕机:事务在主库处于 “Binlog 已写,未 Commit” 状态。
- 重启恢复时:因为 Binlog 完整,主库会提交该事务。
- 数据一致性风险:如果主库宕机前 Binlog 没传给从库,且发生了主从切换,旧主库重启后会有多余的事务(数据冲突)。半同步复制就是为了尽量减少这种窗口期。
redo log
https://xiaolincoding.com/mysql/log/how_update.html#为什么需要-redo-log

之前为了提高读写操作,将数据放入了buffer pool,但是缓存池是基于内存的,一般发生mysql意外关闭或者断电的情况,数据就不可避免的丢失了。所以有了redo log来记录数据页做了什么修改,当server层执行sql将数据页加载到缓存池,缓存池更新数据页,这个数据页就变成了脏页,然后redolog记录这个数据页的修改,最后有一个后台线程定期将脏页刷新到磁盘。redolog是物理日志,记录了某个数据页做了某些修改,比如对某个表的某个数据页某偏移量做了什么更新。
bin log
https://xiaolincoding.com/mysql/log/how_update.html#为什么需要-binlog
事务执行过程中,当mysql完成一条更新操作的时候,会同时写入server层的binlog cahce,当事务提交的时候,就将该事务产生的所有binlog写入binlog日志,他记录了所有数据修改和数据库表的结构变更。所以当数据库被误删除了可以通过binlog恢复,同时mysql也是靠binlog实现的主从复制。





