news 2026/4/23 12:24:54

MySQL 基础教程 - 第八章:索引与性能优化基础

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
MySQL 基础教程 - 第八章:索引与性能优化基础

MySQL 基础教程 - 第八章:索引与性能优化基础

摘要:本章将深入探讨 MySQL 数据库性能优化的核心——索引。我们将从底层 B+ 树数据结构讲起,详细介绍索引的分类、创建方法及设计原则(如最左前缀法则、覆盖索引)。同时,通过EXPLAIN命令深入分析 SQL 执行计划,帮助你识别并解决慢查询问题。本章内容基于 MySQL 5.7.44 版本,重点关注 InnoDB 存储引擎。

8.1 前置准备:数据与索引

为了确保本章的EXPLAIN示例能输出预期的执行计划,我们需要确保usersorders表中包含足够的数据和正确的索引结构。请在开始本章学习前,执行以下 SQL 脚本进行环境校准。

⚠️ 说明:此脚本会尝试补充缺失的字段、索引和数据,不会删除您现有的表结构(除非使用 DROP 重建)。

USEshop_biz;-- 1. 确保 users 表结构与索引完整-- 如果 users 表不存在,请参考前面章节创建。这里重点检查索引。-- 尝试添加索引 (如果已存在可能会报错,可忽略 Duplicate key name 错误,或者使用可视化工具检查)-- 为了演示 8.4.3 节的 ref 类型查询ALTERTABLEusersADDINDEXidx_username(username);-- 2. 确保 orders 表包含 create_time 字段 (用于演示最左前缀)-- 如果报错 Duplicate column name,说明字段已存在,忽略即可ALTERTABLEordersADDCOLUMNcreate_timeDATETIMEDEFAULTCURRENT_TIMESTAMPCOMMENT'下单时间';-- 3. 确保 orders 表包含复合索引 (用于 8.3.1 节)ALTERTABLEordersADDINDEXidx_user_time(user_id,create_time);-- 4. 数据补充 (确保有数据可查)-- 插入 users 测试数据INSERTIGNOREINTOusers(user_id,username,email,age,gender)VALUES(1,'alice','alice@example.com',25,'female'),(2,'bob','bob@example.com',30,'male'),(3,'charlie','charlie@example.com',22,'male'),(4,'david','david@example.com',35,'male'),(5,'eve','eve@example.com',28,'female');-- 插入 orders 测试数据INSERTIGNOREINTOorders(order_id,user_id,total_amount,order_status,create_time)VALUES(1,1,100.00,1,'2023-10-01 10:00:00'),(2,1,50.00,1,'2023-10-02 11:00:00'),(3,2,200.00,0,'2023-10-01 12:00:00'),(4,3,300.00,2,'2023-10-03 09:00:00'),(5,1,120.00,1,'2023-10-05 14:00:00');

8.2 索引概述

8.2.1 什么是索引?

索引(Index)是帮助 MySQL高效获取数据数据结构
如果不使用索引,MySQL 必须从第一行记录开始扫描整个表(Full Table Scan),直到找到符合要求的记录。表越大,查询越慢。
使用索引后,MySQL 可以像查字典一样,通过目录快速定位到数据所在的位置。

  • 优点
    • 极大提高数据检索效率(O(logN) vs O(N))。
    • 通过索引列对数据进行排序,降低数据排序的成本(降低 CPU 消耗)。
  • 代价
    • 空间成本:索引本身也是表,需要占用磁盘空间。
    • 时间成本:当对表进行 INSERT、UPDATE、DELETE 操作时,MySQL 不仅要保存数据,还要维护索引(如 B+ 树的分裂与合并),会降低写操作的性能。

8.2.2 索引的数据结构:B+ Tree

在 MySQL 的 InnoDB 存储引擎中,索引的底层实现是B+ 树(多路平衡搜索树)。

  • 结构特点

    1. 非叶子节点(内部节点)只存储键值(Key)和指针,不存储实际数据行。这使得每个节点能容纳更多的键值,降低树的高度。
    2. 叶子节点(Leaf Nodes)存储了所有的键值和对应的数据(或主键 ID)。
    3. 双向链表:所有叶子节点通过指针连接成一个双向链表。这使得范围查询(Range Scan,如BETWEEN,>,<)非常高效,只需找到起点,然后顺着链表遍历即可。
  • 为什么不用二叉树或 Hash?

    • 二叉树:树高度太高,导致磁盘 I/O 次数过多(索引通常存储在磁盘上,每一层访问都可能产生一次 I/O)。
    • Hash:只适合等值查询(=),不支持范围查询(>,<),不支持排序。

8.3 索引分类与创建

8.3.1 按功能逻辑分类

  1. 普通索引 (Normal Index)

    • 最基本的索引,没有任何限制。
    • 创建:CREATE INDEX index_name ON table(column);ALTER TABLE table ADD INDEX index_name(column);
  2. 唯一索引 (Unique Index)

    • 索引列的值必须唯一,但允许有空值(NULL)。
    • 创建:CREATE UNIQUE INDEX index_name ON table(column);
    • 作用:除了加速查询,更主要用于约束数据的唯一性(如email字段)。
  3. 主键索引 (Primary Key)

    • 一种特殊的唯一索引,不允许有空值。
    • 聚簇索引 (Clustered Index):在 InnoDB 中,主键索引的叶子节点直接存储了整行数据
    • 每个表只能有一个主键。
  4. 全文索引 (Fulltext Index)

    • 用于全文搜索,解决LIKE '%word%'效率低的问题。
    • MySQL 5.7 InnoDB 支持全文索引,但对中文支持需要配置 ngram 解析器(此处不展开,通常建议使用专门的搜索引擎如 ES)。
  5. 组合索引 (Composite Index)

    • 在多个字段上创建的索引,如KEY idx_user_time (user_id, create_time)

8.3.2 索引管理实战

场景:假设我们需要经常根据order_status查询订单。

-- 1. 查看现有索引SHOWINDEXFROMorders;-- 2. 创建普通索引-- 方式一:CREATE INDEXCREATEINDEXidx_statusONorders(order_status);-- 方式二:ALTER TABLE (推荐)ALTERTABLEordersADDINDEXidx_total(total_amount);-- 3. 创建唯一索引-- 假设我们有个业务流水号字段 serial_no 需要唯一-- ALTER TABLE orders ADD UNIQUE INDEX idx_serial (serial_no);-- 4. 删除索引DROPINDEXidx_statusONorders;-- 或者ALTERTABLEordersDROPINDEXidx_total;


8.4 索引设计原则 (核心)

这是面试和实战中最重要的部分。索引不是越多越好,设计不当反而拖慢系统。

8.4.1 最左前缀法则 (Most Left Prefix)

⚠️ 前置检查:本节演示需要orders表包含create_time字段和idx_user_time索引。
如果你一直跟随教程操作,请先执行以下 SQL 补充结构:

-- 1. 补充 create_time 字段ALTERTABLEordersADDCOLUMNcreate_timeDATETIMEDEFAULTCURRENT_TIMESTAMPCOMMENT'下单时间';-- 2. 补充测试数据 (可选,为了演示效果更好)UPDATEordersSETcreate_time='2023-10-01 10:00:00'WHEREorder_id=1;-- 3. 创建复合索引 (核心)ALTERTABLEordersADDINDEXidx_user_time(user_id,create_time);

对于组合索引(例如idx_user_time (user_id, create_time)),查询时必须从索引的最左边列开始匹配。

  • 有效的情况

    -- 1. 查询条件包含最左列 (user_id)SELECT*FROMordersWHEREuser_id=1;-- 走索引-- 2. 查询条件包含前缀列 (user_id AND create_time)SELECT*FROMordersWHEREuser_id=1ANDcreate_time>'2023-10-01';-- 走索引
  • 失效的情况

    -- 1. 跳过最左列,直接查第二个列SELECT*FROMordersWHEREcreate_time>'2023-10-01';-- ❌ 不走 idx_user_time 索引(全表扫描),因为不知道 user_id,B+树无法定位
  • 范围查询截断
    如果组合索引是(a, b, c),查询WHERE a=1 AND b>2 AND c=3

    • a用到了索引。
    • b用到了索引(范围查询)。
    • c无法使用索引。因为b是范围,后面的列在 B+ 树中不再有序。

8.4.2 覆盖索引 (Covering Index)

如果一个索引包含(覆盖)了所有需要查询的字段的值,MySQL 就不需要回表(Back Query)去查找整行数据。

  • 回表:普通索引(二级索引)的叶子节点存储的是主键ID。如果查询需要其他字段,MySQL 需先查普通索引拿到 ID,再去主键索引查整行数据。

  • 覆盖索引示例
    现有索引idx_user_time (user_id, create_time)

    -- 情况 A:需要回表SELECT*FROMordersWHEREuser_id=1;-- 需要查 * (所有字段),索引里只有 user_id 和 create_time,需要回表查 total_amount 等。-- 情况 B:覆盖索引 (高性能)SELECTuser_id,create_timeFROMordersWHEREuser_id=1;-- 只需要查 user_id 和 create_time,这些都在索引里,直接返回,不需要回表。-- EXPLAIN 输出中 Extra 字段会显示 "Using index"。

8.4.3 索引失效的常见场景

  1. 在索引列上进行运算或函数操作
    -- ❌ 失效SELECT*FROMusersWHEREYEAR(create_time)=2023;-- ✅ 优化:改写为范围查询SELECT*FROMusersWHEREcreate_timeBETWEEN'2023-01-01'AND'2023-12-31 23:59:59';
  2. 字符串不加单引号(类型隐式转换):
    如果phone字段是VARCHAR,查询WHERE phone = 13800000000,MySQL 会自动做CAST(phone AS SIGNED),导致全表扫描。
  3. 模糊查询以 % 开头
    -- ❌ 失效SELECT*FROMusersWHEREusernameLIKE'%ice';-- ✅ 有效 (最左匹配)SELECT*FROMusersWHEREusernameLIKE'ali%';
  4. OR 连接的条件
    如果WHERE a=1 OR b=2,除非ab都有索引,否则索引会失效。

8.5 慢查询分析工具:EXPLAIN

EXPLAIN是查看 SQL 执行计划的神器。它模拟优化器执行 SQL 语句,告诉我们 MySQL 是如何处理你的 SQL 的。

8.5.1 基本用法

EXPLAINSELECT*FROMusersWHEREuser_id=1;

执行后会返回一张表,包含以下关键字段:

8.5.2 关键字段详解

字段含义详细解释
id选择标识符select 查询的序列号。id 越大越先执行;id 相同则从上往下执行。
select_type查询类型SIMPLE(简单查询,不含子查询/UNION)、PRIMARY(主查询)、SUBQUERY(子查询) 等。
type访问类型(重要)性能从好到坏:
system>const>eq_ref>ref>range>index>ALL
possible_keys可能用到的索引MySQL 猜测可能使用的索引列表。
key实际使用的索引MySQL 实际决定使用的索引。如果为 NULL,则没用索引。
key_len索引长度使用了索引的多少字节。有助于判断组合索引是否被充分利用。
rows扫描行数预计扫描的行数(估算值),越小越好。
Extra额外信息Using index(覆盖索引,好);
Using where(需回表过滤);
Using filesort(需要额外的排序操作,);
Using temporary(使用了临时表,极坏)。

8.5.3 Type 详解与实战演示

我们通过具体的 SQL 看看type的变化。

  1. const:通过主键或唯一索引命中一条记录。

    EXPLAINSELECT*FROMusersWHEREuser_id=1;-- type: const, key: PRIMARY
  2. ref:非唯一性索引扫描,返回匹配某个单独值的所有行。

    EXPLAINSELECT*FROMusersWHEREusername='alice';-- type: ref, key: idx_username-- 注意:如果 username 不是唯一索引,则是 ref;如果是唯一索引,则是 const。
  3. range:索引范围扫描,常见于<, >, BETWEEN, IN

    EXPLAINSELECT*FROMusersWHEREuser_id>1;-- type: range, key: PRIMARY
  4. index:全索引扫描 (Full Index Scan)。扫描遍历索引树,通常比 ALL 快,因为索引文件通常比数据文件小。

    -- 查询 id (主键) 和 username (索引列),不需要回表,但需要扫描整个 username 索引树EXPLAINSELECTuser_id,usernameFROMusers;-- type: index, Extra: Using index
  5. ALL:全扫描 (Full Table Scan)。最差的情况。

    -- gender 字段没有索引 (虽然建表时可能有默认,假设这里没建或区分度低被优化器忽略)-- 或者查询非索引列EXPLAINSELECT*FROMusersWHEREage=25;-- type: ALL (假设 age 字段未建索引)

8.5.4 实战案例:Filesort 优化

假设我们要查询订单,按total_amount排序。

-- 场景:没有索引时的排序EXPLAINSELECT*FROMordersORDERBYtotal_amount;-- type: ALL, Extra: Using filesort-- 说明 MySQL 无法利用索引完成排序,必须在内存中(或磁盘)进行额外的排序操作,性能差。

优化:添加索引。

ALTERTABLEordersADDINDEXidx_amount(total_amount);EXPLAINSELECTtotal_amountFROMordersORDERBYtotal_amount;-- type: index, Extra: Using index-- 此时直接读取索引树(本身有序),无需额外排序。

8.6 总结

  1. 索引是双刃剑:能极大提升读性能,但会降低写性能并占用空间。
  2. 设计原则
    • 高频查询的字段建索引。
    • 区分度高(唯一性强)的字段适合建索引(如 ID、手机号);区分度低(如性别、状态)通常不适合。
    • 利用最左前缀覆盖索引减少回表。
  3. 分析习惯:写完复杂 SQL 后,习惯性用EXPLAIN看一下执行计划,确保没有出现ALLUsing filesort(在数据量大时)。

下一章,我们将进入数据库事务的世界,探讨如何保证数据的一致性与隔离性。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/23 10:45:32

推荐3个超级实用并且HR也推荐的简历模板工具

在2026年的求职市场中&#xff0c;找到一份合适的工作并不容易。很多求职者存在一个误区&#xff0c;以为所有的简历都会立刻进入机器筛选系统&#xff08;ATS&#xff09;。事实并非完全如此。招聘市场其实是分层的&#xff1a;中小企业&#xff08;SME&#xff09;&#xff1…

作者头像 李华
网站建设 2026/4/23 12:18:23

第 9 篇:代理模式 (Proxy) —— 硬件的“防火墙”

专栏导读:代理模式和适配器模式看起来很像(都是包一层),但目的完全不同。适配器是为了**“兼容接口”,代理是为了“控制访问”**。在嵌入式中,代理模式常用于实现:延迟写(Lazy Write)、缓存(Caching)、权限控制(Protection)和磨损均衡(Wear Leveling)。 1. 场景…

作者头像 李华
网站建设 2026/4/18 23:51:13

房屋交易平台信息管理系统源码-SpringBoot后端+Vue前端+MySQL【可直接运行】

摘要 随着互联网技术的快速发展&#xff0c;房地产行业的信息化需求日益增长。传统的房屋交易方式存在信息不对称、效率低下等问题&#xff0c;亟需通过数字化手段优化交易流程。房屋交易平台信息管理系统旨在整合房源信息、买卖双方需求以及交易流程&#xff0c;为用户提供高…

作者头像 李华
网站建设 2026/4/11 22:50:01

EASY EAI灵眸科技聚势瑞芯微AI软件生态大会

2026年1月27日&#xff0c;瑞芯微AI软件生态大会将于福建福州盛大启幕&#xff0c;EASY EAI灵眸科技作为瑞芯微生态合作伙伴将应邀出席本次盛会。 作为瑞芯微硬件生态合作伙伴的EASY EAI灵眸科技&#xff0c;紧随国产化嵌入式处理器发展浪潮&#xff0c;依托核心板、工控板等多…

作者头像 李华
网站建设 2026/4/22 16:29:57

Flutter for HarmonyOS 开发指南(四):实现上拉加载,下拉刷新能力

前言实现一个Flutter 应用中常见且核心的功能&#xff1a;上拉加载更多和下拉刷新。将从最基础的实现方式入手&#xff0c;使用 Flutter 内置的组件和控制器来构建这个功能。核心思路下拉刷新&#xff1a;使用 Flutter 官方提供的 RefreshIndicator 组件。它能够监听子组件的下…

作者头像 李华