第三章:SQL语句优化与慢查询治理
聊到ERP系统的性能问题,十有八九都跟SQL有关。我做了这么多年ERP架构,见过太多系统被几条烂SQL拖垮的例子。今天就跟大家聊聊,我在ERP项目中治理慢查询的那些实战经验。
3.1 为什么慢查询是ERP的“头号杀手”
ERP系统跟普通互联网应用不一样。它的业务逻辑复杂,表关联多,数据量大。你想想看,一张销售订单可能要关联客户表、产品表、库存表、价格表、财务表……随便一个查询就是七八张表。
我记得有个客户,他们的订单查询页面要等30秒才能出来。业务员每天要查几十次订单,这谁受得了?后来一查,发现是几个慢查询在作祟。说白了,慢查询就是ERP系统的“慢性病”,不治不行。
3.2 避免SELECT *:别拿不需要的数据
这是最基础,也是最容易被忽视的问题。很多开发人员写SQL时习惯用SELECT *,图省事。但在ERP系统里,这简直是灾难。
为什么不能SELECT *?
- ERP表字段多,一张表动辄几十个字段
- 传输大量无用数据,增加网络开销
- 无法利用覆盖索引,导致回表查询
我在项目中遇到过这样的情况:一个订单查询,只需要订单号、客户名、金额三个字段。结果用了SELECT *,把订单备注、审批意见、历史修改记录全拉出来了。数据量直接翻了5倍。
正确的做法是:
-- 错误写法
SELECT * FROM sales_order WHERE order_date >= '2024-01-01';
-- 正确写法
SELECT order_id, customer_name, total_amount
FROM sales_order
WHERE order_date >= '2024-01-01';
我的习惯:写SQL时先想清楚“我需要哪些字段”,再动手写。别偷懒,省那几秒钟,后面可能要花几小时来优化。
3.3 合理使用JOIN:别让关联变成灾难
ERP系统里,JOIN是家常便饭。但JOIN用不好,性能就崩了。我见过最夸张的一个查询,关联了12张表,跑了8分钟还没出结果。
关于JOIN,我有几个原则:
- 小表驱动大表:用数据量小的表作为驱动表
- 确保关联字段有索引:这是基本要求
- 避免多表嵌套:超过3张表的JOIN,要考虑是否合理
举个例子,查询某个客户的订单明细:
-- 低效写法
SELECT o.*, c.*, p.*, i.*
FROM orders o
JOIN customers c ON o.customer_id = c.id
JOIN products p ON o.product_id = p.id
JOIN inventory i ON p.id = i.product_id
WHERE c.customer_code = 'C001';
-- 优化写法
SELECT o.order_id, o.order_date, p.product_name, i.quantity
FROM customers c
JOIN orders o ON c.id = o.customer_id
JOIN products p ON o.product_id = p.id
JOIN inventory i ON p.id = i.product_id
WHERE c.customer_code = 'C001';
你看,第一个写法用SELECT *,而且关联顺序也不对。第二个写法先过滤客户,再关联其他表,数据量小了很多。
避坑指南:我曾经遇到过一个案例,开发人员把LEFT JOIN当INNER JOIN用,结果查出来的数据翻了好几倍。后来排查才发现,关联条件写错了,导致笛卡尔积。嗯,这里要注意,JOIN的条件一定要写对。
3.4 分页优化:延迟关联的妙用
ERP系统里,分页查询太常见了。但传统的LIMIT OFFSET写法,在数据量大时性能极差。为什么?因为数据库要先扫描OFFSET+ROWS行数据,再丢弃前面的OFFSET行。
我有个客户,订单表有500万条数据。每次翻到第100页,查询就要等10秒。后来我用延迟关联优化了一下,查询时间降到了0.1秒。
延迟关联的核心思路:先快速定位到需要的主键,再关联获取完整数据。
-- 传统分页(性能差)
SELECT * FROM orders
ORDER BY order_date DESC
LIMIT 20 OFFSET 1000;
-- 延迟关联(性能好)
SELECT o.*
FROM orders o
JOIN (
SELECT id FROM orders
ORDER BY order_date DESC
LIMIT 20 OFFSET 1000
) tmp ON o.id = tmp.id
ORDER BY o.order_date DESC;
为什么延迟关联快?因为子查询只查了主键和排序字段,可以利用索引快速定位。然后再用主键关联回原表,获取完整数据。这样避免了扫描大量无用数据。
性能对比:
| 分页方式 | 第1页 | 第50页 | 第100页 |
|---|---|---|---|
| 传统分页 | 0.02s | 2.3s | 10.5s |
| 延迟关联 | 0.02s | 0.05s | 0.08s |
你看,越往后翻,延迟关联的优势越明显。我个人习惯,只要数据量超过10万条,分页查询必用延迟关联。
3.5 慢查询日志:找到问题的“照妖镜”
说了这么多优化方法,但前提是——你得先找到慢查询。慢查询日志就是干这个的。
在MySQL里,开启慢查询日志很简单:
-- 开启慢查询日志
SET GLOBAL slow_query_log = ON;
SET GLOBAL long_query_time = 2; -- 超过2秒的查询记录
SET GLOBAL slow_query_log_file = '/var/log/mysql/slow.log';
但光开启还不够,关键是怎么分析。我常用的工具是pt-query-digest,它能自动汇总慢查询,按执行时间排序,还能给出优化建议。
分析慢查询日志时,我重点关注几个指标:
- 查询时间:超过2秒的就要警惕
- 扫描行数:扫描行数远大于返回行数,说明索引有问题
- 执行频率:一个慢查询执行100次,比执行1次的危害大得多
我的经验:在ERP系统上线初期,我就把慢查询日志开着。设置阈值为1秒,每天检查一次。这样能及时发现性能隐患,避免问题积累到不可收拾的地步。
3.6 实战案例:一个订单查询的优化全过程
最后,分享一个我在ERP项目中的真实案例。客户反馈,查询某个月的订单列表要等20秒。
第一步,查慢查询日志:
# Time: 2024-01-15T10:30:00
# Query_time: 20.5s Lock_time: 0.1s
# Rows_sent: 50 Rows_examined: 500000
SELECT * FROM orders
WHERE order_date BETWEEN '2024-01-01' AND '2024-01-31'
ORDER BY order_date DESC
LIMIT 50;
问题很明显:扫描了50万行,只返回50行。而且用了SELECT *。
第二步,优化:
-- 加索引
ALTER TABLE orders ADD INDEX idx_order_date (order_date);
-- 延迟关联
SELECT o.* FROM orders o
JOIN (
SELECT id FROM orders
WHERE order_date BETWEEN '2024-01-01' AND '2024-01-31'
ORDER BY order_date DESC
LIMIT 50
) tmp ON o.id = tmp.id
ORDER BY o.order_date DESC;
优化后,查询时间从20秒降到了0.3秒。客户都惊呆了。
总结一下:SQL优化没有捷径,就是不断发现问题、分析问题、解决问题。慢查询日志是你的“照妖镜”,延迟关联是你的“杀手锏”,避免SELECT *和合理使用JOIN是你的“基本功”。把这些用好了,ERP系统的性能问题至少能解决80%。
嗯,今天就聊到这里。下一章,我会讲讲索引优化的那些事儿,包括复合索引的设计、索引下推、以及如何避免索引失效。咱们下次见。