一、 优化总览与诊断流程
SQL 优化的本质不是盲目加索引,而是减少不必要的数据扫描、计算、返回量和等待。建议遵循"先找慢 SQL → 看执行计划 → 按优先级修复"的流程:
-
定位慢 SQL :通过数据库的慢查询日志或
pg_stat_statements(PostgreSQL)、Query Store(SQL Server)等工具找出耗时或执行频次最高的语句。 -
分析执行计划 :使用
EXPLAIN(MySQL)或EXPLAIN ANALYZE(PostgreSQL)查看数据库打算如何执行查询。 -
识别瓶颈:重点关注全表扫描(Seq Scan / ALL)、高预估扫描行数(rows)、临时表(Using temporary)和文件排序(Using filesort)。
-
按优先级修复:优先调整索引和 SQL 写法,其次考虑业务逻辑拆分,最后才进行数据库参数调优。
二、 索引设计与防失效
索引是提升查询性能最有效的手段,但用错反而会拖慢写入。
-
建索引原则 :优先考虑在
WHERE、JOIN、ORDER BY、GROUP BY中高频出现的字段建索引。组合索引需遵循最左前缀原则,并将区分度高、等值条件的字段放在前面。 -
覆盖索引 :如果查询的字段全部包含在索引中,数据库无需回表查原数据,性能极高。例如为
SELECT id, name FROM user WHERE email = 'xx@xx.com'建立(email, id, name)索引。 -
避免索引失效 :对索引列使用函数(如
DATE(create_time) = '2024-01-01')、表达式计算(如age + 1 = 30)或前置模糊匹配(如LIKE '%abc')都会导致索引失效。应改写为范围查询或后置模糊匹配。
三、 SQL 语句改写技巧
良好的 SQL 写法能从源头减少数据库负担:
-
拒绝
SELECT *:只取需要的字段,既能减少网络传输和内存占用,也能更好地配合覆盖索引。 -
提前过滤数据 :尽早使用
WHERE缩小数据集,再进行JOIN或聚合操作。 -
优化子查询与判断 :能用
JOIN就少用子查询;在大结果集判断存在性时,用EXISTS替代IN;不需要去重时坚决不用DISTINCT。 -
分页优化 :深分页(如
LIMIT 100000, 10)性能极差。应改用游标分页,如WHERE id > 100000 LIMIT 10,利用索引直接定位。 -
慎用
OR与隐式转换 :不同列上的OR容易导致索引失效;字段类型不匹配会引发隐式转换,同样会让索引失效。
四、 复杂场景与高级策略
当单表数据量极大或查询极其复杂时,可采用以下进阶方案:
-
大表 JOIN 与聚合:确保关联字段有索引;聚合查询可借助覆盖索引或预先计算的汇总表。
-
窗口函数 :处理排名、环比等分析类需求时,使用窗口函数(如
ROW_NUMBER()、LAG())替代耗时的自连接。 -
表分区:针对亿级大表,可按时间或哈希进行分区,查询时数据库会自动裁剪无关分区,减少扫描。
-
物化视图与预计算:对高频复杂的报表查询,使用物化视图或定时任务预计算结果,用空间换时间。
-
批量操作:大批量写入或更新时,采用分批提交(Batch),避免长事务导致锁等待和资源耗尽。
五、 实战演练与避坑指南
案例 1:单表查询慢
问题 SQL :SELECT * FROM orders WHERE DATE(create_time) = '2024-01-01';
分析与优化 :DATE()函数导致 create_time索引失效,引发全表扫描。
改写 :SELECT id, amount FROM orders WHERE create_time >= '2024-01-01' AND create_time < '2024-01-02';
配套动作 :建立覆盖索引 idx_create_time_covering (create_time, id, amount)。
案例 2:深分页卡顿
问题 SQL :SELECT id, name FROM user ORDER BY id LIMIT 100000, 10;
分析与优化:数据库需先读取前 100010 行再丢弃,越往后越慢。
改写 :SELECT id, name FROM user WHERE id > 100000 ORDER BY id LIMIT 10;
案例 3:大结果集 IN 子查询
问题 SQL :SELECT * FROM customers WHERE customer_id IN (SELECT customer_id FROM orders WHERE ...);
分析与优化 :大结果集下 IN性能不佳。
改写 :使用 EXISTS或改写为 JOIN。
避坑清单
-
过度索引 :索引虽快但占空间,会降低
INSERT/UPDATE/DELETE的速度,单表索引不宜过多。 -
低区分度字段:在"性别"这类只有几个值的列上建索引毫无意义。
-
陈旧统计信息:数据库优化器依赖统计信息生成最优计划,需定期更新表统计信息。
-
锁竞争:大事务和未提交的长连接会导致严重的锁等待,应避免在事务中做无关耗时操作