【MySQL】《MySQL索引 与 慢SQL优化 面试问答清单》(附《思维导图》)

简介: 本文是面向MySQL开发者与DBA的索引与慢SQL优化实战指南,涵盖B+树原理、聚簇/覆盖索引、EXPLAIN深度解读、四大阶段优化(发现→分析→索引→重构)及电商订单/多表联查真实案例,含可打印速查表,助你将慢查询从3秒优化至0.005秒。

思维导图

一、面试问答清单(按考点优先级排序)

索引基础核心考点

  1. Q:什么是索引?底层数据结构是什么?
    A:索引是帮助MySQL高效获取数据的排好序的数据结构。InnoDB默认使用B+树,特点是:所有数据都在叶子节点,非叶子节点只存索引键+指针;叶子节点通过双向链表相连,天然支持范围查询、排序和分组;树高通常为3-4层,可存储千万级数据。

  2. Q:B+树和B树的区别?
    A:① B树非叶子节点也存数据,B+树非叶子节点只存索引键;② B+树叶子节点通过双向链表相连,B树没有;③ B+树查询更稳定(所有查询都要到叶子节点);④ B+树更适合范围查询。

  3. Q:聚簇索引和非聚簇索引的区别?
    A:① 聚簇索引:InnoDB独有,每张表有且只有一个,叶子节点直接存储整行完整数据,数据物理存储顺序与索引逻辑顺序一致;② 非聚簇索引:叶子节点只存储主键值,查询需要回表操作。

  4. Q:为什么InnoDB表必须有主键,并且推荐使用自增主键?
    A:① InnoDB通过主键聚簇索引组织数据,如果没有主键,会自动生成隐藏自增列;② 自增主键是连续的,插入数据时是顺序写入,不会产生页分裂;③ 自增主键占用空间小,二级索引的叶子节点存储主键值,能节省大量空间。

  5. Q:什么是最左前缀原则?
    A:联合索引从左到右匹配,遇到范围查询(>、<、BETWEEN)就停止匹配。例如联合索引(a,b,c),查询条件a=1 AND b>2 AND c=3,只能用到a和b两列。

  6. Q:什么是覆盖索引?有什么优势?
    A:查询的所有字段都在索引中,无需回表。优势:① 避免回表操作,减少IO次数;② 索引远小于数据行,能显著提高查询性能。

Explain执行计划核心考点

  1. Q:Explain执行计划中最重要的字段有哪些?
    A:id、select_type、type、key、key_len、rows、Extra

  2. Q:type字段的各个级别及性能排序?
    A:性能从优到劣:system > const > eq_ref > ref > range > index > ALL。优化铁律:至少要达到range级别,最好能达到ref级别。

  3. Q:key_len字段有什么作用?如何计算?
    A:用于判断联合索引实际使用了多少列。计算规则:整型TINYINT(1)、SMALLINT(2)、INT(4)、BIGINT(8);CHAR(n)=n字节,VARCHAR(n)=n+2字节;允许NULL额外+1字节。

  4. Q:Extra字段中哪些信息需要特别关注?
    A:✅ 好的:Using index(覆盖索引)、Select tables optimized away;❌ 坏的:Using filesort(文件排序)、Using temporary(临时表)。

慢SQL优化核心考点

  1. Q:慢SQL优化的完整流程是什么?
    A:① 发现:开启慢查询日志,使用mysqldumpslow或pt-query-digest分析;② 分析:使用EXPLAIN查看执行计划;③ 优化:索引优化、SQL重构、表结构优化、系统级优化;④ 验证:对比优化前后的执行计划和性能。

  2. Q:索引失效的常见场景有哪些?
    A:① 违反最左前缀原则;② 索引列上使用函数或表达式;③ 隐式类型转换;④ 模糊查询以%开头;⑤ 使用OR连接非索引列;⑥ 负向查询(NOT IN、!=);⑦ 优化器认为全表扫描更快。

  3. Q:深分页问题如何优化?
    A:① 游标分页:SELECT * FROM table WHERE id > last_id LIMIT 20;② 覆盖索引+延迟关联:SELECT t.* FROM table t JOIN (SELECT id FROM table LIMIT 10000, 20) tmp ON t.id = tmp.id

  4. Q:JOIN查询的优化原则是什么?
    A:① 小表驱动大表;② 确保JOIN字段有索引;③ 尽量减少JOIN的表数量。

  5. Q:如何优化GROUP BY和ORDER BY?
    A:让索引的顺序与GROUP BY或ORDER BY的顺序一致,避免产生临时表和文件排序。

高级技巧考点

  1. Q:什么是函数索引?适用于什么场景?
    A:MySQL 8.0+支持为函数表达式创建索引。适用于需要在索引列上使用函数的场景,如YEAR(create_time)

  2. Q:什么是索引合并?为什么通常不推荐?
    A:当查询条件涉及多个单列索引时,MySQL可能会合并这些索引的结果。但索引合并需要多次扫描索引并合并结果,效率通常不如一个覆盖所有条件的联合索引。

  3. Q:优化的优先级是什么?
    A:SQL与索引 > 表结构 > 配置参数 > 硬件与架构。


二、电商订单查询完整优化实战案例

场景背景

某电商平台订单表orders有500万条数据,用户在订单中心查询"我的订单"时,页面加载需要3-5秒,严重影响用户体验。

步骤1:发现慢SQL

通过慢查询日志发现以下SQL:

SELECT * FROM orders 
WHERE user_id = 123456 AND order_status IN (1, 2, 3) 
ORDER BY create_time DESC 
LIMIT 0, 20;

执行时间:2.8秒,扫描行数:约120万行。

步骤2:分析执行计划

EXPLAIN SELECT * FROM orders 
WHERE user_id = 123456 AND order_status IN (1, 2, 3) 
ORDER BY create_time DESC 
LIMIT 0, 20;

执行计划结果:

id select_type table type possible_keys key key_len rows Extra
1 SIMPLE orders ALL NULL NULL NULL 5000000 Using where; Using filesort

问题诊断

  1. type=ALL:全表扫描,没有使用任何索引
  2. Extra=Using filesort:无法利用索引排序,使用了文件排序
  3. rows=5000000:扫描了整个表的所有数据

步骤3:第一次优化 - 添加基础索引

为查询条件涉及的字段添加索引:

-- 为user_id添加普通索引
CREATE INDEX idx_user_id ON orders(user_id);

再次执行EXPLAIN:

id select_type table type possible_keys key key_len rows Extra
1 SIMPLE orders ref idx_user_id idx_user_id 8 120000 Using where; Using filesort

优化效果

  • 执行时间:0.6秒(提升79%)
  • 扫描行数:12万行(减少97.6%)

剩余问题

  • 仍然有Using filesort,需要对12万行数据进行排序
  • 当用户翻到第100页(LIMIT 2000, 20)时,执行时间又回到2秒以上

步骤4:第二次优化 - 创建联合索引

遵循"等值列在前,范围列在后"原则,创建覆盖查询条件和排序的联合索引:

-- 删除旧索引
DROP INDEX idx_user_id ON orders;

-- 创建联合索引:等值列user_id在前,然后是排序字段create_time
CREATE INDEX idx_user_id_create_time ON orders(user_id, create_time DESC);

再次执行EXPLAIN:

id select_type table type possible_keys key key_len rows Extra
1 SIMPLE orders ref idx_user_id_create_time idx_user_id_create_time 8 120000 Using where

优化效果

  • 执行时间:0.08秒(再提升87%)
  • Using filesort消失了,因为索引本身就是按create_time排序的
  • 翻到第100页(LIMIT 2000, 20)执行时间:0.1秒

剩余问题

  • 查询使用了SELECT *,需要回表获取整行数据
  • 当订单量继续增长到1000万时,深分页问题会再次出现

步骤5:第三次优化 - 使用覆盖索引

将查询需要的字段都包含在索引中,避免回表:

-- 删除旧索引
DROP INDEX idx_user_id_create_time ON orders;

-- 创建覆盖索引:包含查询条件、排序和需要返回的字段
CREATE INDEX idx_user_id_create_time_cover ON orders(
    user_id, create_time DESC, 
    order_id, order_status, total_amount, pay_time
);

修改SQL,只查询需要的字段:

SELECT order_id, order_status, total_amount, pay_time, create_time 
FROM orders 
WHERE user_id = 123456 AND order_status IN (1, 2, 3) 
ORDER BY create_time DESC 
LIMIT 0, 20;

再次执行EXPLAIN:

id select_type table type possible_keys key key_len rows Extra
1 SIMPLE orders ref idx_user_id_create_time_cover idx_user_id_create_time_cover 8 120000 Using where; Using index

优化效果

  • 执行时间:0.01秒(再提升87.5%)
  • Extra=Using index:使用了覆盖索引,无需回表
  • 翻到第1000页(LIMIT 20000, 20)执行时间:0.02秒

步骤6:第四次优化 - 解决深分页问题

当用户翻到第10000页(LIMIT 200000, 20)时,即使使用覆盖索引,也需要扫描200020行数据。使用游标分页彻底解决深分页问题:

前端改造:每次请求带上上一页最后一条记录的create_time和order_id

-- 第一页
SELECT order_id, order_status, total_amount, pay_time, create_time 
FROM orders 
WHERE user_id = 123456 AND order_status IN (1, 2, 3) 
ORDER BY create_time DESC, order_id DESC 
LIMIT 20;

-- 第N页(last_create_time是上一页最后一条记录的create_time,last_order_id是上一页最后一条记录的order_id)
SELECT order_id, order_status, total_amount, pay_time, create_time 
FROM orders 
WHERE user_id = 123456 
  AND order_status IN (1, 2, 3) 
  AND create_time <= '2024-05-01 12:00:00' 
  AND (create_time < '2024-05-01 12:00:00' OR order_id < 123456789)
ORDER BY create_time DESC, order_id DESC 
LIMIT 20;

最终效果

  • 无论翻到多少页,执行时间都稳定在0.01秒以内
  • 系统可以轻松支持千万级订单量的查询

优化总结

优化阶段 执行时间 扫描行数 主要问题
优化前 2.8秒 500万行 全表扫描+文件排序
第一次优化 0.6秒 12万行 仍有文件排序
第二次优化 0.08秒 12万行 需要回表
第三次优化 0.01秒 12万行 深分页问题
第四次优化 0.01秒 20行

总性能提升:280倍

三、多表联查订单详情完整优化实战案例

场景背景

电商平台订单详情页需要展示订单基本信息、商品明细、用户信息和收货地址,涉及4张表关联查询:

  • orders:订单主表,500万行数据
  • order_items:订单明细表,2000万行数据
  • products:商品表,10万行数据
  • users:用户表,100万行数据

步骤1:发现慢SQL

订单详情页加载时间超过1.5秒,通过慢查询日志定位到以下SQL:

SELECT * 
FROM orders o
JOIN order_items oi ON o.order_id = oi.order_id
JOIN products p ON oi.product_id = p.product_id
JOIN users u ON o.user_id = u.user_id
WHERE o.order_id = 123456789;

执行时间:1.2秒,扫描行数:约320万行。

步骤2:分析执行计划

EXPLAIN SELECT * 
FROM orders o
JOIN order_items oi ON o.order_id = oi.order_id
JOIN products p ON oi.product_id = p.product_id
JOIN users u ON o.user_id = u.user_id
WHERE o.order_id = 123456789;

执行计划结果(关键部分):

id select_type table type possible_keys key key_len rows Extra
1 SIMPLE o const PRIMARY PRIMARY 8 1
1 SIMPLE oi ALL NULL NULL NULL 20000000 Using where
1 SIMPLE p ALL NULL NULL NULL 100000 Using join buffer
1 SIMPLE u ALL NULL NULL NULL 1000000 Using join buffer

问题诊断

  1. order_itemsproductsusers表全表扫描(type=ALL
  2. 没有为JOIN字段创建索引
  3. 使用SELECT *查询了大量不需要的字段
  4. 大表order_items作为被驱动表,导致性能极差

步骤3:第一次优化 - 为JOIN字段添加索引

这是多表联查优化的基础,所有JOIN字段必须有索引

-- 订单明细表的order_id和product_id添加索引
CREATE INDEX idx_order_items_order_id ON order_items(order_id);
CREATE INDEX idx_order_items_product_id ON order_items(product_id);

-- 商品表的主键已经是product_id,无需额外添加
-- 用户表的主键已经是user_id,无需额外添加

再次执行EXPLAIN:

id select_type table type possible_keys key key_len rows Extra
1 SIMPLE o const PRIMARY PRIMARY 8 1
1 SIMPLE oi ref idx_order_items_order_id idx_order_items_order_id 8 3
1 SIMPLE p eq_ref PRIMARY PRIMARY 8 1
1 SIMPLE u eq_ref PRIMARY PRIMARY 8 1

优化效果

  • 执行时间:0.08秒(提升93%)
  • 扫描行数:从320万行减少到6行
  • 所有表都使用了索引,没有全表扫描

剩余问题

  • 使用SELECT *查询了大量不需要的字段,增加了IO和网络传输
  • 订单明细表需要回表获取整行数据

步骤4:第二次优化 - 使用覆盖索引,避免SELECT *

只查询页面需要的字段,并为订单明细表创建覆盖索引:

-- 删除旧索引
DROP INDEX idx_order_items_order_id ON order_items;

-- 创建覆盖索引:包含JOIN字段和需要返回的字段
CREATE INDEX idx_order_items_order_id_cover ON order_items(
    order_id, product_id, quantity, unit_price, total_price
);

修改SQL,只查询需要的字段:

SELECT 
    o.order_id, o.order_status, o.total_amount, o.create_time, o.pay_time,
    oi.product_id, oi.quantity, oi.unit_price, oi.total_price,
    p.product_name, p.product_image,
    u.username, u.phone, u.address
FROM orders o
JOIN order_items oi ON o.order_id = oi.order_id
JOIN products p ON oi.product_id = p.product_id
JOIN users u ON o.user_id = u.user_id
WHERE o.order_id = 123456789;

再次执行EXPLAIN:

id select_type table type possible_keys key key_len rows Extra
1 SIMPLE o const PRIMARY PRIMARY 8 1
1 SIMPLE oi ref idx_order_items_order_id_cover idx_order_items_order_id_cover 8 3 Using index
1 SIMPLE p eq_ref PRIMARY PRIMARY 8 1
1 SIMPLE u eq_ref PRIMARY PRIMARY 8 1

优化效果

  • 执行时间:0.01秒(再提升87.5%)
  • Extra=Using index:订单明细表使用了覆盖索引,无需回表
  • 数据传输量减少了90%以上

步骤5:第三次优化 - 拆分大查询为多个小查询

虽然现在性能已经很好,但当系统并发量很高时,多表联查仍然会占用较多数据库连接资源。我们可以将一个大查询拆分为多个小查询,利用应用层缓存进一步提升性能:

-- 1. 查询订单基本信息(可缓存)
SELECT order_id, order_status, total_amount, create_time, pay_time, user_id
FROM orders WHERE order_id = 123456789;

-- 2. 查询订单明细(可缓存)
SELECT product_id, quantity, unit_price, total_price
FROM order_items WHERE order_id = 123456789;

-- 3. 查询商品信息(可缓存,缓存时间更长)
SELECT product_name, product_image
FROM products WHERE product_id IN (1001, 1002, 1003);

-- 4. 查询用户信息(可缓存)
SELECT username, phone, address
FROM users WHERE user_id = 123456;

最终效果

  • 执行时间:0.005秒(再提升50%)
  • 每个查询都非常简单,易于优化和缓存
  • 数据库连接占用时间更短,系统并发能力提升3-5倍
  • 可以针对不同数据设置不同的缓存策略,进一步减轻数据库压力

多表联查优化核心原则总结

  1. 小表驱动大表:让数据量小的表作为驱动表,减少循环次数
  2. 所有JOIN字段必须有索引:这是多表联查优化的基础
  3. 优先使用覆盖索引:避免回表操作
  4. 避免SELECT *:只查询需要的字段
  5. 尽量减少JOIN表数量:超过3张表的JOIN建议拆分
  6. 拆分大查询为小查询:利用应用层缓存提升性能

四、MySQL索引与慢SQL优化可打印速查表

📌 索引基础速查

索引类型 特点 适用场景
聚簇索引 叶子节点存整行数据,每张表只有一个 主键查询
非聚簇索引 叶子节点存主键值,需要回表 普通查询
联合索引 多个字段组合,遵循最左前缀原则 多条件查询
覆盖索引 查询字段都在索引中,无需回表 高频查询

索引设计黄金法则

  • 等值列在前,范围列在后
  • 覆盖索引优先
  • 单表索引不超过5个
  • 索引字段尽量小

📌 Explain执行计划核心字段速查

1. type字段(性能从优到劣)

system > const > eq_ref > ref > range > index > ALL

  • ✅ 理想:const、eq_ref、ref
  • ⚠️ 合格:range
  • ❌ 必须优化:index、ALL

2. 关键字段含义

字段 含义 关注重点
key 实际使用的索引 是否为NULL
key_len 索引使用长度 联合索引使用了多少列
rows 预估扫描行数 越小越好
Extra 额外信息 ✅ Using index
❌ Using filesort
❌ Using temporary

📌 索引失效常见场景速查

❌ 违反最左前缀原则
❌ 索引列上使用函数/表达式
❌ 隐式类型转换(字符串不加引号)
❌ 模糊查询以%开头
❌ 使用OR连接非索引列
❌ 负向查询(NOT IN、!=、<>)
❌ 优化器认为全表扫描更快

📌 慢SQL优化全流程速查

  1. 发现:开启慢查询日志(long_query_time=1
  2. 分析:使用EXPLAIN查看执行计划
  3. 优化
    • 优先添加合适的索引
    • 重构SQL语句(避免SELECT *、优化子查询)
    • 优化表结构(选择合适的数据类型)
    • 系统级优化(配置参数、读写分离)
  4. 验证:对比优化前后的执行计划和性能

📌 常见优化技巧速查

深分页优化

-- 游标分页(推荐)
SELECT * FROM table WHERE id > last_id LIMIT 20;

-- 覆盖索引+延迟关联
SELECT t.* FROM table t 
JOIN (SELECT id FROM table LIMIT 10000, 20) tmp 
ON t.id = tmp.id;

JOIN优化

  • 小表驱动大表
  • JOIN字段必须有索引
  • 尽量减少JOIN表数量

排序/分组优化

  • 让索引顺序与排序/分组顺序一致
  • 避免对大结果集进行排序/分组

📌 优化优先级

SQL与索引 > 表结构 > 配置参数 > 硬件与架构

相关文章
|
4月前
|
存储 关系型数据库 MySQL
【MySQL】《MySQL B+树索引面试问答清单》(附《一页纸面试速记版》)
《MySQL B+树索引面试问答清单》精讲B+树原理、聚簇/非聚簇索引、回表、最左前缀等高频考点,辅以红黑树/B树对比及量化数据(如10亿数据仅需4次IO),涵盖分裂合并示例与面试避坑指南。
|
6月前
|
缓存 Java 数据库
【Spring Boot】Spring Boot 全体系知识结构化拆解(附 Spring Boot 高频面试八股文精简版)
Spring Boot 是 Pivotal 基于 Spring 的“约定大于配置”快速开发框架,简化初始搭建与开发,无缝整合 Spring 全生态,内嵌容器、自动配置、起步依赖开箱即用,是 Java 企业级应用与微服务架构的核心基石。
1849 8
|
4月前
|
SQL 监控 关系型数据库
【MySQL】索引核心:Explain执行计划解读、慢SQL优化全流程
本文系统讲解MySQL索引与慢SQL优化全链路:从B+树原理、聚簇/联合索引设计,到EXPLAIN执行计划深度解读(重点解析type、key、rows、Extra等核心字段),再到慢查询定位、9类索引失效场景及实战优化策略,助力高效根治慢SQL。
|
4月前
|
算法 关系型数据库 MySQL
【MySQL】《MySQL海量数据处理:面试核心考点问答清单》(附:《一页纸速记版》+《20道模拟面试口述题》)
《MySQL海量数据处理:面试核心考点问答清单》聚焦单库瓶颈、读写分离、分库分表、分片策略、分布式事务等八大主题,涵盖演进路线、原理对比、避坑指南与Sharding-JDBC实战,助力候选人系统掌握高并发场景下的架构设计与面试应答要点。
|
6月前
|
Java 数据库 数据格式
【Spring注解】Spring生态常见注解——面试高频考点总结
本文系统梳理Spring生态高频面试注解考点,直击本质——注解只是表象,核心考察IOC容器、Bean生命周期、MVC流程、AOP代理、自动配置与事务机制等底层原理。涵盖@Component/@Configuration、@Autowired/@Resource、@RestController、@Transactional、@SpringBootApplication、@RequestBody等七大类注解的辨析、原理、坑点 及 TOP10 必背题。
790 12
|
6月前
|
安全 Java 数据库连接
【反射】Java反射 全方位知识体系(附 应用场景 + 《八股文常考面试题》)
Java反射是运行时动态获取类元信息(构造器、方法、字段等)并操作对象的能力,核心为 Class对象。广泛应用于Spring、MyBatis等框架的IoC、AOP、ORM映射,以及注解处理、动态代理、SPI扩展等场景,兼具灵活性与解耦优势,但存在性能开销和安全风险。
671 10
|
4月前
|
SQL JSON 关系型数据库
【MySQL】《MySQL 索引核心+8.0索引新特性 面试背诵清单》(附:EXPLAIN执行计划完整教程+《MySQL 8.0 索引新特性速查表》)
《MySQL索引核心面试背诵清单》精讲B+树原理、聚簇/二级索引、最左前缀、覆盖索引与失效场景;配套EXPLAIN深度解析(type/key_len/Extra);并系统梳理MySQL 8.0不可见索引、降序索引、函数索引、跳跃扫描等7大新特性,附实战测试模板——助你高效备战技术面试。
|
3月前
|
存储 监控 Java
【Spring全家桶】Spring Cloud 2023.0.x:链路追踪:SkyWalking、OpenTelemetry(附《思维导图》+《面试高频考点清单》)
Spring Cloud 2023.0.x(Leyton)正式弃用Sleuth,全面转向OpenTelemetry标准,构建Traces/Metrics/Logs三位一体可观测性体系;推荐OpenTelemetry采集 + SkyWalking分析的“标准+专业”协同方案。
|
3月前
|
存储 算法 Java
【JVM虚拟机】垃圾回收GC:垃圾收集器:G1:Region分区、Mixed GC、回收流程、适用场景(高频)(附《思维导图》+《面试高频考点清单》)
G1是JDK 9起默认的低延迟垃圾收集器,将堆划分为2048个可动态分配角色的Region,通过Mixed GC优先回收垃圾最多的区域,结合Remembered Set与SATB算法,在大堆(≥4GB)场景下实现可预测停顿(如≤200ms)与高吞吐平衡。
|
3月前
|
存储 算法 Java
【JVM虚拟机】JVM内存结构(JDK8+):方法区、堆、虚拟机栈、本地方法栈、程序计数器,各区域作用与OOM场景(附《思维导图》+《面试高频考点清单》)
本文是面向Java开发者与面试者的JDK8+ JVM内存结构权威指南,系统梳理程序计数器、虚拟机栈、本地方法栈、堆(含年轻代/老年代/TLAB)、元空间五大运行时数据区,涵盖线程私有/共享特性、OOM场景、核心参数及高频面试题,图文结合、深度实用。

热门文章

最新文章