当前位置: 首页 > news >正文

MySQL学习笔记07:MySQL SQL优化与EXPLAIN分析实战指南(上):执行计划深度解析

🚀 学习系列:MySQL数据库深度学习实战
📅 更新时间:2025年10月1日
🎯 课时内容:SQL优化与EXPLAIN分析核心技术(上篇)
💡 学习重点:执行计划分析、查询优化器原理、分页查询优化
难度等级:⭐⭐⭐⭐⭐

🎯 前言

在高并发的数据库环境中,SQL查询性能直接决定了系统的响应速度和用户体验。一条优化良好的SQL语句和一条性能糟糕的SQL语句,执行时间可能相差成千上万倍。

EXPLAIN是MySQL提供的查询分析利器,通过它我们可以洞察MySQL如何执行每一条SQL语句,找出性能瓶颈并制定优化策略。本文将深入剖析EXPLAIN的核心字段,揭示查询优化器的工作原理,并提供实战的SQL调优技巧。


📚 本文内容

  1. EXPLAIN执行计划详解
  2. 查询优化器工作原理
  3. SQL调优实战技巧
  4. 分页查询优化方案

🔍 核心知识点一:EXPLAIN执行计划详解

什么是EXPLAIN?

EXPLAIN是MySQL提供的查询分析工具,可以显示MySQL如何执行SQL语句,是SQL优化的核心工具。

基本使用方法

-- 基本语法
EXPLAIN SELECT * FROM users WHERE age > 25;-- 更详细的信息(MySQL 8.0+)
EXPLAIN FORMAT=JSON SELECT * FROM users WHERE age > 25;

EXPLAIN输出的核心字段

-- 示例输出
+----+-------------+-------+------+---------------+------+---------+------+------+-------------+
| id | select_type | table | type | possible_keys | key  | key_len | ref  | rows | Extra       |
+----+-------------+-------+------+---------------+------+---------+------+------+-------------+
|  1 | SIMPLE      | users | ALL  | NULL          | NULL | NULL    | NULL | 1000 | Using where |
+----+-------------+-------+------+---------------+------+---------+------+------+-------------+

type字段:访问类型的性能等级

性能从好到坏的排序:

system > const > eq_ref > ref > range > index > ALL
各种type的详细解析

1. system - 神级性能

-- 场景:系统表,只有一行数据
EXPLAIN SELECT * FROM mysql.proxies_priv;
-- 结果:type = system
-- 特点:表中只有0行或1行数据

2. const - 完美性能

-- 场景1:主键等值查询
EXPLAIN SELECT * FROM users WHERE id = 1;
-- 结果:type = const, rows = 1-- 场景2:唯一索引等值查询
EXPLAIN SELECT * FROM users WHERE email = 'john@example.com';
-- 结果:type = const (email有唯一索引)-- 面试重点:为什么是const?
-- 答案:因为最多只能匹配一行,MySQL在优化阶段就能确定结果

3. eq_ref - JOIN中的优秀性能

-- 场景:JOIN时,被驱动表使用主键或唯一索引
EXPLAIN SELECT u.name, o.amount 
FROM users u 
INNER JOIN orders o ON u.id = o.user_id;-- 如果user_id是orders表的唯一索引:
-- users表: type=ALL 或其他
-- orders表: type=eq_ref (每个u.id只匹配一行)

4. ref - 常见的良好性能

-- 场景:非唯一索引等值查询
CREATE INDEX idx_age ON users(age);
EXPLAIN SELECT * FROM users WHERE age = 25;
-- 结果:type = ref, rows = 可能多行-- 场景:多列索引的前缀匹配
CREATE INDEX idx_name_age ON users(name, age);
EXPLAIN SELECT * FROM users WHERE name = 'John';
-- 结果:type = ref (使用了复合索引的前缀)

5. range - 需要优化的性能

-- 场景1:BETWEEN范围查询
EXPLAIN SELECT * FROM users WHERE age BETWEEN 20 AND 30;
-- 结果:type = range-- 场景2:比较操作符
EXPLAIN SELECT * FROM users WHERE id > 100;
-- 结果:type = range-- 场景3:IN查询
EXPLAIN SELECT * FROM users WHERE id IN (1,2,3,4,5);
-- 结果:type = range

6. index - 较差性能,需要优化

-- 场景:全索引扫描
EXPLAIN SELECT id FROM users ORDER BY id;
-- 结果:type = index
-- Extra: Using index (覆盖索引)-- 为什么是index而不是ALL?
-- 因为只需要扫描索引,不需要回表

7. ALL - 最差性能,大厂禁止

-- 场景:全表扫描
EXPLAIN SELECT * FROM users WHERE name = 'John';
-- 结果:type = ALL, rows = 100000 (没有索引)-- 大厂要求:生产环境绝对不允许ALL类型的查询
-- 解决方案:添加索引
CREATE INDEX idx_name ON users(name);

其他重要字段解析

key字段 - 实际使用的索引
EXPLAIN SELECT * FROM users WHERE age = 25 AND name = 'John';-- 可能结果:
-- possible_keys: idx_age, idx_name, idx_age_name
-- key: idx_age_name (优化器选择的最优索引)-- 面试重点:为什么选择这个索引?
-- 答案:基于成本估算,选择扫描行数最少的索引
rows字段 - 预估扫描行数
-- 这是优化的关键指标
EXPLAIN SELECT * FROM users WHERE age > 20;
-- rows: 50000 (预估需要扫描5万行)-- 优化目标:通过索引将rows降到最小
Extra字段 - 额外执行信息

重要的Extra值详解:

1. Using index - 覆盖索引(性能很好)

-- 示例:查询字段完全被索引覆盖
CREATE INDEX idx_name_age ON users(name, age);
EXPLAIN SELECT name, age FROM users WHERE name = 'John';
-- Extra: Using index-- 解释:不需要回表,索引中已包含所需的所有字段
-- 性能:⭐⭐⭐⭐⭐ 非常好

2. Using where - 存储引擎层过滤(一般)

-- 示例:需要在存储引擎层进行条件过滤
EXPLAIN SELECT * FROM users WHERE age > 25 AND name LIKE '%John%';
-- Extra: Using where-- 解释:存储引擎返回行后,MySQL服务层再进行条件过滤
-- 性能:⭐⭐⭐☆☆ 一般

3. Using filesort - 需要额外排序(性能差)

-- 示例:ORDER BY字段没有索引
EXPLAIN SELECT * FROM users ORDER BY created_at;
-- Extra: Using filesort-- 解释:MySQL需要将查询结果在内存或临时文件中排序
-- 性能:⭐⭐☆☆☆ 较差,需要优化-- 优化方案:
CREATE INDEX idx_created_at ON users(created_at);

4. Using temporary - 需要临时表(性能很差)

-- 示例:GROUP BY和ORDER BY使用不同字段
EXPLAIN SELECT name, COUNT(*) FROM users GROUP BY name ORDER BY age;
-- Extra: Using temporary; Using filesort-- 解释:MySQL创建临时表来处理复杂查询
-- 性能:⭐☆☆☆☆ 很差,急需优化-- 优化方案:
CREATE INDEX idx_name_age ON users(name, age);

⚙️ 核心知识点二:查询优化器工作原理

什么是查询优化器?

查询优化器是MySQL的"大脑",负责为每个SQL语句选择最优的执行方案。

优化器的工作流程

第一步:SQL解析
-- 原始SQL
SELECT u.name, o.amount FROM users u JOIN orders o ON u.id = o.user_id WHERE u.age > 25;-- 解析后的逻辑结构:
-- 表:users(u), orders(o)  
-- 连接条件:u.id = o.user_id
-- 过滤条件:u.age > 25
-- 输出字段:u.name, o.amount
第二步:生成多个执行计划
-- 计划A:先过滤users,再JOIN
1. SELECT * FROM users WHERE age > 25;  -- 假设得到1000行
2. JOIN orders ON user_id;              -- 1000次索引查找-- 计划B:先JOIN,再过滤
1. SELECT * FROM users u JOIN orders o ON u.id = o.user_id;  -- 全量JOIN
2. WHERE u.age > 25;                    -- 过滤结果-- 计划C:使用不同的索引组合
-- ... 可能有10多种不同方案
第三步:成本估算
-- 优化器计算每个计划的成本
-- 成本 = I/O成本 + CPU成本-- 计划A成本估算:
-- I/O成本:扫描age索引 + 1000次orders索引查找
-- CPU成本:1000次比较 + 1000次JOIN计算
-- 总成本:大约 150 cost units-- 计划B成本估算:  
-- I/O成本:全表扫描users + 全表扫描orders
-- CPU成本:100万次JOIN计算 + 过滤计算
-- 总成本:大约 50000 cost units-- 优化器选择:计划A (成本最低)

影响优化器选择的因素

1. 表统计信息
-- 查看表统计信息
SHOW TABLE STATUS LIKE 'users';
-- Rows: 估算的行数
-- Avg_row_length: 平均行长度
-- Data_length: 数据文件大小-- 更新统计信息
ANALYZE TABLE users;
2. 索引统计信息
-- 查看索引基数统计
SHOW INDEX FROM users;
-- Cardinality: 索引的唯一值数量
-- 基数越高,选择性越好,优化器越倾向使用-- 示例:
-- idx_age: Cardinality = 50 (年龄只有50种不同值)
-- idx_email: Cardinality = 100000 (邮箱几乎都不重复)
-- 优化器更倾向选择idx_email

手动干预优化器

使用HINT强制索引
-- 强制使用特定索引
SELECT * FROM users USE INDEX(idx_age) WHERE age > 25;-- 忽略特定索引
SELECT * FROM users IGNORE INDEX(idx_name) WHERE name = 'John';-- 强制使用特定索引
SELECT * FROM users FORCE INDEX(idx_age) WHERE age > 25;
查看优化器成本
-- 查看最后一条查询的成本
SHOW STATUS LIKE 'Last_query_cost';
-- 结果:150.5 (成本单位)

🛠️ 核心知识点三:SQL调优实战技巧

SQL优化的核心原则

原则1:避免索引失效

问题1:函数导致索引失效

-- ❌ 错误写法:函数导致索引失效
SELECT * FROM users WHERE YEAR(created_at) = 2024;-- ✅ 正确写法:保持索引有效
SELECT * FROM users WHERE created_at >= '2024-01-01' AND created_at < '2025-01-01';

问题2:LIKE前缀通配符

-- ❌ 前缀通配符导致索引失效
SELECT * FROM users WHERE name LIKE '%john%';
-- type: ALL, rows: 1000000-- ✅ 后缀通配符可以使用索引
SELECT * FROM users WHERE name LIKE 'john%';
-- type: range, rows: 50-- 🔧 如果必须前缀搜索,考虑全文索引
ALTER TABLE users ADD FULLTEXT(name);
SELECT * FROM users WHERE MATCH(name) AGAINST('john');

问题3:隐式类型转换

-- ❌ 字符串字段用数字比较
SELECT * FROM users WHERE phone = 13800138000;  -- phone是VARCHAR
-- 导致全表扫描-- ✅ 使用正确的数据类型
SELECT * FROM users WHERE phone = '13800138000';
-- 可以使用索引
原则2:OR条件优化
-- ❌ OR导致索引失效
SELECT * FROM users WHERE name = 'John' OR age = 25;
-- type: ALL (两个条件都要检查)-- ✅ 改写为UNION
SELECT * FROM users WHERE name = 'John'
UNION
SELECT * FROM users WHERE age = 25;
-- 两个查询都能使用索引

📄 核心知识点四:分页查询优化方案

传统分页的性能问题

深度分页的执行过程
-- 问题SQL:深度分页性能差
SELECT * FROM users ORDER BY id LIMIT 1000000, 10;
-- 需要排序前1000010条记录,然后跳过前1000000条-- 执行时间随着偏移量增加而线性增长:
-- LIMIT 10, 10      → 0.01秒
-- LIMIT 10000, 10   → 0.05秒  
-- LIMIT 1000000, 10 → 2.5秒
性能问题的根本原因
  • MySQL必须处理所有前面的记录,即使我们不需要它们
  • 偏移量越大,需要"跳过"的记录越多,性能越差
  • 这就是为什么大型网站很少提供"跳转到第1000页"功能

分页优化方案

方案1:覆盖索引 + 子查询优化

优化思路
传统分页的问题是需要排序和跳过大量记录。我们可以先通过索引快速定位到起始位置,再查询具体数据。

具体实现

-- ❌ 原始慢查询
SELECT * FROM users ORDER BY id LIMIT 100000, 10;
-- 执行时间:2.5秒
-- 问题:需要排序前100010条记录-- ✅ 优化方案:覆盖索引定位
SELECT * FROM users 
WHERE id >= (SELECT id FROM users ORDER BY id LIMIT 100000, 1
) 
ORDER BY id LIMIT 10;
-- 执行时间:0.05秒
-- 提升:50倍性能提升!

优化原理分析

步骤1:内层查询使用覆盖索引

-- 子查询:SELECT id FROM users ORDER BY id LIMIT 100000, 1
-- 这个查询只需要扫描主键索引,不需要回表
-- 因为只查询id字段,主键索引已经包含了所有需要的数据
-- 执行时间:0.02秒

步骤2:外层查询范围扫描

-- 假设内层查询返回 id = 150000
-- 外层查询变为:SELECT * FROM users WHERE id >= 150000 ORDER BY id LIMIT 10;
-- 这是一个简单的范围查询,直接从id=150000开始取10条
-- 执行时间:0.03秒
方案2:游标分页(基于上次位置)

基本思路
不使用OFFSET跳过记录,而是记住上次查询的最后位置,从该位置继续查询。

实现方式

-- 第一页:从头开始
SELECT * FROM users WHERE id > 0 ORDER BY id LIMIT 10;
-- 返回:id为 1,2,3,4,5,6,7,8,9,10 的记录
-- 记住最后一个id: last_id = 10-- 第二页:从上次位置继续
SELECT * FROM users WHERE id > 10 ORDER BY id LIMIT 10;  
-- 返回:id为 11,12,13,14,15,16,17,18,19,20 的记录
-- 记住最后一个id: last_id = 20-- 第三页:继续下去
SELECT * FROM users WHERE id > 20 ORDER BY id LIMIT 10;
-- 返回:id为 21,22,23,24,25,26,27,28,29,30 的记录

性能优势

-- 每次查询的EXPLAIN结果都是:
-- type: range (范围查询)
-- rows: 10 (只扫描需要的行数)
-- 执行时间:始终保持在0.001秒左右,不受页数影响!

优缺点对比

方案能否跳页性能复杂度适用场景
传统分页✅ 可以❌ 深度分页很慢简单数据量小
覆盖索引优化✅ 可以跳页✅ 快速复杂需要跳页+高性能
游标分页❌ 不能跳页✅ 始终很快中等流式浏览

🎯 重要问题解答

学习过程中的关键问题

Q1: type=ALL说明什么问题?应该如何优化?

A: type=ALL表示全表扫描,是性能最差的访问方式。优化方法:

  1. 分析WHERE条件中的字段,为这些字段添加合适的索引
  2. 检查是否有函数或表达式导致索引失效
  3. 考虑改写SQL语句,避免复杂的条件组合
Q2: 为什么全索引扫描比全表扫描快,但仍然需要优化?

A:

  • 全索引扫描(index)只读取索引文件,文件较小,I/O较少
  • 全表扫描(ALL)需要读取完整的数据文件,包含所有字段,I/O很多
  • 但index仍然需要扫描整个索引,当数据量大时性能仍然不好
  • 优化目标是达到range、ref或const级别的访问
Q3: 分页优化的核心原理是什么?

A:

  • 传统分页:需要排序大量数据,然后跳过不需要的记录
  • 覆盖索引优化:利用索引的有序性,先定位起始位置,再范围查询
  • 游标分页:记住上次位置,避免重复计算和跳跃
  • 核心思想:避免处理不需要的数据,利用索引的有序性
Q4: 什么情况下MySQL优化器会选择错误的执行计划?

A:

  1. 统计信息过时或不准确
  2. 复杂的多表JOIN,估算偏差较大
  3. 新插入大量数据后,统计信息未及时更新
  4. 查询条件的选择性估算错误
  5. 解决方法:定期ANALYZE TABLE,必要时使用HINT

💡 实战优化建议

SQL优化的一般步骤

  1. 使用EXPLAIN分析:找出性能瓶颈点
  2. 检查type字段:目标是避免ALL和index
  3. 观察rows数值:优化目标是减少扫描行数
  4. 关注Extra信息:解决Using filesort和Using temporary
  5. 验证优化效果:对比优化前后的执行时间

生产环境最佳实践

  1. 建立监控体系:开启慢查询日志,监控执行时间超过阈值的SQL
  2. 定期统计信息更新:每周执行ANALYZE TABLE,保持统计信息准确
  3. 索引设计原则:为WHERE、ORDER BY、GROUP BY、JOIN字段建立合适索引
  4. 分页策略选择:根据业务场景选择合适的分页方案
  5. SQL审查制度:所有SQL上线前必须通过EXPLAIN分析

🚀 下篇预告

**《MySQL SQL优化与EXPLAIN分析实战指南(下)》**将涵盖:

  1. JOIN查询优化策略:小表驱动大表、索引优化、多表JOIN替代方案
  2. 子查询优化技巧:EXISTS vs IN、子查询改写、性能对比分析
  3. 实战优化案例:电商订单查询、用户行为分析、复杂报表查询优化
  4. 高级优化技巧:慢查询监控、索引设计原则、生产环境调优实战

💡 学习建议:EXPLAIN是SQL优化的基础工具,建议在实际项目中多加练习。可以搭建测试环境,模拟大数据量场景,观察不同优化方案的效果差异。

🎯 下一步:建议继续学习JOIN查询优化,了解如何在多表关联查询中实现最佳性能。


📝 本文深入解析了MySQL SQL优化的核心技术,掌握EXPLAIN分析方法和分页优化技巧,为高性能数据库应用奠定坚实基础。记住:理论结合实践,才能真正掌握SQL优化的精髓!

http://www.dtcms.com/a/432037.html

相关文章:

  • 产品经理指南:Vibes与AI提示词驱动短视频创新与Instagram优化
  • 手机上怎样制作网站广州市做网站公司
  • 数据要素X_第三批“数据要素×”典型案例——现代农业领域【附全文阅读】
  • 华容网站企业软件管家
  • 汽车可以做哪些广告视频网站南宁建站服务公司
  • 【代码随想录day 31】 力扣 56. 合并区间
  • 成都网站快速优化排名做app需要什么条件
  • 网站怎样做全国地区推广网站seo方案
  • 建站用wordpress 起飞了如何创建网站的步骤
  • 网站建设标新立异类似织梦的建站cms
  • 企业建立网站步骤深圳市9号文
  • 建站推广免费公司个人博客免费模板
  • 什么是营销型网站?哪个wordpress编辑器
  • 域名购买后如何建设网站免费制作网站平台
  • 安全电子商务网站设计所见即所得的网页设计软件
  • 项目实战5:聊天室
  • 网站建设图片流程图我的家乡网页制作素材
  • **全息显示技术的发散创新与深度探索**一、引言随着科技的飞速发展,全息显示技术已成为显示领域的一大研究热点。本文将带你
  • 旅游网站推广方案植物染企业解决方案
  • 深度学习基础知识-Transformer基础
  • 网站建设关键词排名网站中所有标签
  • JVM虚拟机栈溢出与堆溢出有什么区别?
  • 新奇特:负权重橡皮擦,让神经网络学会主动遗忘
  • 成都公园城市建设局网站中山大良网站建设
  • 04、Python从入门到癫狂:对象
  • 云南昆明网站建设公司网站开发环境搭建
  • 营销网站策划方案爱南宁下载安装
  • 钦州网站推广郑州网课老师
  • h5网站建设h精准营销的好处
  • AI从技术到生产力的跨越