目录
- 一,索引结构
- 1. B-Tree(平衡多路搜索树)
- 2.B+Tree
- 3.通过B+Tree来索引
- 4.通过Hash来索引
- 二,语法
- 1. 创建索引的基础语法
- 2. 不同类型的索引创建语法
- 3. 复合索引(多列索引)语法
- 4. 索引选项:指定长度与排序方式
- 5. 删除索引的语法
- 6. 查看索引的语法
- 7. 索引在查询语句中的使用(隐式语法)
- 8. 修改表时添加索引(综合语法)
- 9. 索引的约束与注意事项(语法层面需了解)
- 三,性能分析
- 1.查询频次
- ① 查看各类 SQL 执行频次命令
- ② 查询频次对索引设计的决策指导
- 2. 慢查询日志
- ① 启用与核心参数配置
- ② 慢查询日志的分析方法
- ③ 慢查询日志对索引优化的指导意义
- 3. show profiles
- ① 启用与查看
- ② 深度剖析特定 SQL
- ③ 性能调优的危险信号
- 4. explain(执行计划)
- ① 使用语法
- ② 核心返回字段及分析意义
- 四,使用规则
- 1. 最左前缀法则(最左匹配原则)
- ① 核心规则解析
- ② 范围查询导致右侧索引失效
- ③ 索引列上的各种“骚操作”导致失效
- 2. 索引失效的常见场景
- ① 违反最左前缀法则
- ② 联合索引中范围查询右侧的列失效
- ③ 在索引列上进行运算或函数操作
- ④ 存在隐式类型转换
- ⑤ 模糊查询时通配符 `%` 在开头
- ⑥ 使用 `OR` 连接条件时缺失索引
- ⑦ 估计全表扫描比索引快(数据区分度低)
- 五、 索引的设计原则
- 1. 核心针对性原则
- 2. 数量与空间控制原则
- 3. 业务防错与优化原则
索引就像是一本书的目录,对于数据库来说,它有以下三大作用:
- 极大地加快数据库检索的速度。
- 显著降低数据的排序(Order By)和分组(Group By)成本
- 将“随机 I/O”变成“顺序 I/O”
但是,应用它还是有缺点的。
- 牺牲了’增,删,改‘的速度。(MySQL不仅要更新数据本身,还要同步去维护和调整对应的索引树)
- 占用了额外的物理空间。(它本身也是一种数据,要写入磁盘文件的,当数据量多的时候,也算是一笔硬件成本)。
一,索引结构
先了解索引的结构。
在 MySQL(尤其是最常用的 InnoDB 存储引擎)中,索引的底层结构绝大多数都是 B+Tree(B+树)。
但是我们可以先了解一下简单的Btree。
注:可以在这个网站上动态地来看添加数据时是什么样的。
1. B-Tree(平衡多路搜索树)
B-tree是一种数据结构。
一个 阶数为 m 的 B-Tree 满足:
- 每个节点最多有 m 个子节点。
- 除根节点外,每个非叶子节点至少有 ⌈m/2⌉ 个子节点。
- 根节点至少有 2 个子节点(除非树只有一个节点)。
- 所有叶子节点位于同一层(完全平衡)。
向一个5阶B-Tree依次添加1、2、3、4时,它们按顺序放在同一个节点里,此时节点有4个数,刚好满员。再添加5时,因为5比4大,会放到4的右边,此时节点有5个数,超过了5阶规定的上限(最多4个),于是将中间的那个数(即3)向上提出来成为父节点,3左边的数(1、2)形成一个左子节点,3右边的数(4、5)形成一个右子节点。
如下是存储了1-10的B-Tree:
2.B+Tree
和B-Tree大致相同,只不过提上去的那个中间数字依旧会存储到子节点中,也就是说,最底层的叶子节点会包含完整数据。
上层的父节点仅仅是用来做导航的。同时,左子节点和右子节点还会拉起一条双向链表。
3.通过B+Tree来索引
在 MySQL 中(特别是最核心的 InnoDB 存储引擎),B+Tree 的应用可以说是精妙绝伦。它并不是把索引孤立地存在某个地方,而是直接用 B+Tree 结构把你的数据表组织了起来。
先了解下索引的分类,包括聚簇索引和二级索引。
在 InnoDB 中,表中的数据行,本身就是存放在 B+Tree 的叶子节点上的。这棵树被称为聚簇索引(Clustered Index)。
假设我们有一张用户表,主键是 id,还有 name 和 age 字段。这棵聚簇索引树的结构如下:
非叶子节点:只存主键 id 的值和指向下一层页的指针(比如:id 1~10 往左走,id 11~20 往右走)。 叶子节点(最底层):存放的是整行完整的数据(包含 id, name, age)。而二级索引通常和聚簇索引不一样。
非叶子节点:存的是 name 的值和指针。 叶子节点(最底层):存的是 name 的值 + 对应的主键 id。它没有完整的行数据!也就是说通过二级索引得到主键,最后还要通过聚簇索引回表查询。
但是不一定会回表查询,有时候会覆盖索引。
比如说,我表里面的name已经有索引了。然后我只需要id和名字这两列。那么我在查name这个索引的时候就已经有了name和id了,这就不需要去走第一颗主键树了。就会直接把结果返回。
4.通过Hash来索引
在数据库索引中,除了我们前面大篇幅讲的 B+Tree,另一种重要的索引结构就是 Hash 索引(哈希索引)。
1. Hash 索引是怎么工作的?
它的底层结构实际上就是一个哈希表(Hash Table)。当你在表的某个字段(比如name)上建立了 Hash 索引:
- MySQL 会对每一行的
name值计算一个 Hash 值(比如“张三”算出来是 9527)。 - 在内存中,MySQL 会维护一个哈希表,把 Hash 值 9527 指向对应的数据行磁盘地址(指针)。
- 当你执行
SELECT * FROM users WHERE name = '张三';时,MySQL 瞬间算出“张三”的 Hash 值是 9527,然后直接在哈希表中定位到数据行的地址,直接去拿数据。
2. Hash 索引的“天花板”优势:等值查询快到飞起
如果只论等值查询(用=、IN、<=>查特定的某个人或某个值),Hash 索引的时间复杂度是O ( 1 ) O(1)O(1)。这意味着,无论你的表里有一万条数据还是一千万条数据,它基本上只需要 1 次定位就能找到数据。在这一点上,需要 3~4 次磁盘 I/O 的 B+Tree 在它面前也只能甘拜下风。
既然 Hash 索引这么快,为什么 InnoDB 存储引擎的默认索引结构依然是 B+Tree 呢?因为 Hash 索引的缺点和它的优点一样突出。
致命伤:无法进行范围查询
Hash 算法算出来的值是无序的。
比如“年龄 18”的 Hash 值可能是 4321,“年龄 19”的 Hash 值可能是 1102。它们在哈希表里的位置天各一方。
如果你执行 WHERE age > 18,Hash 索引就彻底废了,因为它没办法在哈希表里顺着往下找,只能苦哈哈地去全表扫描。无法用于排序(Order By)
同理,因为哈希表本身是无序的,当你要求 ORDER BY age 时,Hash 索引完全帮不上忙,MySQL 必须在内存里重新进行文件排序(Filesort)。不支持联合索引的“最左匹配原则”
联合索引是把多个字段拼在一起算 Hash 值。例如你对 (name, age) 建立 Hash 索引,算的是它们整体的 Hash 值。如果你查询时只用了 WHERE name = ‘张三’,由于缺少 age,算不出整体的 Hash 值,索引直接失效。哈希碰撞(Collision)问题
如果两个不同的名字刚好算出了同一个 Hash 值,这就叫哈希碰撞。此时哈希表里对应的位置会拉起一条链表,MySQL 得顺着链表一个一个去比对,如果碰撞严重,查询效率会断崖式下跌。
二,语法
MySQL 中索引的语法从简单到复杂,主要包括创建索引、删除索引、查看索引,以及索引在查询中的使用方式。
1. 创建索引的基础语法
最简单的:建表时直接定义索引
CREATETABLEuser(idINTPRIMARYKEY,-- 主键索引(自动创建)nameVARCHAR(50),ageINT,INDEXidx_name(name)-- 普通索引);为已有表添加索引
CREATEINDEXidx_nameONuser(name);或者使用ALTER TABLE语句:
ALTERTABLEuserADDINDEXidx_name(name);2. 不同类型的索引创建语法
唯一索引(UNIQUE):要求索引列的值不能重复(允许 NULL,但只能有一个 NULL)
CREATEUNIQUEINDEXidx_emailONuser(email);主键索引(PRIMARY KEY):一种特殊的唯一索引,不允许 NULL,一张表只能有一个
ALTERTABLEuserADDPRIMARYKEY(id);全文索引(FULLTEXT):用于全文检索,对CHAR、VARCHAR、TEXT列有效
CREATEFULLTEXTINDEXidx_contentONarticle(content);空间索引(SPATIAL):用于地理数据,要求列类型为GEOMETRY等
CREATESPATIALINDEXidx_locationONplace(location);3. 复合索引(多列索引)语法
按多列建立索引,遵循最左前缀原则:
CREATEINDEXidx_name_ageONuser(name,age);查询时如果条件用到name或者name + age都能使用该索引,但仅用age则无法使用。
4. 索引选项:指定长度与排序方式
前缀索引:只索引列的前 N 个字符,可节省空间(对长字符串很有用)
CREATEINDEXidx_name_prefixONuser(name(10));指定排序方向:MySQL 8.0 起支持降序索引(之前只支持升序)
CREATEINDEXidx_age_descONuser(ageDESC);5. 删除索引的语法
-- 通过索引名删除DROPINDEXidx_nameONuser;-- 使用 ALTER TABLE 删除ALTERTABLEuserDROPINDEXidx_name;-- 删除主键索引ALTERTABLEuserDROPPRIMARYKEY;6. 查看索引的语法
-- 查看表中所有索引SHOWINDEXFROMuser;-- 更详细的信息(包括索引类型、基数等)SHOWINDEXFROMuser\G;-- 垂直显示-- 使用 information_schema 查询SELECT*FROMinformation_schema.STATISTICSWHEREtable_name='user';7. 索引在查询语句中的使用(隐式语法)
虽然索引是用CREATE INDEX建的,但查询时自动使用,不必在SELECT中写任何索引关键字。
例如:
-- 假设 name 上有索引,下面这条查询会自动使用该索引SELECT*FROMuserWHEREname='张三';但也可以强制指定索引(不推荐,主要用于测试或特定优化场景):
SELECT*FROMuserFORCEINDEX(idx_name)WHEREname='张三';8. 修改表时添加索引(综合语法)
-- 可以在一句 ALTER TABLE 中添加多个索引ALTERTABLEuserADDINDEXidx_name(name),ADDUNIQUEINDEXidx_email(email),ADDFULLTEXTINDEXidx_bio(bio);9. 索引的约束与注意事项(语法层面需了解)
- 索引命名:可省略,MySQL 会自动生成,建议自定义有意义的名字(如
idx_列名)。 - 索引长度限制:InnoDB 引擎单索引最大长度为 3072 字节(取决于行格式)。
- 索引与存储引擎:全文索引和空间索引在 MyISAM 和 InnoDB 上的支持度不同。
- 显示使用索引的语法:
USE INDEX(建议)和IGNORE INDEX(忽略):SELECT*FROMuserUSEINDEX(idx_name)WHEREname='李四';SELECT*FROMuserIGNOREINDEX(idx_name)WHEREname='王五';
以上语法从建表时创建到查询时使用,由易到难,涵盖了 MySQL 索引最常用的操作。实际开发中,最常用的是CREATE INDEX和ALTER TABLE ADD INDEX两种方式。
三,性能分析
可以通过以下方法来分析当前数据库的性能。
1.查询频次
通过查看数据库全局或当前会话的各种操作频次,可以清晰地分析出当前数据库是以主导查询(读密集型)还是以主对增删改(写密集型)为主,从而决定索引的优化重心。
① 查看各类 SQL 执行频次命令
在 MySQL 客户端中,可以使用如下命令查询全局各类 SQL 的累计执行次数。该命令通常使用LIKE匹配七个字符长(_占位符)的统计项:
SHOWGLOBALSTATUSLIKE'Com_______';执行后,其核心返回指标及性能分析意义如下:
Com_select:查询(SELECT)操作的次数。核心指标。若该值远大于其他项,说明系统为“读多写少”,建立和优化索引的收益最高。Com_insert:插入(INSERT)操作的次数。若该值过高,说明写入频繁,此时建索引应保持克制,避免过多的索引拖慢写入性能。Com_update:更新(UPDATE)操作的次数。频繁更新的字段不适合建索引,因为每次更新都会触发索引树的重新调整。Com_delete:删除(DELETE)操作的次数。频繁删除会导致索引页产生碎片(空洞),后续需考虑配合OPTIMIZE TABLE整理。
注意:以上参数是从数据库启动开始刷新的累计值。如需查看当前会话的频次,可将
GLOBAL替换为SESSION。
② 查询频次对索引设计的决策指导
- 读密集型(
Com_select占比大,如 > 80%):应全力以赴优化索引。因为写操作极少,维护索引所带来的性能损耗在庞大的读收益面前微不足道。 - 写密集型(
Com_insert/Com_update占比大):索引设计应遵循“精准、精简”原则。优先建立能覆盖多条件的联合索引来替代多个单列索引,尽量用最少的索引树去满足最高频的查询。
2. 慢查询日志
慢查询日志(Slow Query Log)是 MySQL 提供的一种用来记录执行时间超过指定阈值以及扫描行数过多的 SQL 语句的日志。它是定位系统性能瓶颈、精准抓取需要优化索引的 SQL 的核心工具。
① 启用与核心参数配置
慢查询日志默认是关闭的,通常需要在开发测试环境或排查问题时开启。可以通过以下命令查看和临时修改相关配置参数(若想永久生效需修改my.cnf配置文件):
-- 查看慢查询日志是否开启,以及日志文件路径SHOWVARIABLESLIKE'slow_query_log%';-- 开启慢查询日志(1为开启,0为关闭)SETGLOBALslow_query_log=1;-- 查看并设置慢查询的时间阈值(单位:秒,支持微秒。此处设置为执行超过1秒则记录)SHOWVARIABLESLIKE'long_query_time';SETGLOBALlong_query_time=1;-- 开启记录没有使用索引的查询(可选)SETGLOBALlog_queries_not_using_indexes=1;② 慢查询日志的分析方法
当系统运行一段时间后,慢查询日志文件中会堆积大量的文本数据。直接查看原始日志效率较低,生产环境中通常采用以下两种方式进行分析:
**自带工具
mysqldumpslow**:MySQL 官方提供的命令行工具,可以对慢查询日志进行分类汇总、排序和去重。# 按照执行次数(c)排序,显示前10条最慢的 SQLmysqldumpslow-sc-t10/var/log/mysql/mysql-slow.log# 按照返回记录数(r)排序,查找高频返回大量数据的 SQLmysqldumpslow-sr-t10/var/log/mysql/mysql-slow.log第三方可视化分析:在实际生产中,更倾向于使用 **Percona Toolkit 中的
pt-query-digest**工具,或者将日志接入Prometheus + Grafana / ELK监控看板,实现慢 SQL 的直观发现与告警。
③ 慢查询日志对索引优化的指导意义
通过慢查询日志抓出目标 SQL 后,应配合EXPLAIN(执行计划)进行深度剖析。通常优化的常规路径为:
- 未走索引:若日志中显示
Rows_examined(扫描行数)很大,但Rows_sent(返回行数)很小,且未命中索引,则应针对WHERE或JOIN的字段建立相应索引。 - 索引失效:若已经建了索引,SQL 依然出现在慢日志中,需排查是否存在隐式类型转换、模糊查询开头带
%等导致索引失效的操作。 - 基数过大:若字段区分度极低(如状态字段),即使建了索引也会被 MySQL 优化器放弃,此时应考虑重构业务逻辑或引入缓存。
3. show profiles
show profiles是 MySQL 提供的一种用来剖析当前线程中 SQL 语句生命周期和资源消耗的性能分析工具。它可以精准地测量出一条 SQL 在执行过程中,各个阶段(如权限检查、开表、优化、清空数据、发送数据等)分别消耗了多少时间。
注意:在 MySQL 5.7 及更高版本中,
show profiles已被标记为废弃(Deprecated),官方建议使用更强大的Performance Schema(性能schema)进行替代。但由于其使用简单,在日常快速排查中仍被广泛使用。
① 启用与查看
该工具默认是关闭的,开启后会记录当前会话最近发起的 15 条(默认值)SQL 的执行分析。
-- 查看 show profiles 是否开启SELECT@@have_profiling;SHOWVARIABLESLIKE'profiling';-- 开启 profilingSETprofiling=1;-- 执行你的 SQL 语句(用于测试)SELECT*FROMusersWHEREage=18;-- 查看最近执行的 SQL 列表及其总耗时(获取 Query_ID)SHOWPROFILES;② 深度剖析特定 SQL
拿到SHOW PROFILES返回的Query_ID后,可以针对某一条 SQL 进行全方位的资源消耗拆解:
-- 查看指定 Query_ID 的每个执行阶段的具体耗时SHOWPROFILEFORQUERY1;-- 查看指定 SQL 阶段消耗的 CPU 资源SHOWPROFILE CPUFORQUERY1;-- 查看指定 SQL 阶段消耗的磁盘 I/O 资源SHOWPROFILE IOFORQUERY1;③ 性能调优的危险信号
在SHOW PROFILE的结果中,如果以下阶段的耗时过长,通常意味着 SQL 存在严重的性能问题:
Converting HEAP to MyISAM:查询结果太大,内存临时表不够用,正在将数据复制到磁盘上的临时表中。(急需优化查询或索引)Creating tmp table:正在创建临时表(通常由于复杂的GROUP BY或DISTINCT导致)。Copying to tmp table on disk:正在把内存中的临时表数据复制到磁盘。Locked:被锁住了,正在等待锁释放。
4. explain(执行计划)
explain是 MySQL 性能优化中最核心、最常用的利器。在任意SELECT、DELETE、UPDATE或INSERT语句前加上EXPLAIN关键字,MySQL 优化器就会模拟执行该 SQL,并返回一条执行计划。通过它,我们可以直接看到 SQL 底层是否使用了索引、使用了哪个索引、扫描了多少行数据等关键信息。
① 使用语法
EXPLAINSELECT*FROMusersWHEREname='张三';② 核心返回字段及分析意义
执行EXPLAIN后会返回一个表格,其中最核心的考量指标如下:
| 列名 (Column) | 含义 | 专家级调优关注点 |
|---|---|---|
type | 访问类型/连接类型 | 最核心指标。性能从好到差依次为:system>const>eq_ref>ref>range>index>ALL。• const/ref:命中唯一索引或普通索引。• range:索引范围扫描(如BETWEEN、>)。• index:全索引树扫描(虽然走了索引,但遍历了整棵树)。• ALL:全表扫描(性能最差,必须优化)。生产环境通常要求至少达到range级别,力争ref。 |
possible_keys | 可能会用到的索引 | 提示有哪些索引可以用来加速该查询。 |
key | 实际使用的索引 | 如果为NULL,则说明没有使用索引。如果此列有值,说明成功命中了该索引。 |
key_len | 实际使用索引的字节长度 | 联合索引中通过该值可以计算出到底命中了哪几个字段。在不损失精确度的情况下,该值越小越好。 |
rows | 预估需要扫描的行数 | 这是一个估计值。数值越小,意味着需要读取的磁盘数据页越少,性能越好。 |
filtered | 过滤后留下的记录比例 | 返回结果的行数占读取行数的百分比。值越大,说明索引过滤的效果越精准。 |
Extra | 额外附加信息 | 关键辅助指标。常见提示包括: • Using index:使用了覆盖索引,不需要回表,性能极高。• Using index condition:使用了索引下推(ICP)优化。• Using filesort:使用了文件排序,未利用索引排序,性能较差,需优化。• Using temporary:使用了临时表(常见于未加索引的GROUP BY),性能极差。 |
四,使用规则
使用explain和show profiles可以看到每条sql执行的效率,可以利用他们来看有索引和没索引的区别。
1. 最左前缀法则(最左匹配原则)
最左前缀法则(Most-Left Prefix Rule)是联合索引(组合索引)在实际查询中能否生效的核心判断依据。
联合索引在底层 B+Tree 中是按照创建时从左到右的字段顺序进行全局排序的。只有当左边的列值完全相同时,才会对右边的列值进行排序。因此,查询时必须从索引的最左列开始,并且不能跳过中间的列,否则索引将部分或完全失效。
① 核心规则解析
假设我们在表上建立了一个三列的联合索引:idx_user_pro (name, age, city),其底层排序逻辑是先按name排序;在name相同的情况下再按age排序;在age也相同的情况下最后按city排序。
基于此逻辑,不同查询条件的命中情况如下:
- 完全匹配(全额命中):
-- 完美触发索引,三个字段都在索引树中实现精准定位EXPLAINSELECT*FROMusersWHEREname='张三'ANDage=18ANDcity='北京';- 部分匹配(满足最左前缀):
-- 命中 name 和 age。因为从最左侧连续匹配,未发生断档EXPLAINSELECT*FROMusersWHEREname='张三'ANDage=18;-- 仅命中 name。最左列存在,但 age 断档,导致 city 无法使用索引EXPLAINSELECT*FROMusersWHEREname='张三'ANDcity='北京';- 完全失效(丢失最左前缀):
-- 索引完全失效!因为缺少最左列 name,右侧的 age 和 city 在全局上是无序的,只能走全表扫描EXPLAINSELECT*FROMusersWHEREage=18ANDcity='北京';💡 专家提示(位置无关性):
下面这条 SQL 依然可以完全命中联合索引:EXPLAINSELECT*FROMusersWHEREage=18ANDcity='北京'ANDname='张三';这里的“最左”指的是是否存在,而不是在 SQL 语句中的书写顺序。MySQL 的查询优化器(Optimizer)非常聪明,它会在执行前自动颠倒条件顺序,使其去匹配联合索引。
② 范围查询导致右侧索引失效
最左前缀法则中还有一个高频翻车场景:在联合索引中,如果出现了范围查询(如>,<,BETWEEN,LIKE 'xx%'),则该范围列右侧的索引字段将全部失效。
- 右侧失效示例:
-- 此时索引仅命中了 name 和 age 两个字段,city 索引失效-- 原因:age 变成了范围,在多个不同 age 的结果集里,city 的排列不再具有整体全局有序性EXPLAINSELECT*FROMusersWHEREname='张三'ANDage>18ANDcity='北京';- 优化规避方案:
在业务逻辑允许的情况下,尽量使用大于等于(>=)或小于等于(<=)。在 MySQL 5.7+ 之后,如果是等值边界,右侧的索引有概率能继续复用(即常说的业务等值下推)。
③ 索引列上的各种“骚操作”导致失效
除了最左前缀没拉满,在字段上进行以下操作也会导致联合索引(甚至单列索引)直接报废:
- 对索引列进行函数或表达式计算:
WHERE YEAR(birthday) = 2026(应改为范围查询:WHERE birthday BETWEEN...) - 隐式类型转换:
WHERE phone = 13800000000(若 phone 是VARCHAR类型,传入数字会导致全表扫描) - 模糊查询前缀带 %:
WHERE name LIKE '%三'(前缀未知,无法在 B+Tree 树中导航;但LIKE '张%'可以走索引)
除了违反以上的最左前缀法则会失效的情况,还有一些可能的原因。
2. 索引失效的常见场景
在实际开发中,即使为字段建立了索引,如果 SQL 语句书写不当,也会导致 MySQL 优化器放弃走索引而选择全表扫描(type=ALL)。以下是生产环境中最常见的 7 种索引失效场景:
① 违反最左前缀法则
对于联合索引,如果查询条件中缺失了索引的最左列,或者跳过了中间的列,会导致右侧的索引字段全部失效。
- 失效示例:联合索引为
(a, b, c),查询条件为WHERE b = 1 AND c = 2(缺失最左列a,索引完全失效)。
② 联合索引中范围查询右侧的列失效
在联合索引中,如果某个字段使用了范围查询(如>,<,NOT IN,LIKE等),那么该字段右侧的所有索引列都将无法用于建立检索。
- 失效示例:联合索引为
(name, age, city),查询条件为WHERE name = '张三' AND age > 18 AND city = '北京'。此时name和age走索引,但city失效。
③ 在索引列上进行运算或函数操作
对索引列进行任何数学运算、拼接或使用内置函数,都会破坏 B+Tree 中索引值的原有顺序,导致优化器无法在树结构中进行二分查找。
- 错误写法:
WHERE age + 1 = 19; - 错误写法:
WHERE SUBSTRING(name, 1, 2) = '张三'; - 正确改写:
WHERE age = 19 - 1;(将运算移至等号右侧的常量)
④ 存在隐式类型转换
当索引列的字段类型与传入的查询参数类型不一致时,MySQL 会在底层自动调用函数进行转换,从而导致索引失效。这种情况最常发生在字符串(VARCHAR)与数字(INT)的比对上。
- 错误写法:
WHERE phone = 13800000000;(假设phone在数据库中是VARCHAR类型,传入数字会导致全表扫描) - 正确写法:
WHERE phone = '13800000000';
⑤ 模糊查询时通配符%在开头
B+Tree 索引是按照字符从左到右的顺序建立索引树的。如果通配符%放在开头,意味着前缀字符完全未知,MySQL 无法在索引树中向下导航。
- 失效示例:
WHERE name LIKE '%三';(全表扫描) - 生效示例:
WHERE name LIKE '张%';(可以正常走索引范围扫描range)
⑥ 使用OR连接条件时缺失索引
在使用OR连接多个条件时,必须要求OR两边的所有字段都建立了索引,否则整个查询的索引都会失效。
- 失效示例:
WHERE id = 1 OR age = 18;(主键id有索引,但age没有索引,导致整体走全表扫描)
⑦ 估计全表扫描比索引快(数据区分度低)
MySQL 优化器在执行 SQL 前会进行成本计算。如果查询的数据量占全表数据的比例过大(通常在 20%~30% 以上),或者字段的区分度极低(如性别、状态字段),优化器会认为“先读索引树、再频繁回表”的开销远大于直接全表扫描,从而主动放弃索引。
- 典型场景:
WHERE status = 1;(假设表中 90% 的数据status都等于 1,此时即使有索引也不会触发)。
五、 索引的设计原则
在实际开发中,索引并不是建得越多越好。为了在提升查询效率与降低维护成本之间取得最佳平衡,设计索引时应遵循以下核心原则:
1. 核心针对性原则
- 高频查询优先:优先为经常出现在
WHERE子句、JOIN关联条件、ORDER BY排序以及GROUP BY分组中的字段建立索引。 - 高区分度优先:选择字段值重复率低、区分度高的列作为索引(如手机号、用户ID)。联合索引的设计要求字段基数(Cardinality)越大,越要往左边放。
计算公式:区分度 = 表中不同值的数量 表总行数 \text{区分度} = \frac{\text{表中不同值的数量}}{\text{表总行数}}区分度=表总行数表中不同值的数量,该值越接近 1,说明越适合建索引。
- 尽量避免低区分度列:诸如“性别(男/女)”、“状态(0/1)”这类只有少数几种取值的字段,不适合单独建立索引,因为即使建了,MySQL 优化器也大概率会放弃它而走全表扫描。
2. 数量与空间控制原则
- 控制单表索引数量:一张表的索引数量通常建议控制在5 个以内。过多的索引会严重拖慢
INSERT、UPDATE、DELETE的速度,并挤占大量的磁盘空间。 - 联合索引代单列索引:如果多个字段经常组合出现,优先建立一个联合索引,而不是针对每个字段各建一个单列索引。联合索引能够极大地减少表中的索引树数量。
- 短索引(前缀索引)原则:对于
VARCHAR(255)或TEXT等较长的字符串字段,如果需要建索引,不应整个字段去建,而是指定前缀长度(如取前 10 个字符)。这能显著降低索引树的大小,提升内存利用率。
3. 业务防错与优化原则
- 利用唯一约束:业务上具有唯一性特征的字段(如身份证号、订单号),必须建立唯一索引(UNIQUE)。这不仅能加速查询,还能在数据库底层提供强制的业务幂等性保障。
- 力求覆盖索引:在设计联合索引时,尽量让索引包含
SELECT需要返回的字段。一旦实现覆盖索引,MySQL 就能直接在二级索引树上返回数据,彻底免除“回表”操作,性能会发生质的飞跃。 - 慎用频繁更新列:若某个字段的业务修改极其频繁(如用户的步数、账户余额、点击量),不建议为其建立索引,否则每次更新都会导致 B+Tree 频繁发生页分裂和节点重组。