ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

MySQL查询优化实战:从基础到高级技巧

MySQL查询优化实战:从基础到高级技巧

1. MySQL查询优化的重要性与核心思路

作为一名常年与MySQL打交道的数据库工程师,我见过太多因为SQL语句写得烂而导致系统崩溃的案例。上周刚处理过一个电商平台的性能问题:原本每秒能处理2000订单的系统,在促销活动时突然降到每秒不到100单。排查后发现是几个核心查询没有走索引,全表扫描拖垮了整个数据库。

SQL优化不是"高级技巧",而是每个使用MySQL的人都必须掌握的基本功。好的优化能让查询速度提升几十倍甚至上百倍,差的写法则可能让整个系统瘫痪。根据我的经验,80%的数据库性能问题都出在SQL语句上。

优化核心思路很简单:让MySQL用最少的资源(CPU、内存、I/O)获取需要的数据。具体来说就是:

  • 减少数据扫描量(避免全表扫描)
  • 减少临时表使用
  • 减少排序操作
  • 合理利用索引
  • 避免不必要的数据传输

2. 基础优化技巧:从写对SQL开始

2.1 只查询需要的列

新手常犯的错误就是SELECT *。我见过一个查询返回了50个字段,但前端实际只用了3个。这不仅浪费网络带宽,还可能导致无法使用覆盖索引。

-- 错误写法 SELECT * FROM orders WHERE user_id = 100; -- 正确写法 SELECT order_id, total_amount, status FROM orders WHERE user_id = 100;

提示:即使需要多个字段,也尽量控制在5-8个以内。超过这个数量要考虑是否真的需要一次性获取。

2.2 WHERE条件的正确写法

WHERE条件的顺序和写法直接影响索引使用。基本原则:

  1. 等值条件(=)放前面
  2. 范围条件(>,<,BETWEEN)放后面
  3. 避免在索引列上使用函数
-- 错误写法(无法使用create_time索引) SELECT * FROM logs WHERE DATE(create_time) = '2023-01-01'; -- 正确写法 SELECT * FROM logs WHERE create_time >= '2023-01-01' AND create_time < '2023-01-02';

2.3 LIMIT的高效用法

分页查询是性能杀手,特别是大偏移量时。优化方案:

-- 低效写法(偏移量10万时需要扫描10万行) SELECT * FROM products ORDER BY id LIMIT 100000, 20; -- 高效写法(记住上一页最后一条记录的ID) SELECT * FROM products WHERE id > 100000 ORDER BY id LIMIT 20;

对于必须使用大偏移量的场景,可以改用JOIN:

SELECT p.* FROM products p JOIN (SELECT id FROM products ORDER BY id LIMIT 100000, 20) tmp ON p.id = tmp.id;

3. 高级优化技巧:索引与执行计划

3.1 看懂EXPLAIN输出

EXPLAIN是优化SQL的必备工具。关键字段解读:

字段说明理想值
type访问类型const, eq_ref, ref, range
key实际使用的索引显示索引名
rows预估扫描行数越小越好
Extra额外信息避免Using filesort, Using temporary

3.2 创建高效索引

组合索引的黄金法则:将选择性高的列放前面,等值查询列放前面。

-- 用户表常用查询 SELECT * FROM users WHERE country = 'CN' AND age > 18 ORDER BY register_time DESC; -- 最优索引 ALTER TABLE users ADD INDEX idx_country_age_register(country, age, register_time);

3.3 索引失效的常见陷阱

  1. 使用!=或<>操作符
  2. 对索引列使用函数
  3. 隐式类型转换(如字符串列用数字查询)
  4. OR条件未全部使用索引
  5. LIKE以通配符开头
-- 这些写法都会导致索引失效 SELECT * FROM users WHERE age != 20; SELECT * FROM users WHERE YEAR(register_time) = 2023; SELECT * FROM users WHERE mobile = 13800138000; -- mobile是varchar类型

4. 复杂查询优化实战

4.1 JOIN优化三原则

  1. 小表驱动大表(小表放在JOIN前面)
  2. 确保关联字段有索引
  3. 合理使用STRAIGHT_JOIN强制连接顺序
-- 优化前(大表驱动小表) SELECT * FROM large_table l JOIN small_table s ON l.id = s.large_id; -- 优化后 SELECT * FROM small_table s JOIN large_table l ON s.large_id = l.id;

4.2 子查询优化

MySQL对子查询处理较弱,多数情况可以改写为JOIN:

-- 低效子查询 SELECT * FROM products WHERE category_id IN (SELECT id FROM categories WHERE type = '电子'); -- 高效JOIN写法 SELECT p.* FROM products p JOIN categories c ON p.category_id = c.id WHERE c.type = '电子';

4.3 分组统计优化

GROUP BY容易产生临时表,优化方案:

  1. 使用索引列分组
  2. 先缩小数据范围再分组
  3. 考虑使用汇总表
-- 低效写法 SELECT user_id, COUNT(*) FROM orders GROUP BY user_id; -- 高效写法(假设有索引(user_id, create_time)) SELECT user_id, COUNT(*) FROM orders WHERE create_time > '2023-01-01' GROUP BY user_id;

5. 特定场景优化技巧

5.1 大批量导入优化

导入10万条数据时,单个INSERT比多值INSERT慢50倍以上:

-- 慢速写法(约需要50秒) INSERT INTO users(name) VALUES('a'); INSERT INTO users(name) VALUES('b'); ... -- 快速写法(约1秒) INSERT INTO users(name) VALUES('a'),('b'),...; -- 500-1000条一批

5.2 随机抽样优化

ORDER BY RAND()性能极差,替代方案:

-- 低效写法 SELECT * FROM products ORDER BY RAND() LIMIT 10; -- 高效写法(假设id连续) SELECT * FROM products WHERE id >= (SELECT FLOOR(RAND() * MAX(id)) FROM products) LIMIT 10;

5.3 全文搜索优化

LIKE '%关键词%'无法使用索引,解决方案:

  1. 使用FULLTEXT索引
  2. 使用专业搜索引擎(Elasticsearch)
  3. 使用ngram分词
-- 创建全文索引 ALTER TABLE articles ADD FULLTEXT INDEX idx_content(content); -- 使用全文搜索 SELECT * FROM articles WHERE MATCH(content) AGAINST('数据库优化');

6. 性能监控与持续优化

6.1 慢查询日志分析

配置my.cnf开启慢查询日志:

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

使用mysqldumpslow工具分析:

mysqldumpslow -t 10 /var/log/mysql/mysql-slow.log

6.2 性能监控指标

关键指标监控阈值:

指标警告阈值危险阈值
QPS500010000
连接数max_connections的80%90%
慢查询率1%5%
缓存命中率95%90%

6.3 优化案例:电商订单查询

原始查询(执行时间2.8秒):

SELECT * FROM orders WHERE status = 'paid' AND create_time BETWEEN '2023-01-01' AND '2023-01-31' ORDER BY total_amount DESC LIMIT 100;

优化步骤:

  1. 创建索引(status, create_time, total_amount)
  2. 只查询必要字段
  3. 使用覆盖索引技巧

优化后查询(执行时间0.02秒):

SELECT o.order_id, o.user_id, o.total_amount FROM orders o JOIN ( SELECT order_id FROM orders WHERE status = 'paid' AND create_time BETWEEN '2023-01-01' AND '2023-01-31' ORDER BY total_amount DESC LIMIT 100 ) tmp ON o.order_id = tmp.order_id;

7. 常见误区与避坑指南

  1. 不是所有字段都需要索引 - 索引过多会影响写入性能
  2. 不要过早优化 - 先确保功能正确再优化
  3. 避免过度依赖ORM生成的SQL - 复杂查询建议手写
  4. 分区表不是万能药 - 只有特定场景有效
  5. 内存表(MEMORY)不适合生产环境 - 数据易丢失

我在实际工作中总结的优化检查清单:

  1. [ ] 是否使用了SELECT *
  2. [ ] WHERE条件是否能使用索引
  3. [ ] JOIN字段是否有索引
  4. [ ] GROUP BY是否使用了索引列
  5. [ ] ORDER BY是否会导致文件排序
  6. [ ] LIMIT偏移量是否过大
  7. [ ] 是否有不必要的子查询
  8. [ ] 是否可以考虑使用缓存

记住,SQL优化是一个持续的过程。随着数据量增长和业务变化,今天高效的SQL明天可能就变慢了。建议每隔3-6个月复查一次核心查询的性能。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进