第五章:性能优化与调优
5.1 查询性能优化------让数据库跑得比外卖小哥还快的秘籍
欢迎来到「数据库健身房」!今天我们将化身"SQL健身教练",用一家日均处理百万订单的虚拟快递公司崩溃案例,教你如何把龟速查询变成闪电侠。⚡📦
5.1.1 查询性能优化的基本原则------快递公司的"生存法则"
血泪案例 :
某快递公司未做查询优化导致:
- 查询某个包裹的物流信息需要8秒(足够快递小哥骑电动车绕办公楼3圈)
- 促销期间数据库CPU飙到98%(比双十一的快递仓库还热闹)
- 报表生成耗时2小时(财务小姐姐已经画完三幅简笔画)
黄金三定律:
- 少搬砖:减少数据扫描量(能用索引绝不扫全表)
- 走捷径:利用索引快速定位(像快递分拣机的条形码扫描)
- 不返工:避免重复计算(缓存重复使用的中间结果)
📌 行业黑话:执行成本就像快递运费------扫描的行数越多,"运费"越高!
5.1.2 使用 EXPLAIN 分析查询------给SQL做"X光体检"
sql
-- 查看包裹查询的执行计划
EXPLAIN
SELECT * FROM packages
WHERE sender_phone = '13812345678'
AND create_time > '2023-01-01';
体检报告解读:
+----+-------------+----------+------+---------------+---------+---------+-------+------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | rows | Extra|
+----+-------------+----------+------+---------------+---------+---------+-------+------+-------------+
| 1 | SIMPLE | packages | ref | idx_phone | idx_phone | 66 | 1 | Using where |
+----+-------------+----------+------+---------------+---------+---------+-------+------+-------------+
诊断术语表:
- type: ALL(全表扫描→快递员徒步找包裹)、index(索引扫描→骑电动车找)、range(范围扫描→用GPS导航)
- rows: 预估扫描行数(值越小越好)
- Extra: Using filesort(需要额外排序→手动整理包裹堆)
5.1.3 优化 WHERE 子句------给查询装上"GPS导航"
反面教材:
sql
SELECT * FROM packages
WHERE YEAR(create_time) = 2023 -- 索引失效!
AND LEFT(receiver_address, 3) = '北京市'; -- 索引再次阵亡!
优化方案:
sql
-- 使用索引友好写法
SELECT * FROM packages
WHERE create_time BETWEEN '2023-01-01' AND '2023-12-31'
AND receiver_address LIKE '北京市%'; -- 最左前缀匹配
-- 创建联合索引
CREATE INDEX idx_time_addr ON packages(create_time, receiver_address);
性能对比:
- 优化前:扫描120万行,耗时4.8秒
- 优化后:扫描856行,耗时0.02秒
5.1.4 优化 JOIN 操作------多表联动的"高速公路"
车祸现场:
sql
SELECT *
FROM orders
JOIN users ON orders.user_id = users.id
JOIN products ON orders.product_id = products.id
WHERE users.city = '上海';
问题分析:
- 未使用索引导致嵌套循环连接
- 大表JOIN顺序不合理
优化手段:
sql
-- 添加索引
CREATE INDEX idx_user_city ON users(city);
CREATE INDEX idx_product ON products(id);
-- 调整JOIN顺序(小表驱动大表)
SELECT /*+ STRAIGHT_JOIN */
orders.*, users.name, products.price
FROM users
JOIN orders ON users.id = orders.user_id
JOIN products ON orders.product_id = products.id
WHERE users.city = '上海';
💡 就像让快递小哥先拿小件货,再装大件货更省时间!
5.1.5 优化子查询------解开"俄罗斯套娃"的诅咒
灾难案例:
sql
SELECT *
FROM packages
WHERE id IN (
SELECT package_id
FROM logistics
WHERE status = 'delayed'
GROUP BY package_id
HAVING COUNT(*) > 3
);
执行分析:
- 子查询生成临时表
- 无法使用索引
优化方案:
sql
-- 改用JOIN+临时表
CREATE TEMPORARY TABLE tmp_delayed
SELECT package_id
FROM logistics
WHERE status = 'delayed'
GROUP BY package_id
HAVING COUNT(*) > 3;
SELECT p.*
FROM packages p
JOIN tmp_delayed t ON p.id = t.package_id;
-- 或者使用EXISTS
SELECT *
FROM packages p
WHERE EXISTS (
SELECT 1
FROM logistics l
WHERE l.package_id = p.id
AND l.status = 'delayed'
GROUP BY l.package_id
HAVING COUNT(*) > 3
);
5.1.6 优化 GROUP BY 和 ORDER BY------给数据"排队发快递"
低效写法:
sql
SELECT city, COUNT(*)
FROM users
GROUP BY city
ORDER BY COUNT(*) DESC;
问题:
- 临时表 + 文件排序
优化技巧:
sql
-- 利用覆盖索引
ALTER TABLE users ADD INDEX idx_city (city);
-- 调整SQL结构
SELECT city, user_count
FROM (
SELECT city, COUNT(*) AS user_count
FROM users
GROUP BY city
) AS tmp
ORDER BY user_count DESC;
高级技巧:
sql
-- 使用索引排序(8.0+支持)
SELECT province, city, COUNT(*)
FROM users
GROUP BY province, city
ORDER BY province, city; -- 确保有(province,city)的联合索引
5.1.7 查询缓存------数据库的"记忆面包"
配置秘籍:
sql
-- 查看缓存状态
SHOW VARIABLES LIKE 'query_cache%';
-- 临时禁用缓存测试真实性能
SELECT SQL_NO_CACHE * FROM ...
-- 最佳实践配置(my.cnf)
query_cache_type = 1
query_cache_size = 64M
query_cache_limit = 2M
适用场景:
- 读多写少的报表查询
- 重复率高的基础配置查询
失效案例 :
某论坛开启查询缓存后,每次用户发帖都导致大量缓存失效------就像每次有新快递入库,整个仓库的货物都要重新整理!
课后彩蛋:性能优化冷知识
- MySQL 8.0废弃了查询缓存功能(因为维护成本太高)
- 某银行系统曾因
ORDER BY RAND()
导致每秒20万次全表扫描 - 最早的数据库优化方式是物理排序磁带来加速查询
现在你已经成为"SQL性能优化大师"!下一章我们将进入《数据库调优------让MySQL跑得比双十一快递还快的终极秘籍》的科幻世界,记得给你的服务器准备氮气加速------查询速度即将突破次元壁! 🚀💨