name: performance-gate description: 验证性能,包括N+1查询检测、可扩展性评估和复杂性分析。警告:在/own:done流程中触发此门。
门 4:性能审查
“代码能运行是第一步。代码能扩展是第二步。”
目的
此门在性能问题造成麻烦之前捕捉性能反模式。重点是明显问题,而不是微优化。
门状态
- 通过 — 无明显性能问题
- 警告 — 发现可能在大规模时导致问题的迹象
门问题
问题 1:可扩展性
“当有10,000个项目时会发生什么?1,000,000个时呢?”
关注点:
- 对数据增长的意识
- 大数据集的分页
- 高效的数据结构
- 无不必要的循环
问题 2:查询效率
“此操作进行多少次数据库查询?”
关注点:
- 无N+1查询
- 适当的批量操作
- 查询列上的索引
- 查询成本的意识
问题 3:重新渲染意识(前端)
“当此状态改变时,哪些组件会重新渲染?”
关注点:
- 对渲染触发器的意识
- 适当的记忆化
- 状态放置优化
- 渲染中无昂贵的计算
性能检查清单
数据库操作
- [ ] 无N+1查询(循环中的查询)
- [ ] 列表端点的分页
- [ ] 频繁查询列的索引
- [ ] 只选择需要的列(非SELECT *)
前端渲染
- [ ] 昂贵计算使用useMemo
- [ ] 事件处理程序在需要时使用useCallback
- [ ] 大列表使用虚拟化
- [ ] 重组件懒加载
API 和网络
- [ ] 响应负载最小化
- [ ] 大数据分页
- [ ] 适当的缓存头
- [ ] 无冗余API调用
一般
- [ ] 无嵌套循环(O(n²))除非有正当理由
- [ ] 无阻塞操作
- [ ] 清理间隔/超时
- [ ] 合理的内存使用
响应模板
如果通过
✅ 性能门:通过
性能考虑看起来良好:
- 数据获取高效
- 无明显的N+1模式
- 适当的分页就位
进入下一门...
如果警告
⚠️ 性能门:警告
发现 [X] 个性能问题:
**问题 1:[N+1查询 / 低效循环]**
位置:`file.ts:42`
问题:“这进行了 [N] 次查询。我们可以批处理成1次吗?”
**问题 2:[缺少分页]**
位置:`file.ts:88`
问题:“有100,000条记录时会发生什么?”
**问题 3:[昂贵渲染]**
位置:`Component.tsx:15`
问题:“这需要在每次渲染时重新计算吗?”
这些问题现在可能无关紧要,但随着应用增长会变成问题。
常见检查问题
1. N+1查询问题
❌ const users = await User.findAll();
for (const user of users) {
user.posts = await Post.findByUserId(user.id);
}
// 1 + N 次查询!
✅ const users = await User.findAll({
include: [{ model: Post }]
});
// 1 次查询带有 JOIN
2. 获取所有内容
❌ // 返回10,000个用户,每个50个字段
GET /api/users
✅ // 分页,只使用需要的字段
GET /api/users?page=1&limit=20&fields=id,name,email
3. 昂贵的渲染计算
❌ function UserList({ users }) {
// 每次渲染都运行
const sorted = users.sort((a, b) => a.name.localeCompare(b.name));
return <ul>{sorted.map(...)}</ul>;
}
✅ function UserList({ users }) {
const sorted = useMemo(
() => [...users].sort((a, b) => a.name.localeCompare(b.name)),
[users]
);
return <ul>{sorted.map(...)}</ul>;
}
4. 内联函数导致重新渲染
❌ function Parent() {
return <Child onClick={() => doSomething()} />;
// 每次渲染新函数 → 子组件重新渲染
}
✅ function Parent() {
const handleClick = useCallback(() => doSomething(), []);
return <Child onClick={handleClick} />;
}
5. 缺少清理
❌ useEffect(() => {
const interval = setInterval(fetchData, 5000);
// 内存泄漏!永远运行
}, []);
✅ useEffect(() => {
const interval = setInterval(fetchData, 5000);
return () => clearInterval(interval);
}, []);
苏格拉底式性能问题
代替直接指出修复,询问:
- “此端点为100个用户执行多少次查询?”
- “如果我添加10,000个更多项目,什么会崩溃?”
- “这个数组在每次渲染时都会重新排序吗?”
- “当组件卸载时,什么清除这个间隔?”
- “我们需要此表的所有50列吗?”
大O快速参考
| 模式 | 复杂度 | 10,000个项目 | 关注级别 |
|---|---|---|---|
| 映射查找 | O(1) | 1 操作 | 良好 |
| 单循环 | O(n) | 10,000 操作 | 通常良好 |
| 嵌套循环 | O(n²) | 100M 操作 | 警告 |
| 三重循环 | O(n³) | 1T 操作 | 关键 |
性能红旗
| 红旗 | 问题 | 原因 |
|---|---|---|
| 循环中的查询 | “我们能批处理吗?” | N+1问题 |
| 无分页 | “大规模时呢?” | 内存/时间爆炸 |
| SELECT * | “需要所有字段吗?” | 浪费带宽 |
| setInterval无清理 | “什么清除这个?” | 内存泄漏 |
| JSX中的内联对象/函数 | “新引用?” | 不必要的重新渲染 |
| 渲染中的Array.sort() | “缓存了吗?” | 每次渲染都运行 |
何时不必担心
并非所有都需要优化:
- 小数据集:不要为20个项目分页
- 罕见操作:一次性管理员脚本可以慢
- 原型阶段:先让它工作
- 微优化:专注于算法,而不是
for与forEach
此门是关于捕捉明显问题,而不是微优化。