# 问题处理：捕获输出查询接口 500（MySQL 排序爆内存）

> 处理时间：2026-08-24
> 现象报告人：用户（验证登录压测 token 捕获时发现）
> 关联文档：`_PRD_bodyTemplate动态变量替换与响应捕获.md`
> 状态：✅ 已修复并部署

---

## 1. 问题现象

登录基准压测任务 `perf_0fbcb792dcc143fd80b42cb405a75176` 配置 captureRules
（`tokens ← $.data.access_token`）执行完成后，查询捕获输出接口返回 **500**：

```
GET /api/performance/outputs?task_id=perf_0fbcb7...           → 500
GET /api/performance/tasks/{task_id}/outputs                  → 500
```

容器日志：

```
pymysql.err.OperationalError: (1038, 'Out of sort memory, consider increasing server sort buffer size')
```

**数据特征**：该任务捕获 4033 个 JWT token（每个 322 字符），单行 `value`
列（JSON 数组）约 **1.28 MB**。

---

## 2. 根因分析

`backend/app/routers/performance_output.py` 两个列表端点均执行：

```python
query.order_by(desc(PerformanceTaskOutput.created_at))  # SELECT 整行(含 value 大列) + ORDER BY
```

MySQL 执行 `ORDER BY` 时，若无法用索引完成排序，会走 **filesort**：
将**整行**（包括 1.28MB 的 `value` JSON 大列）载入 `sort_buffer`。
默认 `sort_buffer_size`（256KB~2MB）远小于单行体积 → 报 1038 错误。

> 行数少不代表安全：**行宽大**同样爆 buffer。捕获输出表「每任务每 key 一行」，
> 行数通常个位数，但单行可达数 MB（数千 token / messageId）。

SQLite（本地开发）无此限制，故本地从未复现——典型的"本地好、服务器炸"差异。

---

## 3. 修复方案

**排序阶段不触碰 `value` 大列**：两段式查询——先用轻量列（id + created_at）
排序 + 分页拿到目标行 id，再按 id 回查整行。

```python
# 1) 轻量排序：只取 id，不 SELECT value（不进 sort buffer）
id_rows = await db.execute(
    select(PerformanceTaskOutput.id)
    .where(...)                      # task_id / key 过滤
    .order_by(desc(PerformanceTaskOutput.created_at))
    .offset(...).limit(page_size)
)
ids = [r[0] for r in id_rows]

# 2) 按 id 回查整行，按 id 列表序保持分页顺序
rows = await db.execute(
    select(PerformanceTaskOutput).where(PerformanceTaskOutput.id.in_(ids))
)
```

同步修改的配套点：

| 端点 | 修改 |
|------|------|
| `GET /outputs` | 两段式查询（desc 排序） |
| `GET /tasks/{task_id}/outputs` | 两段式查询（asc 排序） |
| `GET /outputs/{output_id}` | 单行主键查询，本就不排序，无需改 |

**为什么不调大 `sort_buffer_size`**：治标不治本（token 数再翻倍仍会爆），
且 MySQL 官方不建议全局调大（每连接分配，浪费内存）。

---

## 4. 验证结果

| 验证项 | 结果 |
|--------|------|
| 本地 SQLite 回归（排序/分页/key 过滤） | ✅ |
| 5.60 MySQL 部署后 `GET /outputs?task_id=...` | ✅ 200（原 500） |
| 5.60 MySQL 部署后 `GET /tasks/{id}/outputs` | ✅ 200 |
| 大列数据完整性（4033 token 原样返回） | ✅ |

---

## 5. 经验总结

1. **含 JSON 大列的表禁止整行参与 ORDER BY**——排序前先想"这一行有多大"。
2. "每任务一行聚合值"的设计让单行体积无上限增长（capture_max=10000 值 × 322 字符 ≈ 3MB+），
   后续若性能进一步恶化，考虑拆行存储（每值一行）或截断展示。
3. SQLite 不校验的，MySQL 会教做人（同 [[mysql-deployment-gotchas]] 的老规律）。

---

*本文档由 Claude Code 生成*
