学习是为了不落后整理则是为了不忘记。这是我在数据分析这行干了近10年的体会。SQL写得烂不只是慢是浪费公司的钱——你多跑1小时的查询集群就多烧1小时的钱。2026年了AI能帮你写SQL但AI写出来的SQL不一定快。今天船长把压箱底的8个优化技巧全拆给你看每个都有真实案例直接复制就能用。一、EXPLAIN先看执行计划别猜90%的SQL性能问题EXPLAIN一眼就能看出来。但很多人习惯改了再跑跑完再说。正确姿势EXPLAIN ANALYZESELECT u.name, COUNT(o.id) AS order_countFROM users uLEFT JOIN orders o ON u.id o.user_idWHERE u.created_at 2026-01-01GROUP 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 paidWHERE user_id 123-- ❌ 不能命中索引跳过了最左列WHERE status paid AND created_at 2026-01-01船长经验单表索引不超过5个组合索引列数不超过3列。超过这个数说明你的表设计有问题。三、子查询改成JOIN性能差10倍这是新手最常犯的错。子查询在MySQL 5.6之前每执行一次就是一次全表扫描改成JOIN可以直接走索引。慢写法子查询SELECT * FROM ordersWHERE user_id IN (SELECT id FROM users WHERE city 北京);快写法JOINSELECT o.* FROM orders oINNER JOIN users u ON o.user_id u.idWHERE 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代替逐条INSERTinserting 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 eINNER JOIN (SELECT dept, MAX(salary) AS max_salFROM 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 rnFROM 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性能坑评论区聊聊船长帮你看看。