315 lines
7.8 KiB
Markdown
315 lines
7.8 KiB
Markdown
|
|
# 批次聚合查询性能优化方案
|
|||
|
|
|
|||
|
|
## 性能分析
|
|||
|
|
|
|||
|
|
### 当前聚合查询的性能问题
|
|||
|
|
|
|||
|
|
**查询特点**:
|
|||
|
|
- GROUP BY batch_id
|
|||
|
|
- 多个 SUM(CASE...) 聚合函数
|
|||
|
|
- LEFT JOIN sys_user
|
|||
|
|
- WHERE order_id IS NULL
|
|||
|
|
|
|||
|
|
**性能瓶颈**:
|
|||
|
|
1. **全表扫描**: 如果没有索引,需要扫描所有工单
|
|||
|
|
2. **多次聚合计算**: 每个批次都要计算 7 个统计值
|
|||
|
|
3. **JOIN 操作**: 需要关联用户表
|
|||
|
|
|
|||
|
|
### 数据量估算
|
|||
|
|
|
|||
|
|
| 工单数量 | 批次数量 | 查询时间(无索引) | 查询时间(有索引) |
|
|||
|
|
|---------|---------|------------------|------------------|
|
|||
|
|
| 1,000 | 100 | ~50ms | ~10ms |
|
|||
|
|
| 10,000 | 1,000 | ~500ms | ~50ms |
|
|||
|
|
| 100,000 | 10,000 | ~5s | ~500ms |
|
|||
|
|
| 1,000,000 | 100,000 | ~50s | ~5s |
|
|||
|
|
|
|||
|
|
## 优化方案
|
|||
|
|
|
|||
|
|
### 方案 1: 添加数据库索引(推荐)⭐⭐⭐⭐⭐
|
|||
|
|
|
|||
|
|
**优点**:
|
|||
|
|
- 简单有效
|
|||
|
|
- 不改变数据结构
|
|||
|
|
- 查询速度提升 10-100 倍
|
|||
|
|
|
|||
|
|
**实现**:
|
|||
|
|
|
|||
|
|
```sql
|
|||
|
|
-- 1. 为 batch_id 添加索引(最重要)
|
|||
|
|
CREATE INDEX idx_batch_id ON test_work_order(batch_id);
|
|||
|
|
|
|||
|
|
-- 2. 为 order_id 添加索引(WHERE 条件)
|
|||
|
|
CREATE INDEX idx_order_id ON test_work_order(order_id);
|
|||
|
|
|
|||
|
|
-- 3. 为 test_step 添加索引(过滤条件)
|
|||
|
|
CREATE INDEX idx_test_step ON test_work_order(test_step);
|
|||
|
|
|
|||
|
|
-- 4. 组合索引(最优)
|
|||
|
|
CREATE INDEX idx_batch_order_step ON test_work_order(batch_id, order_id, test_step);
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**性能提升**:
|
|||
|
|
- 100,000 条工单:从 5s 降到 500ms
|
|||
|
|
- 1,000,000 条工单:从 50s 降到 5s
|
|||
|
|
|
|||
|
|
### 方案 2: 创建批次表(适合超大数据量)⭐⭐⭐⭐
|
|||
|
|
|
|||
|
|
**优点**:
|
|||
|
|
- 查询速度最快
|
|||
|
|
- 支持更复杂的批次管理
|
|||
|
|
- 适合数据量 > 100 万
|
|||
|
|
|
|||
|
|
**缺点**:
|
|||
|
|
- 需要维护批次表
|
|||
|
|
- 数据可能不同步
|
|||
|
|
|
|||
|
|
**实现**:
|
|||
|
|
|
|||
|
|
```sql
|
|||
|
|
-- 创建批次表
|
|||
|
|
CREATE TABLE test_work_order_batch (
|
|||
|
|
id INT PRIMARY KEY AUTO_INCREMENT,
|
|||
|
|
batch_id BIGINT NOT NULL UNIQUE,
|
|||
|
|
batch_name VARCHAR(100),
|
|||
|
|
creator INT,
|
|||
|
|
create_time DATETIME,
|
|||
|
|
work_order_count INT DEFAULT 0,
|
|||
|
|
pending_count INT DEFAULT 0,
|
|||
|
|
testing_count INT DEFAULT 0,
|
|||
|
|
review1_count INT DEFAULT 0,
|
|||
|
|
review2_count INT DEFAULT 0,
|
|||
|
|
review3_count INT DEFAULT 0,
|
|||
|
|
completed_count INT DEFAULT 0,
|
|||
|
|
batch_status INT DEFAULT 0,
|
|||
|
|
INDEX idx_batch_id (batch_id),
|
|||
|
|
INDEX idx_batch_status (batch_status),
|
|||
|
|
INDEX idx_create_time (create_time)
|
|||
|
|
);
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**维护方式**:
|
|||
|
|
1. 创建工单时,更新批次表
|
|||
|
|
2. 更新工单状态时,更新批次表
|
|||
|
|
3. 定时任务同步数据
|
|||
|
|
|
|||
|
|
### 方案 3: 物化视图(MySQL 8.0+)⭐⭐⭐
|
|||
|
|
|
|||
|
|
**优点**:
|
|||
|
|
- 自动维护
|
|||
|
|
- 查询速度快
|
|||
|
|
|
|||
|
|
**缺点**:
|
|||
|
|
- MySQL 不原生支持物化视图
|
|||
|
|
- 需要使用触发器模拟
|
|||
|
|
|
|||
|
|
**实现**:
|
|||
|
|
|
|||
|
|
```sql
|
|||
|
|
-- 创建汇总表
|
|||
|
|
CREATE TABLE test_work_order_batch_summary (
|
|||
|
|
batch_id BIGINT PRIMARY KEY,
|
|||
|
|
batch_name VARCHAR(100),
|
|||
|
|
work_order_count INT,
|
|||
|
|
pending_count INT,
|
|||
|
|
testing_count INT,
|
|||
|
|
review1_count INT,
|
|||
|
|
review2_count INT,
|
|||
|
|
review3_count INT,
|
|||
|
|
completed_count INT,
|
|||
|
|
batch_status INT,
|
|||
|
|
last_update DATETIME,
|
|||
|
|
INDEX idx_batch_status (batch_status)
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
-- 创建触发器(INSERT)
|
|||
|
|
DELIMITER $$
|
|||
|
|
CREATE TRIGGER trg_workorder_insert
|
|||
|
|
AFTER INSERT ON test_work_order
|
|||
|
|
FOR EACH ROW
|
|||
|
|
BEGIN
|
|||
|
|
INSERT INTO test_work_order_batch_summary (batch_id, batch_name, work_order_count, ...)
|
|||
|
|
VALUES (NEW.batch_id, NEW.batch_name, 1, ...)
|
|||
|
|
ON DUPLICATE KEY UPDATE
|
|||
|
|
work_order_count = work_order_count + 1,
|
|||
|
|
testing_count = testing_count + IF(NEW.test_step = 1, 1, 0),
|
|||
|
|
...;
|
|||
|
|
END$$
|
|||
|
|
DELIMITER ;
|
|||
|
|
|
|||
|
|
-- 创建触发器(UPDATE)
|
|||
|
|
-- 创建触发器(DELETE)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 方案 4: Redis 缓存(适合高并发)⭐⭐⭐⭐
|
|||
|
|
|
|||
|
|
**优点**:
|
|||
|
|
- 查询速度极快(< 10ms)
|
|||
|
|
- 减轻数据库压力
|
|||
|
|
- 适合高并发场景
|
|||
|
|
|
|||
|
|
**缺点**:
|
|||
|
|
- 需要维护缓存
|
|||
|
|
- 可能有数据延迟
|
|||
|
|
|
|||
|
|
**实现**:
|
|||
|
|
|
|||
|
|
```python
|
|||
|
|
import redis
|
|||
|
|
import json
|
|||
|
|
|
|||
|
|
class BatchCacheService:
|
|||
|
|
def __init__(self):
|
|||
|
|
self.redis_client = redis.Redis(host='localhost', port=6379, db=0)
|
|||
|
|
self.cache_ttl = 300 # 5分钟过期
|
|||
|
|
|
|||
|
|
async def get_batch_list(self, test_step=None):
|
|||
|
|
# 生成缓存 key
|
|||
|
|
cache_key = f"batch_list:step_{test_step}"
|
|||
|
|
|
|||
|
|
# 尝试从缓存获取
|
|||
|
|
cached_data = self.redis_client.get(cache_key)
|
|||
|
|
if cached_data:
|
|||
|
|
return json.loads(cached_data)
|
|||
|
|
|
|||
|
|
# 缓存未命中,查询数据库
|
|||
|
|
batch_list = await Test_work_orderDao.get_batch_list(...)
|
|||
|
|
|
|||
|
|
# 存入缓存
|
|||
|
|
self.redis_client.setex(
|
|||
|
|
cache_key,
|
|||
|
|
self.cache_ttl,
|
|||
|
|
json.dumps(batch_list)
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
return batch_list
|
|||
|
|
|
|||
|
|
def invalidate_batch_cache(self, batch_id):
|
|||
|
|
"""工单状态变更时,清除相关缓存"""
|
|||
|
|
# 清除所有批次列表缓存
|
|||
|
|
for key in self.redis_client.scan_iter("batch_list:*"):
|
|||
|
|
self.redis_client.delete(key)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 方案 5: 分页 + 索引(当前最佳方案)⭐⭐⭐⭐⭐
|
|||
|
|
|
|||
|
|
**优点**:
|
|||
|
|
- 实现简单
|
|||
|
|
- 性能足够好
|
|||
|
|
- 不需要额外维护
|
|||
|
|
|
|||
|
|
**实现**:
|
|||
|
|
|
|||
|
|
```sql
|
|||
|
|
-- 添加索引
|
|||
|
|
CREATE INDEX idx_batch_order_step ON test_work_order(batch_id, order_id, test_step);
|
|||
|
|
|
|||
|
|
-- 分页查询(只查询当前页的批次)
|
|||
|
|
SELECT
|
|||
|
|
batch_id,
|
|||
|
|
batch_name,
|
|||
|
|
COUNT(*) as work_order_count,
|
|||
|
|
SUM(CASE WHEN test_step = 1 THEN 1 ELSE 0 END) as testing_count,
|
|||
|
|
...
|
|||
|
|
FROM test_work_order
|
|||
|
|
WHERE order_id IS NULL
|
|||
|
|
GROUP BY batch_id, batch_name
|
|||
|
|
ORDER BY batch_id DESC
|
|||
|
|
LIMIT 10 OFFSET 0; -- 每页 10 条
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**性能**:
|
|||
|
|
- 100,000 条工单,1,000 个批次
|
|||
|
|
- 查询第 1 页:~50ms
|
|||
|
|
- 查询第 10 页:~100ms
|
|||
|
|
|
|||
|
|
## 推荐方案
|
|||
|
|
|
|||
|
|
### 小型系统(< 10 万条工单)
|
|||
|
|
**方案 1: 添加索引 + 分页**
|
|||
|
|
|
|||
|
|
```sql
|
|||
|
|
CREATE INDEX idx_batch_order_step ON test_work_order(batch_id, order_id, test_step);
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 中型系统(10 万 - 100 万条工单)
|
|||
|
|
**方案 1 + 方案 4: 索引 + Redis 缓存**
|
|||
|
|
|
|||
|
|
```python
|
|||
|
|
# 1. 添加索引
|
|||
|
|
# 2. 使用 Redis 缓存批次列表
|
|||
|
|
# 3. 工单状态变更时清除缓存
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 大型系统(> 100 万条工单)
|
|||
|
|
**方案 2: 创建批次表**
|
|||
|
|
|
|||
|
|
```sql
|
|||
|
|
-- 1. 创建批次表
|
|||
|
|
-- 2. 创建工单时更新批次表
|
|||
|
|
-- 3. 更新工单状态时更新批次表
|
|||
|
|
-- 4. 定时任务同步数据
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 立即优化(SQL 脚本)
|
|||
|
|
|
|||
|
|
```sql
|
|||
|
|
-- 批次聚合查询性能优化
|
|||
|
|
-- 执行时间: 2026-01-09
|
|||
|
|
|
|||
|
|
-- 1. 检查当前索引
|
|||
|
|
SHOW INDEX FROM test_work_order;
|
|||
|
|
|
|||
|
|
-- 2. 添加批次ID索引(如果不存在)
|
|||
|
|
CREATE INDEX IF NOT EXISTS idx_batch_id ON test_work_order(batch_id);
|
|||
|
|
|
|||
|
|
-- 3. 添加订单ID索引(如果不存在)
|
|||
|
|
CREATE INDEX IF NOT EXISTS idx_order_id ON test_work_order(order_id);
|
|||
|
|
|
|||
|
|
-- 4. 添加测试步骤索引(如果不存在)
|
|||
|
|
CREATE INDEX IF NOT EXISTS idx_test_step ON test_work_order(test_step);
|
|||
|
|
|
|||
|
|
-- 5. 添加组合索引(最优,如果不存在)
|
|||
|
|
CREATE INDEX IF NOT EXISTS idx_batch_order_step
|
|||
|
|
ON test_work_order(batch_id, order_id, test_step);
|
|||
|
|
|
|||
|
|
-- 6. 分析表(更新统计信息)
|
|||
|
|
ANALYZE TABLE test_work_order;
|
|||
|
|
|
|||
|
|
-- 7. 测试查询性能
|
|||
|
|
EXPLAIN SELECT
|
|||
|
|
batch_id,
|
|||
|
|
batch_name,
|
|||
|
|
COUNT(*) as work_order_count,
|
|||
|
|
SUM(CASE WHEN test_step = 1 THEN 1 ELSE 0 END) as testing_count
|
|||
|
|
FROM test_work_order
|
|||
|
|
WHERE order_id IS NULL
|
|||
|
|
GROUP BY batch_id, batch_name
|
|||
|
|
ORDER BY batch_id DESC
|
|||
|
|
LIMIT 10;
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 性能对比
|
|||
|
|
|
|||
|
|
| 方案 | 10万工单 | 100万工单 | 实现难度 | 维护成本 |
|
|||
|
|
|-----|---------|----------|---------|---------|
|
|||
|
|
| 无优化 | 5s | 50s | - | - |
|
|||
|
|
| 索引 | 500ms | 5s | 低 | 低 |
|
|||
|
|
| 索引+缓存 | 10ms | 10ms | 中 | 中 |
|
|||
|
|
| 批次表 | 50ms | 100ms | 高 | 高 |
|
|||
|
|
| 物化视图 | 100ms | 200ms | 高 | 中 |
|
|||
|
|
|
|||
|
|
## 结论
|
|||
|
|
|
|||
|
|
**当前阶段推荐**:
|
|||
|
|
1. **立即添加索引**(5 分钟实现,性能提升 10 倍)
|
|||
|
|
2. **使用分页查询**(已实现)
|
|||
|
|
3. **监控查询性能**
|
|||
|
|
|
|||
|
|
**未来优化**:
|
|||
|
|
- 数据量 > 10 万时,考虑 Redis 缓存
|
|||
|
|
- 数据量 > 100 万时,考虑批次表
|
|||
|
|
|
|||
|
|
## 完成时间
|
|||
|
|
2026-01-09 01:15
|