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

315 lines
7.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

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