本文目录导读:

优化SQL查询性能是一个系统性的工作,通常可以从索引优化、SQL语句改写、表结构设计、以及系统配置四个层次入手,以下是经过分类整理的优化策略和具体方法:
索引优化(最直接有效的手段)
-
合理使用索引类型:
- 主键索引:InnoDB表建议使用自增整型主键,避免使用UUID(无序,导致页分裂)。
- 覆盖索引:查询的列完全包含在索引中(
SELECT key_col FROM table WHERE ...),避免回表(根据二级索引查询数据)。 - 联合索引:遵循最左前缀原则,例如索引
(A, B, C),查询条件必须包含A才能生效,把区分度高的列放在前面。 - 前缀索引:针对长字符串(如URL、文本)建立索引时,只索引前N个字符,减少索引体积和IO开销(如
INDEX(column(10)))。
-
避免索引失效的常见场景:
- 不要在索引列上使用函数或计算:
WHERE DATE(create_time) = '2024-01-01'应改为WHERE create_time >= '2024-01-01 00:00:00' AND create_time < '2024-01-02 00:00:00' - 避免隐式类型转换:如果字段是字符串类型,查询条件中值必须加引号,否则索引可能失效。
- 避免使用 、
<>、NOT IN:这些操作符通常会导致索引失效(具体看数据库版本和优化器)。 - LIKE查询开头不要带通配符:
LIKE '%keyword'无法使用索引,LIKE 'keyword%'可以。 - OR条件:两边的字段都需要有索引,或改用
UNION ALL替代。
- 不要在索引列上使用函数或计算:
SQL语句改写(减少扫描数据量)
-
只查询需要的列:
- 不用 SELECT *:只查询必要的字段,减少数据传输量和IO开销。
- 避免重复数据:使用
DISTINCT或GROUP BY去重,但如果数据本来就唯一则不需要。
-
减少JOIN操作:
- 尽量小表驱动大表(特别是在JOIN时):外层循环是小表,内层是大表。
- 避免笛卡尔积(缺少JOIN条件的连接)。
- 使用
EXISTS替代IN(当子查询数据量大时,EXISTS更快,因为它一旦找到即停止)。-- 慢:子查询可能返回大量结果集 SELECT * FROM A WHERE id IN (SELECT id FROM B WHERE ...); -- 快:相关子查询 SELECT * FROM A WHERE EXISTS (SELECT 1 FROM B WHERE A.id = B.id AND...);
-
分页优化:
- 深分页问题:
LIMIT 100000, 20会扫描前100020条数据,可以用延迟关联或子查询优化。-- 慢 SELECT * FROM table ORDER BY id LIMIT 100000, 20; -- 快(延迟关联) SELECT t.* FROM table t INNER JOIN (SELECT id FROM table ORDER BY id LIMIT 100000, 20) AS tmp ON t.id = tmp.id;
- 使用唯一键排序(如主键
id)进行游标分页:WHERE id > last_id ORDER BY id LIMIT 20。
- 深分页问题:
-
合理使用聚合函数:
COUNT(*)比COUNT(1)或COUNT(column)通常更快(因为MySQL对 有特定优化,且不关心是否有NULL值)。- 大表做
GROUP BY时,确保GROUP BY的列有索引。
表结构与数据模型设计
-
选择合适的数据类型:
- 能用
TINYINT不用INT,能用VARCHAR(50)不用VARCHAR(500)。 - 使用
DATETIME替代TIMESTAMP存储时间(除非需要考虑时区转换,但DATETIME范围更大)。 - 存储IP地址用
INT UNSIGNED(INET_ATON/INET_NTOA函数转换),不要用VARCHAR(15)。
- 能用
-
反范式化设计:
- 在频繁查询但更新少的场景下,适当增加冗余字段(例如订单表中冗余存储用户姓名),避免多表JOIN。
- 使用汇总表(预计算表):如统计类数据(日活跃用户数)定期计算并存储,避免实时
COUNT大表。
-
分库分表:
- 单表数据量超过500万-1000万行时,考虑水平分表(按ID范围、Hash、时间分片)或分库。
- 冷热数据分离:将历史数据归档到单独的冷存储表或数据库中。
执行计划分析(找问题根源)
不要猜,要诊断:
-- 在查询前加上 EXPLAIN EXPLAIN SELECT * FROM users WHERE email = 'test@example.com';
- 重点看几个字段:
type:从system->const->eq_ref->ref->range->index->ALL。ALL是可怕的:全表扫描。rows:预估扫描的行数,越少越好。Extra:出现了Using filesort、Using temporary是需要优化的强信号,出现了Using index是很好的(覆盖索引)。key:实际使用的索引是什么,是否为NULL。
调整思路:如果发现 type=ALL(全表扫描)且 rows 很大,通常是因为缺乏索引或索引失效;如果发现 Extra=Using filesort,检查和排序字段相关的索引。
系统与配置层面
-
调整MySQL配置:
innodb_buffer_pool_size:设置为物理内存的70%-80%(只针对InnoDB表,非常重要)。query_cache_type:建议关闭(MySQL 8.0已移除查询缓存),因为在高并发下缓存失效和更新反而会降低性能。max_connections:根据服务器内存和线程成本合理设置,避免过多连接争抢资源。
-
硬件优化:
- SSD替代HDD:随机读写性能提升巨大。
- 增加内存:让更多数据页可以缓存在
buffer pool中。
-
读写分离:
- 主库负责写(INSERT/UPDATE/DELETE),从库负责读(SELECT)。
- 利用负载均衡分散查询压力。
通用排查与优化流程
-
监控慢查询日志:
SET GLOBAL slow_query_log = 1; SET GLOBAL long_query_time = 2; -- 记录执行超过2秒的SQL
定位需要优化的目标SQL。
-
先看EXPLAIN:确认索引使用情况和扫描行数。
-
检查索引:是否建立索引?是否失效?是否需要联合索引?
-
尝试分段:如果SQL很复杂,拆成多个简单SQL(如先查ID再关联),有时候比一个大SQL更快(因为减少锁冲突,更精确控制数据量)。
-
考虑返回结果集大小:如果必须返回几十万行数据,网络传输时间可能占大头,可以增加分页或数据压缩。
最常用的3个黄金法则
- 索引先行:90%的慢查询都可以通过合适的索引解决。
- 减少数据访问量:只取需要的行和列,利用分页和WHERE条件。
- 避免索引失效:不在索引列上使用函数、隐式转换、
LIKE '%xxx'。
如果需要针对具体的SQL语句优化,欢迎贴出该SQL和对应的 EXPLAIN 输出,可以帮你做更精确的分析。