人人都会AI编程

6.3 数据库场景:SQL 语句编写与优化

更新时间:2026-06-28

场景概述

线上数据库性能问题,80%都源于糟糕的SQL。本节聚焦实际开发中最常见的慢查询、锁等待、CPU飙高场景,给出可直接落地的编写规范和优化套路。


一、基础编写规范(先别写烂SQL)

1. SELECT * 是万恶之源

反面教材:

-- 不要这样写
SELECT * FROM user WHERE status = 1;

正确姿势:

-- 只拿需要的字段,减少网络IO和内存占用
SELECT id, username, email 
FROM user 
WHERE status = 1 
LIMIT 100;

2. 批量操作分批做

一次性插入10万条数据会导致binlog暴增、主从延迟。

正确姿势:

// Java伪代码:分批提交,每批1000条
for (int i = 0; i < total; i += 1000) {
    insertBatch(list.subList(i, Math.min(i + 1000, total)));
}

二、索引使用实战(效率提升10倍的关键)

1. 索引失效的坑

最频繁的翻车现场:

| 错误写法 | 为什么失效 | 修正方案 |
|---------|-----------|---------|
| WHERE LEFT(phone, 3) = '138' | 对字段做函数运算 | WHERE phone LIKE '138%' |
| WHERE order_id + 1 = 100 | 索引列参与计算 | WHERE order_id = 99 |
| WHERE status IN (1,2,3,4,5,6) | IN过多导致全表扫描 | 用 BETWEEN 或拆多次查询 |
| WHERE order_no IS NULL | NULL值不走索引(MyISAM) | 默认值设为0或空字符串 |

2. 联合索引最左前缀

建了 (a,b,c) 索引,只有以下情况能用上:

  • WHERE a=1
  • WHERE a=1 AND b=2
  • WHERE a=1 AND b=2 AND c=3
  • WHERE b=2 ✗(跳过了a)

实战建议: 把区分度最高的字段放最左边。


三、常见优化技巧

1. 分页优化(深分页问题)

问题SQL:

-- 100万页之后慢如狗
SELECT * FROM orders 
WHERE status=1 
ORDER BY create_time DESC 
LIMIT 1000000, 10;

优化方案:

-- 利用覆盖索引+子查询
SELECT * FROM orders o
JOIN (
    SELECT id FROM orders 
    WHERE status=1 
    ORDER BY create_time DESC 
    LIMIT 1000000, 10
) tmp ON o.id = tmp.id;

2. 关联查询 vs 多次单表查询

数据量小时(< 1万条): JOIN 更方便,注意关联字段加索引。
数据量大时: 拆成多次单表查询,在业务层组装(避免临时表过大)。

3. 写操作优化

  • UPDATE/DELETE 一定要带WHERE条件(血泪教训)
  • UPDATE避免批量锁表:
-- 分批更新,每次500条
UPDATE orders 
SET status = 2 
WHERE status = 1 
LIMIT 500;

四、真实案例:从3秒到30毫秒

背景: 电商系统订单查询慢,高峰期数据库CPU 90%。

原SQL:

SELECT o.*, u.username, p.product_name 
FROM orders o
LEFT JOIN user u ON o.user_id = u.id
LEFT JOIN product p ON o.product_id = p.id
WHERE o.create_time > '2024-01-01'
AND o.status IN (1,2,3,4,5)
ORDER BY o.id DESC
LIMIT 20;

问题诊断:

  1. create_timestatus 单独有索引,但MySQL选择了全表扫描
  2. 大表JOIN产生临时表
  3. SELECT * 返回字段过多

优化后:

-- 1. 建立联合索引 (create_time, status, id)
-- 2. 先查主键,再回表取详情
SELECT o.*, u.username, p.product_name 
FROM (
    SELECT id FROM orders 
    WHERE create_time > '2024-01-01' 
    AND status = 1  -- 去掉IN,业务分多次查或改为范围查询
    ORDER BY id DESC 
    LIMIT 20
) tmp
JOIN orders o ON tmp.id = o.id
LEFT JOIN user u ON o.user_id = u.id
LEFT JOIN product p ON o.product_id = p.id;

结果: 查询时间从3.2秒降至28毫秒,CPU降到15%。


五、Checklist:上线前必查

  • [ ] EXPLAIN 检查是否走了索引(type列至少range以上)
  • [ ] 表数据量>10万时,是否有合适的索引
  • [ ] 是否存在隐式类型转换(如字符串ID传了数字)
  • [ ] 批量操作是否有上限控制
  • [ ] 是否用了FOR UPDATE却没走索引(会锁全表!)

工具推荐:

  • MySQL:使用 EXPLAIN FORMAT=JSON 看执行代价
  • 阿里云/腾讯云:开启慢查询日志(>1秒),定期用pt-query-digest分析

小结: SQL优化不是炫技,而是减少数据扫描量避免额外排序。先让SQL跑起来,再让它跑得快,记住:好的索引设计胜过一切调优技巧。