1. 为什么需要关注MySQL查询优化
记得刚入行那会儿,我接手过一个电商后台系统,某个商品列表页的加载时间竟然要8秒多。通过EXPLAIN分析发现,一条看似简单的SELECT语句全表扫描了200万条记录。那次经历让我深刻认识到:数据库查询优化不是"高级技巧",而是每个开发者必须掌握的基本功。
查询优化的本质是在有限的硬件资源下,用更少的IO和CPU时间获取相同的数据。好的优化能让吞吐量提升数倍,差的查询则可能拖垮整个系统。特别是在用户量增长、数据量膨胀时,那些在测试环境跑得飞快的SQL,在生产环境可能瞬间成为性能瓶颈。
2. 基础优化策略与原理剖析
2.1 索引的正确打开方式
上周帮同事排查一个"订单查询超时"问题,发现他们在user_id和create_time字段上分别建了单列索引,但查询条件是WHERE user_id=? AND create_time>?。这种场景下,MySQL只能选择一个索引(最终选了user_id),导致仍然扫描了大量记录。解决方案很简单——建立联合索引(user_id,create_time),查询速度立即从2.3秒降到23毫秒。
联合索引的字段顺序很重要,要遵循"最左前缀原则"。比如索引(a,b,c)可以用于:
- WHERE a=?
- WHERE a=? AND b=?
- WHERE a=? AND b=? AND c=?
但不能用于: - WHERE b=?
- WHERE c=?
- WHERE b=? AND c=?
2.2 EXPLAIN执行计划详解
执行计划是优化SQL的"X光片"。重点看这几个字段:
- type:从最优到最差依次是 system > const > eq_ref > ref > range > index > ALL
- key:实际使用的索引
- rows:预估扫描行数
- Extra:常见重要值:
Using filesort:需要额外排序Using temporary:使用了临时表Using index:覆盖索引扫描
经验:如果
type是ALL或rows超过1000,就需要重点优化了
3. 高级优化技巧实战
3.1 分页查询的优化陷阱
典型的"深分页"问题:
sql复制SELECT * FROM orders ORDER BY id LIMIT 1000000, 10
这种写法会先读取1000010条记录,然后丢弃前100万条。优化方案:
sql复制SELECT * FROM orders WHERE id > 上次最大ID ORDER BY id LIMIT 10
或者使用延迟关联:
sql复制SELECT t.* FROM orders t
JOIN (SELECT id FROM orders ORDER BY id LIMIT 1000000,10) tmp ON t.id=tmp.id
3.2 避免隐式类型转换
发现过一个慢查询,字段code是varchar类型,但查询用了:
sql复制SELECT * FROM products WHERE code=12345
这会导致全表扫描,因为MySQL需要把每行的code转换为数字再比较。正确的写法:
sql复制SELECT * FROM products WHERE code='12345'
4. 实战中的复杂场景处理
4.1 大数据量下的JOIN优化
当多表关联查询变慢时,可以尝试:
- 确保关联字段有索引且类型一致
- 用小表驱动大表(MySQL会自动优化)
- 考虑拆分成多个查询,在应用层处理
- 使用临时表预处理数据
曾经优化过一个报表查询,将5表JOIN改为先汇总数据到临时表,执行时间从47秒降到1.8秒。
4.2 避免SELECT * 的隐藏成本
某次优化发现,一个SELECT *查询返回了20个字段,但前端只用其中3个。改为只查询必要字段后:
- 网络传输量减少85%
- 覆盖索引生效,避免了回表
- 内存占用降低
5. 性能监控与持续优化
5.1 慢查询日志配置建议
在my.cnf中配置:
code复制slow_query_log=1
slow_query_log_file=/var/log/mysql/mysql-slow.log
long_query_time=1 # 超过1秒的记录
log_queries_not_using_indexes=1
定期分析慢日志工具:
bash复制mysqldumpslow -s t -t 10 /var/log/mysql/mysql-slow.log
5.2 常用性能分析语句
sql复制-- 查看当前运行线程
SHOW PROCESSLIST;
-- 查看索引统计信息
SHOW INDEX FROM table_name;
-- 查看表状态
SHOW TABLE STATUS LIKE 'table_name';
-- 查看变量设置
SHOW VARIABLES LIKE '%buffer%';
6. 真实案例:电商系统优化实录
去年优化过一个日均订单10万+的电商系统,几个典型问题:
-
商品搜索慢:原查询使用
LIKE '%关键词%',改为全文索引后,响应时间从1200ms降到80ms -
库存扣减卡顿:将
UPDATE inventory SET stock=stock-1 WHERE product_id=?改为:sql复制UPDATE inventory SET stock=stock-1 WHERE product_id=? AND stock>=1配合应用层重试机制,解决了超卖问题
-
统计报表超时:将实时计算改为定时任务预生成,并用物化视图存储中间结果
7. 必须知道的MySQL特性与陷阱
7.1 ICP索引条件下推
MySQL5.6引入的特性,能在存储引擎层就过滤数据。要充分利用需要:
- 使用二级索引
- WHERE条件能被索引覆盖
- 不是所有存储引擎都支持(InnoDB支持)
7.2 MRR多范围读优化
对于范围查询,MySQL会先收集索引键值,然后排序后再读取数据页。启用方式:
sql复制SET optimizer_switch='mrr=on,mrr_cost_based=off';
7.3 小心NULL值陷阱
某次排查发现WHERE status!=1没有使用索引,因为表中存在NULL值。解决方案:
sql复制WHERE status!=1 AND status IS NOT NULL
8. 工具链推荐
- Percona Toolkit:包含pt-query-digest等强大工具
- sys schema:MySQL5.7+内置的性能视图
- Prometheus+Granafa:监控数据库指标
- gh-ost:在线DDL变更工具
9. 参数调优黄金法则
不要盲目复制网上的参数配置,应该:
- 先了解默认值
- 监控当前使用情况
- 小幅度调整
- 观察效果
几个关键参数:
innodb_buffer_pool_size:通常设为物理内存的70-80%innodb_io_capacity:SSD建议设2000+table_open_cache:根据实际打开表数量调整
10. 架构层面的优化思考
当单机优化到达瓶颈时,需要考虑:
- 读写分离
- 分库分表
- 引入缓存层
- 使用列式存储分析数据
曾将一个大表的访问模式改为:
- 近期数据:MySQL主库
- 历史数据:按月分表
- 统计查询:ClickHouse
整体吞吐量提升了15倍