从页、区、段到 B+Tree:InnoDB 表空间如何组织数据?

基础提问(redo log /binlog/ 事务崩溃恢复)

题目 1:redo log 和 binlog 的区别?

  1. 所属层次:redo log 属于 InnoDB 引擎层;binlog 属于 MySQL Server 层。
  2. 日志类型:redo log 是物理日志,记录数据页的修改;binlog 是逻辑日志,记录行变更或 SQL 语句。
  3. 写入方式:redo log 循环环形写,空间固定,脏页刷盘后日志可覆盖;binlog 追加写,不断生成新文件,不会覆盖旧日志。
  4. 作用:redo log 实现 crash-safe,宕机恢复;binlog 用于主从复制、时间点备份恢复。
  5. 引擎支持:redo log 仅 InnoDB 拥有;binlog 所有存储引擎都可以产生。

一句话总结:redo log 是 InnoDB 引擎层的日志,用来 crash-safe(崩溃恢复);binlog 是 MySQL Server 层日志,用来做主从复制、数据备份。


题目 2:为什么需要两阶段提交,不能先写 binlog 再写 redo log?

如果写完 binlog,还没写 redo log 时宕机: binlog 已经存在,从库会同步执行这条变更;但主库 redo log 没写,重启后主库事务回滚。 结果:主库没有数据,从库有数据,主从不一致。

如果先写 redo log 再写 binlog 也有问题: 写完 redo log prepare 阶段,还没写 binlog 宕机:主库回滚,没有 binlog,从库不会同步,数据一致。 写完 binlog,redo log 还没 commit 标记宕机:主库重启提交事务,binlog 存在,从库同步,数据一致。

2PC 的目标:保证 redo log 和 binlog 对同一个事务,要么同时生效,要么同时失效。


题目 3:redo log 的 write pos 和 checkpoint 是什么?

redo log 是环形文件,两个指针:

  • write pos:当前日志写入位置,不断往后走,写到末尾回到开头。
  • checkpoint:脏页已经刷到磁盘对应的日志位置,这个位置之前的 redo log 可以覆盖。 write pos 和 checkpoint 之间的区域,是可用日志空间。

write pos 和 checkpoint 之间的区域,是可用日志空间。 当 write pos 追上 checkpoint,就必须触发 checkpoint,把脏页刷盘,释放 redo log 空间,此时会阻塞数据库写入。


题目 4:binlog 的三种格式,各自优缺点?

  1. statement:记录原始 SQL。优点:日志体积小;缺点:now()、rand()等函数,主从执行结果不一致。
  2. row:记录修改前后的行数据。优点:主从数据绝对一致;缺点:日志量大,更新大表会产生大量 binlog。生产推荐。
  3. mixed:混合模式,MySQL 自动判断,敏感语句用 row,普通语句用 statement。

题目 5:crash recovery 崩溃恢复流程?

MySQL 重启,读取 redo log:

  1. 扫描 redo log,找到所有事务:

    • redo log 状态 COMMIT:直接提交
    • redo log PREPARE:去查找有没有对应的完整 binlog
      • 有 binlog:提交事务
      • 无 binlog:回滚事务
  2. 把已提交事务的变更应用到数据页;回滚未提交事务。


题目 6:binlog 什么时候刷盘?

  1. 事务执行阶段,变更存放在 binlog cache,不写 binlog 文件;回滚直接丢弃 cache。

  2. 事务 commit 时,一次性把整个事务写入 binlog 文件 OS page cache(write)。

  3. 真正刷到磁盘由sync_binlog控制:

    • =1:每次事务 commit fsync 刷盘;
    • =0:交给操作系统;
    • =N:攒 N 个事务再 fsync。
  4. binlog write 操作发生在 2PC prepare 之后、redo commit 之前;5.7 支持 binlog 组提交合并刷盘,优化 IO。


补充面试题:sync_binlog=1,但是机器断电,会不会丢失 binlog?

答:不会。sync_binlog=1 代表 binlog write 之后,必须 fsync 持久化到磁盘,才会继续执行后面 redo log commit 标记。断电时 binlog 已经落盘,满足 2PC 一致性判断。


题目 7:为什么 mysql8.0 移除了查询缓存?

查询缓存存在全局互斥锁瓶颈 + 缓存失效机制过于粗暴 + 命中条件苛刻,绝大多数线上业务收益很低,多核高并发场景下反而成为性能瓶颈;官方选择把资源投入到更普适的优化,缓存交给 Redis/ProxySQL 等中间件。

补充面试题:Query Cache 和 InnoDB Buffer Pool 的区别?

  • Query Cache:Server 层,缓存 SQL 文本 + 结果集,行结果缓存,8.0 移除;
  • Buffer Pool:InnoDB 引擎层,缓存数据页、索引页,磁盘块缓存,保留,是 MySQL 最重要缓存。

表空间、索引

分类

  • 按照数据结构划分:B+Tree 、Hash 、Full-text (全文索引)
  • 按物理存储划分:聚簇索引(主键索引)、二级索引(辅助索引)
  • 按字段特性划分:主键索引、唯一索引、普通索引、前缀索引
  • 按字段个数划分:单列索引、联合索引

Mysql 索引的优缺点?

优点:加快查询,避免全表扫描;支持快速排序分组;唯一索引保证唯一性;覆盖索引避免回表。

缺点:占用磁盘空间;增删改时需要维护索引,降低写入性能;存在索引失效场景;索引过多会加重写压力。

InnoDB 表空间分类

  1. 系统表空间(System Tablespace,ibdata1)
  • 文件默认:ibdata1,可以多个文件 ibdata1,ibdata2...

  • 作用:

    • 表数据和索引数据
    • InnoDB数据字典
    • 双写缓冲区的持久数据
    • 更改缓冲区的持久数据
    • 存储数据字典(元数据:表定义、列、索引、约束)
    • 存储 Undo 日志(MySQL5.6~5.7,8.0 默认 undo 独立)
    • 如果没有开启独立表空间,所有表的数据 + 索引全部存在 ibdata1 里
  • 缺点:ibdata1只增不减,删除大量数据后文件不会收缩,只能导出全库重建。

配置参数:innodb_data_file_path


  1. 独立表空间(File-Per-Table,每表一个表空间)
  • 参数:innodb_file_per_table,MySQL5.6 默认开启,8.0 默认开启

  • 每个表单独生成:表名.ibd 文件 + 表名.frm(8.0 废除 frm,元数据放到数据字典)

  • 内容:该表聚簇索引 + 所有二级索引的数据页

  • 优点:

    1. 删表直接删除.ibd文件,释放磁盘空间
    2. 可单独迁移表(transportable tablespace 可迁移表空间)
    3. 碎片只影响单表
  • 缺点:每个表单独文件,文件数量多;小表会浪费少量空间(至少一个区 1MB 起步)


  1. 通用表空间(General Tablespace)MySQL5.7 + 新增
  • 手动创建共享表空间,多个表可以放到同一个.ibd文件
sql 复制代码
CREATE TABLESPACE ts1 ADD DATAFILE 'ts1.ibd' Engine=InnoDB;
CREATE TABLE t1(id int) TABLESPACE ts1;
  • 特点:

    • 多个表共用一个表空间文件
    • 支持压缩表
    • 可以跨库放表
    • 删除表不会直接删除 ibd 文件,空间在表空间内部标记空闲复用,不会返还操作系统
  • 使用场景:大量小表,减少文件数量,节约 inode


  1. Undo 表空间(Undo Tablespace) MySQL8.0
  • 5.7 undo 还在 ibdata1;8.0 默认把 undo 日志剥离为独立 undo 表空间文件
  • 参数:innodb_undo_tablespaces
  • 作用:存放 undo log,事务回滚、MVCC 版本链
  • 支持 truncate 收缩 undo 文件,解决 ibdata1 膨胀问题

  1. 临时表空间(Temporary Tablespace)
  • 8.0:ibtmp1,存储临时表、排序临时数据
  • 实例重启自动重建,重启就清空。

双写缓冲区(系统表空间)

Doublewrite Buffer(双写缓冲区)属于 InnoDB 存储引擎层,不属于 MySQL 服务层(Server 层) ,是 InnoDB 脏页刷盘机制里的组件,不是事务提交时触发,而是【脏页从 Buffer Pool 刷新到磁盘】的时候才会走双写逻辑

InnoDB 将 Buffer Pool 里的脏页刷新落盘时,才会走双写流程。

  1. Page Cleaner 后台线程批量刷脏(最常见) :后台线程按 Checkpoint 机制,刷 Flush List 上的脏页;
  2. LRU 淘汰单页刷脏 :Buffer Pool 不够,需要淘汰冷页,遇到脏页就触发单页刷新;
  3. 同步 Checkpoint(紧急刷脏) :redo log 空间快要写满,强制触发 Checkpoint,大量脏页刷盘。

写入流程:

  1. 内存脏页准备刷盘,先把页拷贝到内存里的 doublewrite buffer
  2. 批量把这些页顺序写入磁盘上的 doublewrite 区域,执行 fsync 落盘(顺序 IO,开销不大)
  3. 确认双写区写入成功后,再把这些页离散写入到真正的数据文件 (.ibd)
  4. 数据文件写入成功后,双写缓冲区里的副本就可以复用了

页 (Page)

Mysql中的数据,虽然是以行为单位存储的,但是在读取的时候是以页为单位进行读取的,而且考虑到局部性,还会顺便读取相邻的页,来进一步减少可能带来的磁盘IO,那Mysql的数据页默认呢是16KB。

页标识 名称 作用
FIL_PAGE_INDEX 索引页(数据页) B + 树节点,存聚簇索引 / 二级索引记录(我们前面讲页结构就是这种页)
FIL_PAGE_UNDO_LOG Undo 日志页 存放 undo log,事务回滚、MVCC 版本链
FIL_PAGE_INODE 段信息页 管理段 (segment),记录段包含哪些区、页
FIL_PAGE_TYPE_FSP_HDR 表空间头部页 ibd 文件第 0 号页,记录表空间元数据、区的使用情况
FIL_PAGE_TYPE_XDES 区描述页(Extent 描述页) 扩展的区信息,FSP_HDR 不够时用,管理 extent
FIL_PAGE_IBUF_BITMAP Change Buffer 位图页 标记哪些页有 change buffer 缓存(旧叫 Insert Buffer)
FIL_PAGE_IBUF_FREE_LIST Change Buffer 空闲列表页 管理 Change Buffer 空闲页链表
FIL_PAGE_TYPE_SYS 系统页 存放系统相关元信息
FIL_PAGE_TYPE_TRX_SYS 事务系统页 全局事务信息:最大事务 ID、回滚段地址等
FIL_PAGE_TYPE_BLOB BLOB 页 存超长大字段,行内放不下就溢出到 BLOB 页

InnoDB 重要页页分类的详细说明:

  1. 索引页(FIL_PAGE_INDEX) :B + 树节点,存放表数据和索引记录,最核心;
  2. 表空间管理页:FSP_HDR 表空间头、XDES 区描述页、INODE 段信息页,用来管理表空间、区、段;
  3. 事务相关页:UNDO 日志页、事务系统页,支撑 MVCC 与回滚;
  4. Change Buffer 相关:位图页、空闲列表页;
  5. BLOB 溢出页,保存超长字段;还有系统页保存元数据。

页(Page)的存储结构:

  1. File Header 文件头(38 字节 ) 页通用信息:页号、页类型、上一页 / 下一页指针,把同层 B + 树叶节点串成双向链表。
  2. Page Header 页面头(56 字节) 本页专属信息:页内记录条数、空闲空间大小、页目录槽数量、最大事务 ID 等。
  3. Infimum + Supremum 最小 / 最大虚拟记录(26 字节) 两条不存在的虚拟行,作为页内记录链表的上下边界;用户记录按主键升序连成单向链表。
  4. User Records 用户记录(可变) 真实存储的行数据(聚簇索引记录),从空闲空间里划分出来。
  5. Free Space 空闲空间(可变) 页内未分配空间,插入新记录时从这里拿空间;空间耗尽触发页分裂。
  6. Page Directory 页目录(可变) 页内稀疏索引,存放多个槽(slot),每个槽指向一组记录的主键;查找时先二分页目录,再线性搜索本组记录,加速页内定位。
  7. File Trailer 文件尾(8 字节) 校验和,用来检测页在磁盘上是否损坏。

页(Page)的插入和查询流程:

那在一开始生成页的时候呢,其实并没有user records这部分,那当我们插入一条记录,就会从这个free space部分,也就是尚未使用的存储空间中,申请一个记录大小的空间,划分到这个user records里面,那当free space的空间全部都被这个,user records这一部分替代之后呢,也就意味着这个页使用完了,那如果还有新的记录插入的话,就需要去申请新的页了。

那如果我们执行一条SQL查询,比如我们查询主键ID,等于100的这条记录,查询流程是什么样的? 由于数据页中存储了这个页中数据的最大值和最小值,所以呢我们可以先遍历数据页,找到ID等于100这条数据所在的数据页,然后呢从这个数据页的最小值开始,沿着单链表进行遍历,直到找出ID等于100的记录,那如果遍历到ID大于100,还没有找到这条记录,就说明这条记录是不存在的。

那一个数据页呢有16KB,那如果单行数据很小那么一个数据页呢就可以存储比较多的数据行。那这个时候呢,如果每次都遍历整个数据页进行查找,那么查找的效率呢就会很低,所以Mysql对数据页进行遍历的时候是通过页目录进行优化的。

通过槽减少数据页内数据行的遍历 也就是通过分组来定位到ID等于100的行,具体在哪个分组,只需要遍历这个小的分组,就能定位出具体的数据了,那这个分组信息呢其实就是这个页目录,那这些分出来的组呢就是我们前面所说的槽。

如果我们再查找ID等于100的这条数据就可以在查找数据的时候根据二分法先在叶目录中快速找到ID等于100的这条数据所属的槽,然后呢再遍历对应的槽的链表 就可以定位到具体的数据行所以在一个数据页中查找指定主键值的记录。

Innodb存储引擎会把数据存储到磁盘上,磁盘的速度太慢需要以页为单位把数据加载到内存中进行修改,然后呢再通过后台线程将脏页呢刷新到磁盘上,那这个过程中,16K的脏页在落盘的过程中呢,可能写到一半,Mysql服务发生了宕机,当Mysql服务恢复的时候,就需要校验之前落盘的数据是否完整那这个时候就需要用到file tailer部分的信息了,页的校验和日志序列号不一致,就进行同步修复了。


下面是涉及到的名词概念:

槽中数量行的规定:

  • 对于最小记录所在的分组呢,只能有一条记录
  • 那最大记录所在的,分组拥有的记录数呢,只能在1-8条之间
  • 那剩下的分组中的记录数呢范围在4-8条之间

File Trailer:

  • 前4个字节代表页的校验和
  • 后4个字节代表页面被最后修改时对应的日志序列位置(LSN)

日志序列号( log sequence number )

  • 表示当前系统中写入的redolog的日志量,初始的LSN=8704
  • InnoDB是以一个MTR生成的一组redo日志为单位写入log buffer

区(Extent)、段 ( Segment )

当表中数据量大的时候,为了解决以页为单位存储带来大量随机IO的问题,分配空间就按区为单位进行分配了,每个区的大小为1MB,连续的64个页会被划为一个区。

表空间是由很多段组成的,段也是表空间的存储单位,段是由多个区组成的,段主要分为3类:

  • 索弓段:存放 B+树的非叶子节点的区的集合。
  • 数据段:存放B+树的叶子节点的区的集合。
  • 回滚段:存放的是回滚数据的区的集合,MVCC就是利用回滚段进行存储和回滚的。

B+Tree

结构特性:

  • 数据页和数据行是以主键从小到大的顺序进行排序的
  • B+树的叶子节点存储的是完整的用户记录

主键索引树与非主键索引树的区别:

  • 非主键索引|树叶子节点只有非主键字段列的值和主键列的值
  • 普通索引的叶子节点不存完整行数据,只存【索引列的值 + 聚集索引主键值】 , 查到主键后,再去聚集索引里查找完整行,这个过程叫回表。

提问1、为什么不在非主键索引树上存储全部数据呢?

  • 冗余太大,磁盘占用翻倍
  • 写入(增删改)成本急剧升高
  • B + 树的页容量下降,树高度变高,IO 变多
  • 数据一致性维护麻烦

提问2、聚集索引和辅助索引的区别?

  • 存储数据的方式:聚集索引是将数据直接存储在B+Tree 叶子节点上,辅助索引存储的是是索引列的值 + 聚集索引主键值,一个数据表只能由一个聚集索引,而一个表可以有多个辅助索引。 聚集索引存储在物理上是连续存在的,而聚集索引是逻辑存储连续的。
  • 查询性能:聚集索引不用回表,性能高于辅助索引。
  • 数据更新:聚集索引是按照主键的顺序组织的,为了保证表中物理和索引顺序一致,在记录插入的时候会对数据页进行重新排序,所以数据更新效率会比较慢,辅助索引是根据其他数据进行排序的,叶子结点并没有存储所有的数据值,而是采用了指向表中数据的指针的方式,不会进行数据的重排。

回表的代价:

  • 回表操作会查询两个B+树索引 ( 二级索引、和聚簇索引)
  • 访问二级索引需要一次顺序I/O,访问聚簇索引需要一次随机I/O

提问3、为什么InnoDB用B+树作为索引?

InnoDB 索引瓶颈是磁盘 IO,B+Tree 是多路平衡树,非叶子节点不存储数据,一页能存放大量索引键,树的高度很低,减少磁盘 IO;叶子节点通过链表相连,支持高效范围查询和排序;对比 B 树、二叉树、哈希表,最契合数据库既有等值查询,又有大量范围查询的业务场景。

提问4、mysql 索引为什么是最左前缀原则?

联合索引在 B + 树中,是按照索引字段从左到右依次排序存储的。查询时必须满足最左边的连续索引列,才能使用这个联合索引;一旦左边字段断了,后面的字段就无法走索引。

最左前缀两种理解:

  1. 字段前缀 :联合索引 (a,b,c),能利用索引的前缀是 (a)、(a,b)、(a,b,c)
  2. 值前缀 :字符串索引,like 'hel%',匹配字符串最左侧字符前缀,满足最左前缀;like '%hel' 不满足。

索引长度限制:

  • MySQL5.6默认不能超过767 bytes, 5.7不超过3072 bytes
  • MySQL在5.5.3之后增加了utf8mb4的编码 (most bytes 4)
  • MySQL5.5 innodb_large_prefix,表示是否开启更大的字节的限制

提问5、索引覆盖和索引下推有什么区别?

覆盖索引:解决「要不要回表」;索引下推:解决「回表之前,能不能在索引层先过滤一部分数据」。

覆盖索引 Covering Index :查询所需要的所有字段,都在二级索引叶子节点里面,不需要拿着主键去聚集索引查找完整行,避免回表。

索引:idx_name_age(name, age)

sql 复制代码
-- 覆盖索引,不需要回表
select name,age from user where name='张三';

-- 不是覆盖索引,需要回表拿phone
select name,age,phone from user where name='张三';

索引下推 Index Condition Pushdown (ICP):在二级索引遍历阶段(还没回表) ,把索引中包含的查询条件 ,直接在索引层过滤,过滤掉不满足条件的记录,减少回表次数。

提问6、mysql 中使用 like "% x" 索引一定会失效么?

like %xxx,百分号在前面,不能走 range 索引快速检索;如果查询字段刚好全部包含在索引中(覆盖索引),会走索引全扫描 type=index,此时不算完全没有使用索引,但性能依然较差。

提问7、唯一索引的数据就一定不会重复吗?

唯一索引不能保证所有情况完全不重复 : 唯一索引约束的是非 NULL 值不能重复;多个 NULL 值可以并存。 如果字段加上 NOT NULL,此时唯一索引就可以保证该字段数据绝对不会重复。 联合唯一索引是多个字段的组合唯一,单字段可以重复。

提问8、唯一索引一定比普通索引快吗?

唯一索引不一定比主键索引快。 InnoDB 是聚集索引,主键索引叶子节点存放完整行数据,查询主键直接获取数据;唯一索引属于辅助索引,叶子节点存储主键值,查询完整数据需要回表,性能更差。 只有覆盖索引场景下,唯一索引不需要回表,二者性能接近。 唯一只是数据约束,不会降低 B + 树的查找 IO 次数;并且 RR 隔离级别下,唯一索引等值查询不存在的数据还会加间隙锁,带来锁开销。

提问8、MySQL主键发生断层的原因?

  • 事务发生回滚
  • 唯一索引冲突
  • 删除记录
  • 新增数据时指定了自增主键的值
  • auto_increment_offset和auto_increment_increment不为1

提问9、count(*)count(1)和count(字段名) 到底有什么区别?

count是什么?统计结果集中,不为null值的记录数。 所以 count(1) 和 count(主键) 代表的是获取表中的全部记录。

count函数的具体执行过程:

  • MySQL是通过在server层为每一个count查询维护一个count变量,来实现记录数统计的。
  • server层会循环向InnoDB读取记录并进行累加。
  • 最后将获取的总记录数返回给客户端。
写法 统计规则 是否判断 NULL 扫描开销 推荐度
count(*) 总行数 不需要 低(优先最小二级索引) ⭐⭐⭐⭐⭐ 标准写法
count(1) 总行数 不需要 低,和 count (*) 几乎一致 ⭐⭐⭐⭐,可读性弱一点
count (字段) 字段非 NULL 行数 需要判断字段是否 NULL 高,需要读取字段值 ⭐⭐,业务需要统计非空才用

最后结论:count(*)=count(1)>count(索引l字段)>count(主键)>count(非索引l字段)

提问10、char 和 varchar 到底有什么区别?

  1. CHAR (M) 定长,M 代表字符数,最大 255 字符;不足补空格,查询去掉尾部空格;适合固定长度字符串,读取快,空间浪费。
  2. VARCHAR (M) 变长,M 代表字符数;utf8mb4 最大 VARCHAR (16383);存储带 1/2 字节长度前缀,不补空格,保留尾部空格;节省空间。
  3. VARCHAR(10) 在 utf8mb4 下最多存10 个汉字,不是 10 字节。
  4. InnoDB 单行总字节上限 65535 字节,所有字段共享,限制 VARCHAR 上限。

开发小建议:

  • 手机号、身份证、固定编码:用 CHAR;
  • 用户名、地址、简介、长度变化大:VARCHAR;
  • 不要随便定义 VARCHAR(1000),MySQL 排序、临时表时会分配定义长度的内存,定义过大浪费内存。
  • 长文本(几千字以上)用 TEXT,不要 VARCHAR。

索引(生效)

能利用 B + 树索引的有序性快速定位数据,优化器评估走索引的开销低于全表扫描,就会选择索引。

1、等值查询( = <> IN )、范围查询( > < >= <= between )

sql 复制代码
-- 可以走索引
select * from user where id = 10;
select * from user where name = '张三';

-- 单列索引可以走range索引
select * from user where id > 100;

-- idx(a,b,c),a等值,b范围;a、b走索引,c无法使用索引
select * from t where a=1 and b>10 and c=5;

2、like 前缀匹配 like '关键词%'

sql 复制代码
-- ✅ 前缀匹配,可以利用索引有序性
select * from user where name like '张%';

-- ❌ like '%张' / '%张%' 不能走索引

3、is null 查询

B + 树索引叶子节点可以存放 NULL 值,where col is null 可以走索引 ( 但是 is not null 要看返回数据量,如果匹配行数太多,优化器会放弃索引。)

sql 复制代码
select * from user where phone is null;

4、or 查询,or 两边字段都有独立索引,如果只有一边有索引,or 大概率索引失效。

sql 复制代码
-- a、b各自建有单列索引,可能会走索引合并 index_merge
select * from t where a=1 or b=2;

5、排序 order by、分组 group by

如果 order by / group by 的字段顺序满足联合索引最左前缀 ,可以利用索引天然有序,避免 Using filesort,如果排序字段不满足索引顺序,会产生文件排序,无法利用索引排序。

sql 复制代码
-- idx(a,b)
select a,b from t where a=1 order by b;

6、join 关联查询

join 的关联字段建立索引,能加快关联匹配(驱动表拿到结果后,被驱动表通过索引快速匹配)

sql 复制代码
select * from t1 join t2 on t1.id = t2.t1_id;
-- t2.t1_id 建立索引,提升join性能

索引失效

默认 InnoDB,B + 树联合索引,记住核心思想:索引有序性一旦被破坏,索引就无法使用

1、对索引列做函数运算、表达式计算 , 原因:数据库无法利用索引有序性,需要把每一行数据读出来再计算,只能全表扫描。

sql 复制代码
-- 失效:在索引字段上使用函数
select * from user where DATE(create_time) = '2026-10-01';

-- 改成:把常量放到函数一侧,索引字段裸写,就可以走索引
select * from user where create_time >= '2026-10-01' and create_time < '2026-10-02';

-- 失效:表达式运算
select * from user where age + 1 = 18;

2、隐式类型转换(非常高频坑)

字段 phone varchar(11),索引建在 phone 上,原理:phone 是字符串,MySQL 会把索引列 phone 转为数字 ,等价于 CAST(phone AS SIGNED)=13800138000,相当于在索引列上执行函数,索引失效。

sql 复制代码
-- 失效!字符串字段,传入数字,发生隐式转换
select * from user where phone = 13800138000;

-- 正常:字符串匹配
select * from user where phone = '13800138000';

3、联合索引不满足最左前缀原则,where 条件书写顺序无关,优化器会重排;看索引定义顺序。

sql 复制代码
-- 失效:跳过最左a,直接查b
select * from t where b=1;
-- 失效:a有,跳过b直接查c,前缀断了
select * from t where a=1 and c=2;

4、范围条件后面的索引列失效(联合索引)

索引 idx(a,b,c) , 规则:联合索引,一旦某个字段是范围(> < between like % xxx),该字段右侧所有索引字段失效

sql 复制代码
-- a等值,b范围,b后面c无法走索引
select * from t where a=1 and b>10 and c=3;

5 、like 以通配符开头 %xxx

sql 复制代码
-- 失效:%在最前面,无法利用索引有序前缀
select * from t where name like '%张三';

-- 可以走索引:前缀匹配
select * from t where name like '张三%';

6、使用 or 连接条件,or 一侧没有索引, 原理:or 要求两边都找到结果,b 没有索引只能全表,MySQL 就放弃索引直接全扫。

sql 复制代码
-- 假设a有索引,b无索引 → 整个SQL索引失效,全表扫描
select * from t where a=1 or b=2;

7、使用 not in、!=、<>、not exists , 原因:!= /not in 结果集通常很大,优化器认为走索引 + 回表开销 > 全表扫描,直接放弃索引。

sql 复制代码
select * from t where id != 100;
select * from t where id not in (1,2,3);

小数据集有可能走索引,不要死记 "一定失效"。

其他

导致索引失效的情况:

  • 索引碎片过多
  • 数据表太小
  • 查询优化器估算错误

索引命名规范:

索引类型 前缀 示例
普通索引 idx_ idx_user_phone
唯一索引 uk_(unique key) uk_user_account
主键索引 pk_(primary key) pk_user_id
联合索引 idx_,字段用下划线拼接 idx_user_name_age
全文索引 ft_(fulltext) ft_article_content

几条硬性规则

  1. 全部小写,下划线分隔 ,禁止驼峰 idxUserName
  2. 名字长度控制,不要超长;字段多的时候可适当缩写,保证可读性
  3. 一个表内索引名称不能重复
  4. 主键一般不用手动命名,InnoDB 默认主键名 PRIMARY,无法修改
  5. 不要在名字里包含 index 单词,冗余

索引的使用原则

  1. 高选择性、高频 where/join/order by 字段适合建索引;低区分度、频繁更新、小表不适合。
  2. 联合索引遵循最左前缀,范围条件放末尾,优先设计覆盖索引减少回表。
  3. 索引列避免函数运算、隐式转换,防止索引失效。
  4. 控制单表索引数量,防止写入性能下降,杜绝冗余索引。
  5. SQL 优化用 explain 验证索引是否真正生效。
相关推荐
JAVA面经实录9173 小时前
Java高级后端 · 全套面试通关手册(MySQL)
java·mysql·面试
java1234_小锋4 小时前
【技术专题】Mysql8 数据库 - Mysql8 简介 & 安装以及配置
数据库·mysql
斯内普吖4 小时前
(开源)水果蔬菜商城实战指南 基于 Java + SSM + Vue + MySQL
java·vue.js·mysql·开源
做运维的阿瑞5 小时前
mysql数据库分组查询:GROUP BY 与 HAVING 的执行逻辑
数据库·sql·mysql
沐欣工作室_lvyiyi6 小时前
基于物联网的智能商业零售管理系统设计与实现(论文+源码)
物联网·mysql·mysql数据库·零售
细嗅蔷薇@6 小时前
DML常见操作
数据库·mysql
青山木7 小时前
秒杀系统设计(二):数据正确性——防超卖、分布式锁与一人一单
分布式·后端·mysql·中间件·架构
ao-weilai7 小时前
MySQL数据库:MySQL基础概要
数据库·mysql
Mortalbreeze7 小时前
MySQL 基础篇(三):一文掌握 MySQL 常见数据类型
linux·服务器·数据库·mysql