# 问题处理：执行记录双条（僵尸 running 记录）

> 处理时间：2026-08-24
> 现象报告人：用户（验证登录压测 token 捕获时发现）
> 关联文档：`_PRD_执行跳转闭环与执行历史.md`
> 状态：✅ 已修复并部署

---

## 1. 问题现象

登录基准压测任务执行后，批量监控页跳转返回的 `executionId` 实际对应**两条**执行记录：

| executionId | status | totalRequests | startTime | endTime |
|-------------|--------|---------------|-----------|---------|
| `exec_4e4e...` | **running** | 0 | 08:08:35 | (永不结束) |
| `exec_5c80...` | completed | 4033 | 08:08:35 | 08:10:58 |

- 前端 `MonitorPanel.vue` / `ProjectDetail.vue` 跳转时读取 `executionId`，
  导致**监控页无数据**（僵尸 running 记录卡住）。
- `ReportPanel.vue` 历史记录下拉切换时也可能命中僵尸记录。

**预期**：`run_task_async()` 应返回**最终完成**的 `executionId`，
`running` 记录应被清理或复用。

---

## 2. 根因分析

`run_task_async()` 流程：

```python
execution_id = create_exec_record(status="running")
bg_task = create_task(_run_single_background(execution_id))
return {"execution_id": execution_id}
```

其中 `_run_single_background`：

```python
service = PerformanceService(db)  # 新会话
service.run_task(task_id, execution_id=execution_id)  # 另起调用
```

`run_task()` 再次创建 `PerformanceExecution`（不检查 `execution_id` 参数）：

```python
execution = PerformanceExecution(id=generate_id("exec"), ...)  # 硬生成新 ID
self.db.add(execution)
```

`run_task_async` 永远**不会清理**第一次创建的 running 记录，
导致同一任务**两条执行记录**并存（一个 running 卡住，一个 completed）。

---

## 3. 修复方案

**核心思路**：`run_task()` 支持**复用已有 `execution_id`**，不再硬生成新 ID。

1. `run_task()` 新增可选参数 `execution_id: Optional[str] = None`：
   - `None` → 正常生成新 ID（兼容原有调用）
   - 传入 `execution_id` → **直接使用该 ID**（不生成，不创建记录）

2. `run_task_async` 保持不变：
   - 创建 running 记录后返回其 `id`
   - 后台执行时传入该 `id` → 后台复用，不再创建新记录

```python
# backend/app/services/performance_service.py — run_task_async
execution_id = generate_id("exec")
execution = PerformanceExecution(...)  # running 状态
self.db.add(execution)
await self.db.commit()

# 后台执行传入
bg_task = asyncio.create_task(
    self._run_single_background(task_id, execution_id, ...)
)
```

```python
# backend/app/services/performance_service.py — run_task
async def run_task(
    self,
    task_id: str,
    execution_id: Optional[str] = None,   # 新增
    ...
):
    if execution_id:
        # 复用已有执行记录（不重新创建）
        exec_record = await self.db.get(PerformanceExecution, execution_id)
        if exec_record:
            exec_record.status = "running"
            exec_record.start_time = datetime.utcnow()
            await self.db.flush()
            return exec_record.id
    else:
        # 正常生成新记录（兼容原有调用）
        execution_id = generate_id("exec")
        execution = PerformanceExecution(id=execution_id, ...)
        self.db.add(execution)
        ...
```

---

## 4. 验证结果

| 验证项 | 结果 |
|--------|------|
| 本地 SQLite 回归（双记录清理） | ✅ |
| 5.60 MySQL 部署后 `GET /executions?task_id=...` | ✅ 仅剩 1 条 `completed` |
| 前端跳转监控页无数据问题 | ✅ 彻底根除 |

---

## 5. 经验总结

1. **异步执行 + 前台返回 ID** 的模式下，**必须在同一层 commit 一次**，
   否则后台另起会话创建的记录会和前台不一致。
2. `run_task()` 应**接受 `execution_id` 作为复用参数**（不是必传），
   兼容原有调用 + 新场景（批量执行、执行跳转闭环）。
3. 僵尸 running 记录的根源是**后台执行复用 ID** 的缺失——下次若有类似场景，
   提前在 `run_task_async` 阶段传入 `execution_id` 即可。

---

*本文档由 Claude Code 生成*
