ETest-Vue-FastAPI/批次聚合查询性能优化方案.md

315 lines
7.8 KiB
Markdown
Raw Normal View History

# 批次聚合查询性能优化方案
## 性能分析
### 当前聚合查询的性能问题
**查询特点**:
- 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