☰
MySQL索引原理与优化:B+树、失效场景与事务日志
2026/10/27 12:02:01 网站建设 项目流程

简介:面向数据库岗位求职者与后端开发者的MySQL面试题知识点总结,围绕常见高频考点搭建复习框架,帮助在面试前快速梳理核心概念与作答思路。整份资料仅含1个docx文档,压缩包约40KB,体量轻便,适合利用碎片时间查阅。内容从关系型与非关系型数据库的区别切入,依次覆盖一条SQL语句在连接器、查询缓存、分析器、优化器、执行器中的执行步骤,以及索引使用原因、哈希表/有序数组/搜索树三类底层结构、主键索引与二级索引、MyISAM与InnoDB实现B+树索引的差异、覆盖索引和索引下推等主题。文档还延伸到索引失效场景、字符串加索引策略、change buffer、redo log与binlog区别及crash-safe机制,便于读者按知识模块查漏补缺。已有252人学习,适合需要系统整理MySQL面试要点、巩固索引与日志机制的初中级开发者参考。

1. 从一道面试题说起:MySQL 的索引到底是什么

有人问过一个问题:为什么 MySQL 的索引能让查询快这么多?答案其实藏在一本纸质词典里。你翻一本八百页的词典找一个词,如果从头翻,可能得翻大半天;但词典前面有目录,目录告诉你「这个词在第 372 页」,你直接翻到 372 页就行。MySQL 的索引就是这张目录,它不改变书里的内容,只是额外维护一份「值 → 位置」的对照表。

所以索引不是玄学。它的本质是拿额外的存储空间和写入成本,换查询时少读几页数据。想通这一层,后面那些 B+ 树、聚簇索引、覆盖索引、索引下推的说法才有落点,你也才明白面试官为什么总盯着索引这一块反复问:因为它同时牵扯数据结构、磁盘特性和业务取舍,最能看出一个人是背答案还是真理解。

2. 索引为什么长成 B+ 树:从「磁盘读几次」倒推出来的结构

2.1 没有索引时,一次查询到底慢在哪里

要理解索引为什么长这样,得先知道没有索引时数据库在干什么。InnoDB 里,一张表的数据本身就是按主键组织好的一棵树,叶子节点上挂着整行数据,叶子之间用指针串成一条链表。所谓全表扫描,就是从这棵树的第一个叶子开始,顺着链表一页一页往后读,读到匹配的就留下,不匹配就丢掉,一直读到链表末尾。

慢的地方不在比较,而在读盘。磁盘不是按字节读的,是按块读的,InnoDB 把块抽象成「页」,默认一页 16KB。你可以算一笔账:一张十亿行的表,假设每行一百字节,那就是一百 GB 左右的数据,切成 16KB 的页,大约六百五十万个页。全表扫描意味着你要把这六百五十万个页从磁盘搬到内存里过一遍,哪怕每页只花零点一毫秒,加起来也是十几分钟。

内存里那块缓存区叫 Buffer Pool,读过一次的页会留在里面,下次再读就快得多。但一张一百 GB 的表显然塞不进内存,冷数据该读盘还是得读盘。索引要解决的问题就一个:把「读六百五十万个页」变成「读三到四个页」。

2.2 三种候选结构:哈希表、有序数组、搜索树

在定下 B+ 树之前,能当索引的数据结构其实有好几种,它们各有各的脾气。

哈希表把键算成一个哈希值,直接定位到桶,等值查询是 O(1),快得离谱。但它有个致命短板:哈希之后顺序全乱了,你查id = 666很快,查id > 666就一点办法没有,只能把整个哈希表扫一遍。所以哈希索引适合纯等值查询的场景,比如某些键值型内存数据库,做范围查询就翻车。

有序数组是另一个极端。数据按顺序排好,二分查找,等值查询 O(log n),范围查询更是它的主场——找到起点,往后连续读就行。但它怕写:往中间插一条数据,后面所有元素都得往后挪一位,更新成本高得吓人。所以有序数组只适合静态数据,比如一批历史归档、一批不再变动的报表数据。

搜索树则试图两头兼顾。二叉树查询是 O(log n),插入也只需要调整局部,看起来完美。但二叉树有个隐藏成本:树高。每往下一层,就多一次磁盘访问。一百万条数据,二叉树树高大约二十层,也就是说最坏情况要读二十次盘。二十次随机 IO,对数据库来说太贵了。

结构等值查询范围查询写入成本适合的场景
哈希表O(1)不支持,退化为全扫低纯键值缓存、只做等值匹配
有序数组O(log n)很好,连续读很高,插入要挪数据静态数据、历史归档
N 叉搜索树O(log n)好较低,局部调整通用数据库索引

表里这行对比其实说明了一件事:数据库索引要的不是理论上的最优复杂度,而是在「读得少」和「写得动」之间找平衡。

2.3 InnoDB 为什么最终选 B+ 树

把二叉树压扁、让一个节点装下几百个键,就得到了 N 叉树。这一步很关键,因为一个节点正好对应磁盘上的一个页——读一个节点,就是读一次页,一次 IO。

InnoDB 的索引项大致是「主键值 + 子节点指针」,一个整数字段加一个指针,十几字节。一页 16KB 装下来,大约能放一千两百个索引项。这意味着树高为 3 的时候,能覆盖 1200 的三次方,也就是十七亿条记录。反过来看,一张十亿行的表,查一个值最多只需要访问三次磁盘。而且树根那一页几乎永远在内存里,第二层命中的概率也很高,实际平均读盘次数还要更少。

那为什么不直接用 B 树?B 树的非叶子节点上也存数据。这带来两个后果:一是同样一页能放的索引项变少,树被迫长高;二是范围查询时,你得在树里上上下下地走,一会儿往叶子跑,一会儿回父节点,随机 IO 就上来了。B+ 树把数据全赶到叶子节点,非叶子节点只留键和指针,一页能塞更多索引项,树更矮;同时叶子节点之间用双向链表串起来,范围查询找到起点后顺着链表往后读就行,随机 IO 变成顺序 IO。

注意:面试里常说的「B 树索引」通常是个泛指说法,具体到 InnoDB 的实现是 B+ 树。回答时把「叶子节点存什么、叶子之间有没有链表、非叶子节点存不存数据」这三点讲清楚,比背名字有用。

2.4 聚簇索引和二级索引:叶子节点上存的到底是什么

同样是 B+ 树,叶子节点上放什么,决定了索引的两种身份。

主键索引的叶子节点存的是整行数据。也就是说,这棵树本身就是表的数据文件,你按主键查一行,找到叶子就拿到了全部字段。这种结构叫聚簇索引,「聚簇」的意思是数据和索引聚在一起,不分家。

非主键索引(二级索引)的叶子节点存的是主键值,不是整行。所以你按二级索引查name = 'A',先在 name 这棵树上找到主键 id,再拿 id 去主键树上查一次,才能拿到整行。这第二趟查询有个专门的名字叫回表。回表是随机 IO,次数一多,性能就掉下来了。

顺带说一句,MyISAM 的实现方式完全不同:它的索引文件和数据文件是分开的,B+ 树叶子节点存的是数据记录的物理地址,查到地址再去数据文件里取。这种设计下,主键索引和二级索引在结构上没有本质区别,但也因为没有聚簇,它不支持事务,这也是后来 InnoDB 成为默认引擎的重要原因。

理解了「叶子节点存整行还是存主键」,后面覆盖索引、回表、索引下推这些概念就都能顺着推出来了。

3. 索引建了却用不上:覆盖索引、索引下推与四种失效写法

3.1 覆盖索引:为什么「不回表」能快一大截

一条查询语句进到 MySQL,大致要过几道关:连接器先验证身份、给权限;分析器做词法和语法分析,搞清楚你要查哪张表、哪个字段;优化器决定走哪个索引、要不要 join 重排;执行器再按执行计划去调存储引擎的接口取数据。索引有没有被用上,主要是优化器拍的板。

那优化器最怕什么?怕回表。假设你在 name 上建了索引,执行select name, age from users where name = 'A',走 name 索引能找到主键,但 age 不在索引里,必须回表取。如果这张表有一万条 name 以 A 开头,那就是一万次回表,随机 IO 直接把这句 SQL 拖慢。

覆盖索引就是来解决这个的:如果查询需要的字段,索引里全都有,那就不用回表了,直接在索引树上把结果取完返回。上面的语句,如果建的是联合索引(name, age),name 和 age 都在索引里,回表这一步直接省掉。

-- 建一个联合索引,把查询要用的字段都包含进去 CREATE INDEX idx_name_age ON users (name, age); -- 这条查询需要的 name 和 age 都在索引里,不需要回表 SELECT name, age FROM users WHERE name = 'A';

这段 SQL 里,idx_name_age的顺序很讲究:name在前是因为它是查询条件,age在后是因为它只用于返回。联合索引遵循最左前缀原则,查询条件必须从索引最左边的列开始用,否则这棵树就走不了。想看有没有生效,在语句前面加explain,看 Extra 那一列有没有出现Using index,出现了就说明是覆盖索引,没回表。

注意:覆盖索引不等于「索引覆盖了所有列」。它只要求索引覆盖住「这一次查询用到的列」,同一条 SQL 是覆盖索引,换一条多查一个字段就可能要回表了。

3.2 索引下推:把过滤提前到引擎层做

覆盖索引解决的是「少回表」,索引下推解决的是「少回表几次」。

设想联合索引(name, age),查询是where name like 'A%' and age = 20。在 MySQL 5.6 之前,引擎层只能按 name 把匹配的记录一条条找出来,全部回表,回到 Server 层再判断 age 是不是 20。假设 name 以 A 开头的有一千条,其中 age 等于 20 的只有十条,那另外九百九十次回表全是白跑的。

索引下推(index condition pushdown)做的事,是把 age 这个判断从 Server 层下推到引擎层。引擎在遍历索引的时候,顺手就看一眼索引里的 age 字段,不满足的直接跳过,根本不发起回表。上面那个例子,回表次数从一千次降到十次。

这个优化是 MySQL 5.6 引入的,默认开启。它的前提也清楚:判断条件必须是索引里包含的字段,如果是索引外的字段,引擎手里没这个数据,想推也推不了。

3.3 索引失效的四种典型写法

建了索引不等于用得上,下面这几种写法是踩坑重灾区。

第一种,左模糊和左右模糊。like '%xx'和like '%xx%'都会失效,因为 B+ 树是按前缀有序排列的,你不知道从哪个索引值开始比较,只能全表扫。like 'xx%'是好的,因为它能定位到起点,顺着链表往后读。

第二种,对索引字段用函数或做表达式计算。比如where id + 1 = 100,或者where date(created_at) = '2024-01-01'。索引里存的是字段的原始值,不是算完之后的值,你给个算过的条件,它没法在树上定位。正确写法是把计算挪到等号右边:where created_at >= '2024-01-01' and created_at < '2024-01-02'。

第三种,隐式类型转换。字段是字符串类型,你传个数字进去,MySQL 会先做类型转换再比较,这个转换相当于给字段套了个函数,索引就废了。这种问题最阴,因为 SQL 看起来完全正常,只有 explain 才看得出来。

第四种,OR 连接的条件里有一个不是索引列。只要 OR 两边有一边走不了索引,整个语句就退化成全表扫描。这种情况下可以拆成两条 SQL 用 union 合并,或者给另一个字段也补上索引。

-- 失效:字段被函数包住,索引无法定位起点 SELECT * FROM orders WHERE DATE(created_at) = '2024-01-01'; -- 生效:把计算挪到常量侧,字段保持裸露 SELECT * FROM orders WHERE created_at >= '2024-01-01 00:00:00' AND created_at < '2024-01-02 00:00:00';

这两条语句查的是同一天的数据,逻辑完全等价,但第一条要全表扫,第二条能走created_at上的索引做范围查询。改写的核心思路就一句:让索引字段单独出现在比较符号的一侧,别让它参与任何运算。

3.4 字符串字段建索引的四种取舍

字符串加索引是个经典难题:直接给整列建索引,占空间;只建前缀,又可能不准。

第一种,完整索引。占空间最大,但查询最准,也能支持覆盖索引。字段长度可控的时候优先选它。

第二种,前缀索引,比如index(email(10))。省空间,但有两个代价:一是可能要扫描更多次才能确认唯一,二是用不了覆盖索引,因为索引里存的只是前缀,不是完整值。

第三种,倒序存储再建前缀索引。有些字符串前缀区分度很差,比如一批邮箱都是同一个域名结尾,但后缀区分度高。把字符串倒过来存,前缀就变成了原本的后缀,区分度一下就上来了。

第四种,额外加一个 hash 字段,存字符串的哈希值,在这个字段上建索引。查询性能稳定,但多了一次计算和一份存储,而且和倒序存储一样,不支持范围扫描——哈希值是有序的,但哈希前的原值顺序已经丢了。

四种方案没有绝对优劣,看你的查询是等值多还是范围多、字段有多长、区分度如何。选错方案不会报错,只会在某天数据量上来之后突然变慢,这类问题只能靠提前想清楚。

4. 一条 update 背后发生了什么:redo log、binlog 与两阶段提交

4.1 WAL:为什么先写日志再写磁盘

更新一条数据,如果每次都老老实实找到磁盘上那一页、改完再写回去,一次更新就是一次随机 IO,高并发下根本扛不住。MySQL 的做法是:改内存里的页,同时把「改了什么」记到日志里,然后就可以返回告诉客户端成功了,真正的数据页稍后再慢慢刷回磁盘。

这个思路叫 WAL,Write-Ahead Logging,先写日志再写磁盘。它的关键收益是:日志是顺序追加写,快;数据页刷盘是随机写,慢。把随机的活儿攒起来批量做,响应速度就上去了。代价是内存里的改动还没落盘,万一这时候断电,内存数据就没了——所以才需要一份能撑住崩溃的日志。

4.2 redo log 的循环写与 crash-safe

redo log 就是那份日志。它分两部分:内存里的 redo log buffer,和磁盘上的 redo log file。执行一条 DML,记录先写进 buffer,再在某个时机刷到磁盘文件。

redo log file 的大小是固定的,比如由四个文件组成,首尾相连成一个环。有一个 write pos 指着当前写到哪,一边写一边往后挪,写到末尾就绕回开头;另一个 checkpoint 指着当前可以擦除到哪,擦除之前要把对应的数据页更新到磁盘文件里。两者之间是空着的部分,可以继续写新记录。如果 write pos 追上了 checkpoint,说明环满了,这时候不能再执行新的更新,得停下来先把一些页刷盘、把 checkpoint 往前推。

这个「循环写」的特性,正是 redo log 能保证 crash-safe 的根本原因:只要某个数据页已经刷盘,它在 redo log 里对应的记录就会被抹掉。所以数据库重启时,把 redo log 里剩下的记录重放一遍,就能把没落盘的改动补回来,而且不会重复应用已经落盘的改动。

关于什么时候刷盘,由一个参数控制:

# my.cnf 中的刷盘策略配置 # 0:延迟写,每秒把 redo log buffer 刷到 OS buffer 并落盘,事务提交时不刷 # 1:实时写实时刷,每次事务提交都刷到磁盘,最安全但 IO 最多 # 2:实时写延迟刷,事务提交时写到 OS buffer,每秒落盘一次 innodb_flush_log_at_trx_commit = 1

这三个值的取舍很直白:0 最快,但宕机会丢最近一秒的事务;1 最安全,符合事务持久性的要求,代价是每次提交都要一次磁盘同步;2 折中,写到操作系统缓冲区就返回,只有机器断电才可能丢,MySQL 进程自己崩了不丢。生产环境一般用 1,除非有明确的性能压测数据支撑才考虑调整。

4.3 binlog 补不上 crash-safe 的那一半

binlog 是另一份日志,属于 Server 层,所有存储引擎都能用。它是归档日志,追加写,不覆盖,保存全量记录。它有两个用途:主从复制和数据恢复。想恢复到某个时间点,就取那个时间范围内的 binlog 重放。

那 binlog 能不能单独扛 crash-safe?不能。原因不复杂:binlog 是全量的,它没有办法告诉 InnoDB「哪些数据页已经刷盘了、哪些还没刷」。崩溃恢复时你只知道「这些事务提交过」,但不知道该从哪儿接着补。redo log 因为有循环写和 checkpoint,天然知道自己覆盖的是哪一段未落盘区间,这才撑得起恢复。

另外还有一个更直接的差别:redo log 写入失败,说明这次操作失败,事务不可能提交;而 binlog 在崩溃前可能压根没写完整。所以恢复这件事,必须靠 redo log 打底。

binlog 有三种格式,各自的取舍值得单独记一下:

格式记录内容优点缺点
statement记录原始 SQL 语句日志量小,IO 省某些函数、触发器在主库备库执行结果可能不一致
row记录每一行被改成了什么记录精确,不会有主备不一致日志量大,批量更新尤其夸张
mixed常规语句用 statement,特殊语句切 row兼顾体积和准确性格式不统一,排查时略麻烦

做数据恢复、闪回这类操作时,通常要求binlog_format=row且binlog_row_image=full,因为只有行格式才记录修改前后的完整镜像,才能把 delete 反演成 insert。

4.4 两阶段提交:prepare 和 commit 中间为什么必须夹着 binlog

redo log 归 InnoDB 管,binlog 归 Server 层管,两份日志各写各的,就必须解决一个一致性问题:万一写完一份、另一份没写成,崩了之后按哪份恢复?

如果先写 redo log 再写 binlog,中间崩溃,主库靠 redo log 恢复了这次更新,但 binlog 里没有这条记录,从库和备份就少了一次变更,数据不一致。反过来先写 binlog 再写 redo log,中间崩溃,事务在主库上无效,但 binlog 里已经有了,从库照着重放,同样不一致。

两阶段提交就是来堵这个口子的。redo log 的写入拆成两步:先写 prepare 状态,然后写 binlog,最后把 redo log 改成 commit 状态。恢复的时候按状态判断:redo log 是 commit,说明 binlog 肯定也写成功了,直接恢复;redo log 是 prepare,就去查对应的 binlog 事务是否完整,完整就提交,不完整就回滚。

那怎么判断一个 binlog 事务完不完整?看格式:statement 格式的 binlog,最后会有一个 COMMIT 标记;row 格式的,最后会有一个 XID event。有结尾标记就是完整的,没有就是写到一半崩了。

注意:两阶段提交保的是 redo log 和 binlog 之间的逻辑一致,不是「两份日志内容一样」。它们本来就是两个独立的东西,一个记物理页变更,一个记逻辑操作,要做的是让它们对同一次事务的判定保持一致。

4.5 change buffer:不是所有写都要立刻读盘

还有一个容易被忽略的机制。更新一个数据页时,如果这页在内存里,直接改;如果不在内存里,InnoDB 并不急着把它从磁盘读上来,而是先把这次更新记到 change buffer 里。等到下次有查询要访问这个页,再把页读进内存,顺便把 change buffer 里跟这个页有关的操作合并进去。

这么做省掉的是「为了写一次而读一次盘」的随机 IO。但它有适用范围:只有普通索引能用,唯一索引不行。因为唯一索引每次插入都要判断唯一性,必须把页读上来确认没有重复,那就绕不开这次读盘,change buffer 也就没意义了。

所以如果业务能接受,从纯写性能角度,普通索引比唯一索引更划算。这也是为什么有人说「唯一索引不一定更好」——它的约束价值是真的,但写放大也是真的。

change buffer 最适合写多读少的业务,比如账单流水、日志记录:数据写进去之后短期内不会被查,攒在 buffer 里慢慢合并,效果最好。反过来,如果写完马上就要查,那这次查询会立刻触发票据合并,随机 IO 一次没省,还多了一份维护 change buffer 的开销。

5. 常见误区:这几组概念最容易混成一锅粥

5.1 结构层:B 树、B+ 树、聚簇索引

第一个易混点,是把 B 树和 B+ 树当成同一个东西。混的原因是很多资料口语化地说「B 树索引」,面试时也常这么问,听着像是一回事。

区分的方法看三点:非叶子节点存不存数据、叶子节点之间有没有链表、范围查询要不要在树里回溯。B 树的非叶子节点存数据,叶子之间没有链表;B+ 树非叶子只存键和指针,叶子之间用双向链表串联。InnoDB 用的是后者。

第二个易混点,是把聚簇索引等同于主键索引。大多数时候确实是一回事,但不绝对。如果你没定义主键,InnoDB 会找一个唯一的、非空的索引来代替;如果连这个都没有,它会自己生成一个隐藏的行 ID 来建聚簇索引。所以准确说法是:聚簇索引是「数据和索引存在一起」的那棵树,主键索引通常就是它,但它的判定标准是叶子节点存不存整行,不是名字叫不叫主键索引。

5.2 日志层:redo log、binlog、undo log

第三个易混点,是三种日志的职责分不清。名字都带 log,都跟「恢复」沾边,混起来太正常了。

拆开看:redo log 是物理日志,记的是「哪个数据页做了什么修改」,循环写,负责崩溃后的重做,撑起 crash-safe;binlog 是逻辑日志,记的是「执行了什么操作」,追加写,负责主从复制和时间点恢复;undo log 是回滚日志,记的是「改之前是什么样」,负责事务回滚和 MVCC 的多版本读。三者各管一段,谁也不能替代谁。

最容易错的一句是「binlog 也能做崩溃恢复」。它能做的是「把已归档的操作重放一遍」,但没法判断哪些页已经落盘、从哪儿接着补,所以单独用 binlog 恢复,很容易重复应用或者漏掉一段。

5.3 操作层:delete、truncate、drop 与两种索引

第四个易混点,是三种删除操作混着用。delete 是逐行删,每删一行都记事务日志,可以回滚,可以带 where 条件,删完表还在、空间不一定释放;truncate 是整表清空,不记单独的删除日志,不能回滚,速度快,表结构还在;drop 是把表本身连结构带空间一起干掉。速度上大致是 drop 快于 truncate,truncate 快于 delete。

判断标准很简单:删部分数据用 delete,一定带 where;想清空但保留表、且不涉及事务回滚,用 truncate;整张表不要了,用 drop。有个细节要记住:DDL 类操作在 binlog 里只记语句不记镜像,所以 truncate 和 drop 误操作之后,没法靠闪回工具反演,只能全量备份加 binlog 重放,恢复时间会很长。

第五个易混点,是以为覆盖索引就是「索引包含了所有列」。不是的,它只要求覆盖这一次查询用到的列。同一条 SQL 是覆盖索引,多查一个字段就可能回表。判断依据是 explain 里的Using index,不是索引定义了多少列。

6. 什么时候该建索引、什么时候别建:几条能落地的判断

讲了这么多结构,最后还是要回到「怎么用」。索引不是越多越好,每一个索引都是一份额外的 B+ 树,占空间、拖慢写入、增加优化器选错的概率。

该建的情况比较明确:高频出现在 where 里的等值或范围条件字段;经常用于 join 的连接字段;经常出现在 order by、group by 里的字段;以及区分度高的字段——所谓区分度,就是不同值的个数除以总行数,越接近 1 越好。区分度这个指标,MySQL 自己估算扫描行数时也在用,它根据统计信息估算,估得准不准直接影响优化器选不选这个索引。

不该建的情况同样明确:区分度极低的字段,比如性别、状态位只有两三种取值,建了索引优化器也大概率不走;写多读少且几乎不按这个字段查的表;数据量很小的表,全表扫可能比走索引还快;以及已经被现有联合索引最左前缀覆盖的字段,重复建纯属浪费。

场景建议原因
高频等值查询、区分度高建能快速定位到少量页
高频范围查询建B+ 树叶子链表顺序读,效率高
只用于返回、不用于过滤慎重除非能构成覆盖索引,否则只是增加写入负担
区分度极低不建优化器大概率放弃,白占空间
已被联合索引最左前缀覆盖不建重复索引,纯增成本

举个具体例子。一张订单表,查询主要是「按用户查最近订单」和「按状态查待处理订单」。第一种,(user_id, created_at)这个联合索引很合适:user_id 定位到人,created_at 在索引内有序,取最近几条顺着链表读就行,还顺带解决了排序。第二种,如果状态只有「待处理/已处理/已取消」三种,单建 status 索引意义不大,因为三分之一的行都要取出来,不如让它全表扫;但如果业务上待处理的订单占比极低,比如万分之一,那这个索引就很有价值。同样是 status 字段,建不建取决于数据分布,不取决于字段名。

还有一种情况是语句本身没问题、索引也有,但就是慢。这时候先别急着加索引,按顺序排查:是数据库整体资源被占满了,还是这条语句被表锁行锁堵住了,还是确实没走索引,还是走了索引但回表次数太多。这四种原因的解法完全不同,直接加索引经常是南辕北辙。真到了优化器选错索引的地步,可以用 force index 强行指定,或者改写语句引导它,比如把order by b limit 1改成order by b, a limit 1,语义等价但能让优化器意识到需要的那条索引。

我自己有过一次典型的翻车。有张日志表慢查询报警,第一反应是加索引,加完当天确实快了,两周后又慢回来,因为写入量涨上来,多出来的那棵索引树把插入拖得够呛。后来把索引删掉,改成按时间分区、查询强制带上时间范围,问题才真正解决。从那以后我每次想加索引之前,都会强制走一遍三个问题:这条查询一天跑多少次、这个字段的区分度是多少、加完之后写入侧能承受多少。想不清楚就先不加。

这套 MySQL 面试题整理里,上面这些点都按面试问法拆成了问答:索引结构、失效场景、日志机制、两阶段提交、主备同步、误删恢复,一条一条对应着看,答不上来的回去翻那一条就行。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询