MySQL 多表连接 + 聚合查询的性能优化实践
MySQL 多表连接 + 聚合查询的性能优化实践
在实际业务开发中,复杂的统计类 SQL 查询常常因为频繁的表连接和聚合操作而导致性能问题。本文通过一个真实场景的 SQL 查询,演示如何通过公共表表达式(CTE)、逻辑拆分与结构优化,有效提升查询性能。
🔍 背景与问题描述
原始 SQL 查询用于统计每个产品在仓库中的各种状态(待入库、已入库、待出库等)下的数量和箱数。由于逻辑复杂,涉及三个表的大量连接与聚合,导致执行缓慢、可维护性差。
✅ 优化目标
- 减少重复连接,提高性能
- 拆分逻辑结构,提升可读性
- 支持分页,避免一次加载过多数据
🛠 优化关键点
1. 使用 CTE(公共表表达式)
将多个聚合子查询中都用到的基础表连接操作抽出成一个 BaseData,避免重复连接。
2. 拆分聚合逻辑
针对不同的聚合统计需求(如待入库数、在库可用数、待出库数等),单独使用一个 CTE 来处理。
3. 主查询中聚合拼接 + 分页控制
将所有聚合统计 LEFT JOIN 回主统计表,并通过 LIMIT 进行分页控制。
🧾 完整优化后的 SQL 查询
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
WITH BaseData AS (
SELECT
rl.ProductId,
whbox.WarehouseId,
box.ShopId,
box.ProductKindCount,
whbox.ContainerBoxStatus,
whbox.HasException,
whbox.WarehouseType,
whbox.IsLocked,
whbox.InWarehouseCount,
rl.ContainerCount,
rl.AllowBound
FROM warehouse_WarehouseBoxes whbox
JOIN warehouse_Boxes box ON whbox.BoxId = box.Id
JOIN warehouse_BoxProductRls rl ON box.Id = rl.BoxId
WHERE box.ProductKindCount > 0
AND whbox.WarehouseType = 1
AND (7140703588798103553 = 0 OR box.ShopId = 7140703588798103553)
AND (0 = 0 OR rl.ProductId = 0)
),
inv AS (
SELECT ProductId, WarehouseId, COUNT(1) AS BarCount
FROM BaseData
WHERE ContainerBoxStatus IS NOT NULL AND HasException = 0
GROUP BY ProductId, WarehouseId
),
inw AS (
SELECT ProductId, WarehouseId, SUM(ContainerCount) AS AwaitInWarehouseCount
FROM BaseData
WHERE AllowBound = 1 AND ContainerBoxStatus BETWEEN 10 AND 19
GROUP BY ProductId, WarehouseId
),
inwb AS (
SELECT ProductId, WarehouseId, COUNT(1) AS AwaitInWarehouseBoxCount
FROM BaseData
WHERE ContainerBoxStatus BETWEEN 10 AND 19
GROUP BY ProductId, WarehouseId
),
onuw AS (
SELECT ProductId, WarehouseId, SUM(ContainerCount) AS OnWarehouseUsableCount
FROM BaseData
WHERE AllowBound = 1 AND ContainerBoxStatus = 20 AND IsLocked = FALSE
GROUP BY ProductId, WarehouseId
),
onuwb AS (
SELECT ProductId, WarehouseId, COUNT(1) AS OnWarehouseUsableBoxCount
FROM BaseData
WHERE ContainerBoxStatus BETWEEN 20 AND 39 AND InWarehouseCount IS NOT NULL
GROUP BY ProductId, WarehouseId
),
ouw AS (
SELECT ProductId, WarehouseId, SUM(ContainerCount) AS AwaitOutWarehouseCount
FROM BaseData
WHERE AllowBound = 1 AND ContainerBoxStatus BETWEEN 40 AND 49
GROUP BY ProductId, WarehouseId
),
ouwb AS (
SELECT ProductId, WarehouseId, COUNT(1) AS AwaitOutWarehouseBoxCount
FROM BaseData
WHERE ContainerBoxStatus BETWEEN 40 AND 49
GROUP BY ProductId, WarehouseId
)
SELECT
inv.ProductId,
inv.WarehouseId,
inw.AwaitInWarehouseCount,
inwb.AwaitInWarehouseBoxCount,
onuw.OnWarehouseUsableCount,
onuwb.OnWarehouseUsableBoxCount,
ouw.AwaitOutWarehouseCount,
ouwb.AwaitOutWarehouseBoxCount
FROM inv
LEFT JOIN inw ON inw.ProductId = inv.ProductId AND inw.WarehouseId = inv.WarehouseId
LEFT JOIN inwb ON inwb.ProductId = inv.ProductId AND inwb.WarehouseId = inv.WarehouseId
LEFT JOIN onuw ON onuw.ProductId = inv.ProductId AND onuw.WarehouseId = inv.WarehouseId
LEFT JOIN onuwb ON onuwb.ProductId = inv.ProductId AND onuwb.WarehouseId = inv.WarehouseId
LEFT JOIN ouw ON ouw.ProductId = inv.ProductId AND ouw.WarehouseId = inv.WarehouseId
LEFT JOIN ouwb ON ouwb.ProductId = inv.ProductId AND ouwb.WarehouseId = inv.WarehouseId
LIMIT 0, 10;
📌 附加优化建议
添加索引:确保以下字段存在 BTree 索引以提高 JOIN 与 WHERE 性能:
warehouse_Boxes.Idwarehouse_BoxProductRls.BoxIdwarehouse_BoxProductRls.ProductIdwarehouse_WarehouseBoxes.BoxIdwarehouse_WarehouseBoxes.ContainerBoxStatuswarehouse_WarehouseBoxes.IsLockedwarehouse_WarehouseBoxes.HasExceptionwarehouse_WarehouseBoxes.WarehouseTypewarehouse_Boxes.ShopIdwarehouse_Boxes.ProductKindCount
使用 EXPLAIN 分析执行计划:可帮助确认是否使用索引、连接顺序是否合理。
考虑将 BaseData 落地为临时表:对于频繁使用的逻辑,可以使用临时表缓存 BaseData 内容,避免重复计算。
✅ 总结
通过结构重构(CTE)、逻辑拆分与索引优化,我们将一个复杂的 SQL 查询变得更高效、更清晰。如果你在处理复杂聚合查询时遇到性能瓶颈,不妨尝试这种方式进行重构。
本文由作者按照 CC BY 4.0 进行授权