SQL性能优化:让查询快10倍的8个实战技巧(2026版)

0 阅读5分钟

学习是为了不落后,整理则是为了不忘记。

这是我在数据分析这行干了近10年的体会。SQL写得烂,不只是慢,是浪费公司的钱——你多跑1小时的查询,集群就多烧1小时的钱。

2026年了,AI能帮你写SQL,但AI写出来的SQL不一定快。今天船长把压箱底的8个优化技巧全拆给你看,每个都有真实案例,直接复制就能用。

一、EXPLAIN先看执行计划,别猜

90%的SQL性能问题,EXPLAIN一眼就能看出来。但很多人习惯"改了再跑",跑完再说。

正确姿势:

EXPLAIN ANALYZE

SELECT u.name, COUNT(o.id) AS order_count

FROM users u

LEFT JOIN orders o ON u.id = o.user_id

WHERE u.created_at > '2026-01-01'

GROUP BY u.id, u.name;

看三个指标:Seq Scan vs Index Scan(有没有走索引)、rows estimated vs rows actual(预估行数准不准)、execution time(总耗时)。

**实战案例:**一个用户画像查询从12秒优化到0.3秒,就是因为EXPLAIN发现走了全表扫描,加了个组合索引就解决了。

二、索引不是越多越好,组合索引有讲究

见过有人给表加了20个索引,结果INSERT比SELECT还慢。索引是双刃剑,写快了读就慢,读快了写就慢。

组合索引的黄金法则:最左前缀匹配

-- 建索引

CREATE INDEX idx_user_status_date ON orders(user_id, status, created_at);

-- ✅ 能命中索引

WHERE user_id = 123 AND status = 'paid'

WHERE user_id = 123

-- ❌ 不能命中索引(跳过了最左列)

WHERE status = 'paid' AND created_at > '2026-01-01'

**船长经验:**单表索引不超过5个,组合索引列数不超过3列。超过这个数,说明你的表设计有问题。

三、子查询改成JOIN,性能差10倍

这是新手最常犯的错。子查询在MySQL 5.6之前每执行一次就是一次全表扫描,改成JOIN可以直接走索引。

慢写法(子查询):

SELECT * FROM orders

WHERE user_id IN (

  SELECT id FROM users WHERE city = '北京'

);

快写法(JOIN):

SELECT o.* FROM orders o

INNER JOIN users u ON o.user_id = u.id

WHERE u.city = '北京';

**实测数据:**一个100万行的订单表,子查询耗时3.2秒,JOIN只用了0.15秒,差了21倍。

四、避免SELECT *,只查需要的列

SELECT * 看着方便,但后果很严重:①网络传输量暴增 ②无法使用覆盖索引 ③回表次数增加。

**覆盖索引的威力:**如果你的查询只涉及索引列,数据库直接从索引返回数据,不需要回表查数据行。这个优化在某些场景下可以快100倍。

-- 假设有索引 idx_user_name_age(user_id, name, age)

-- ✅ 覆盖索引,不需要回表

SELECT user_id, name, age FROM users WHERE user_id = 123;

-- ❌ 多查了一列,必须回表

SELECT user_id, name, age, email FROM users WHERE user_id = 123;

五、LIKE模糊查询的坑

LIKE '%关键词'LIKE '%关键词%'都会导致索引失效,触发全表扫描。

解决方案:

① 前缀匹配可以用索引:LIKE '关键词%'

② 全文检索用全文索引:MySQL的FULLTEXT或Elasticsearch

③ 精确匹配用等号:= '关键词'

**实战案例:**一个商品搜索接口,原来用LIKE '%手机%',500万商品表查询耗时8秒。改用MySQL FULLTEXT索引后,查询降到0.05秒。

六、分页查询的深分页问题

LIMIT 100000, 20这种写法,数据库要先查出100020行,再丢弃前100000行。越往后翻越慢。

优化方案:游标分页

-- ❌ 深分页:查100万行,只要最后20行

SELECT * FROM orders ORDER BY id LIMIT 1000000, 20;

-- ✅ 游标分页:只查大于上一页最后一条ID的记录

SELECT * FROM orders WHERE id > 1000000 ORDER BY id LIMIT 20;

**实测数据:**1000万行订单表,OFFSET分页第50页耗时2.3秒,游标分页始终在0.01秒。

七、批量INSERT代替逐条INSERT

inserting 1万条数据,逐条INSERT要30秒,批量INSERT只要0.5秒。

-- ❌ 逐条插入(循环1万次)

INSERT INTO logs (user_id, action) VALUES (1, 'click');

-- ✅ 批量插入(一次1千条)

INSERT INTO logs (user_id, action) VALUES

(1, 'click'), (2, 'view'), (3, 'click'), ...;

**船长经验:**批量大小控制在500-1000条/次,太大可能触发MySQL的max_allowed_packet限制。

八、用窗口函数替代自连接

"查每个部门的薪资最高的人",老写法是自连接,新写法用窗口函数ROW_NUMBER()。

-- ❌ 自连接:两次扫描同一张表

SELECT e.* FROM employees e

INNER JOIN (

  SELECT dept, MAX(salary) AS max_sal

  FROM employees GROUP BY dept

) m ON e.dept = m.dept AND e.salary = m.max_sal;

-- ✅ 窗口函数:一次扫描搞定

WITH ranked AS (

  SELECT *, ROW_NUMBER() OVER (

    PARTITION BY dept ORDER BY salary DESC

  ) AS rn

  FROM employees

)

SELECT * FROM ranked WHERE rn = 1;

**实测数据:**50万行员工表,自连接耗时1.8秒,窗口函数0.4秒,快了4.5倍,代码也更清晰。

总结:SQL优化检查清单

✅ 1. 先EXPLAIN看执行计划,别凭感觉

✅ 2. 组合索引遵循最左前缀,单表不超过5个

✅ 3. 子查询改JOIN,避免全表扫描

✅ 4. 不用SELECT *,善用覆盖索引

✅ 5. LIKE前缀通配符走索引,后缀不行

✅ 6. 深分页用游标,别用OFFSET

✅ 7. 批量INSERT,每次500-1000条

✅ 8. 窗口函数替代自连接

📌 数据来源:个人工作记录统计(2024-2026年数据分析项目实战),测试环境为MySQL 8.0,表数据量100万-1000万行。

你在工作中遇到过哪些SQL性能坑?评论区聊聊,船长帮你看看。