n-plus-one-prevention
Use when fetching related data in loops. Use when seeing multiple queries for one request. Use when database is slow on list endpoints.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
Use when fetching related data in loops. Use when seeing multiple queries for one request. Use when database is slow on list endpoints.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
基于 SOC 职业分类
Use when writing tests. Use when test structure is unclear. Use when arrange/act/assert phases are mixed.
Use when designing or modifying APIs. Use when adding breaking changes. Use when clients depend on API stability.
Use when implementing authentication. Use when storing passwords. Use when asked to store credentials insecurely.
Use when same data is fetched repeatedly. Use when database queries are slow. Use when implementing caching without invalidation strategy.
Use when tempted to use class inheritance. Use when creating class hierarchies. Use when subclass needs only some parent behavior.
Use when acquiring multiple locks. Use when operations wait for each other. Use when system hangs without crashing.
| name | n-plus-one-prevention |
| description | Use when fetching related data in loops. Use when seeing multiple queries for one request. Use when database is slow on list endpoints. |
Never query in a loop. Fetch related data in a single query.
N+1 is when you fetch N items, then make N more queries to get related data. It's the most common database performance killer.
NEVER put a database query inside a loop.
No exceptions:
If you query inside a loop, STOP:
// ❌ VIOLATION: N+1 queries
const orders = await Order.findAll(); // 1 query
const ordersWithCustomers = await Promise.all(
orders.map(async (order) => {
// N queries (one per order)
const customer = await Customer.findByPk(order.customerId);
return { ...order, customerName: customer.name };
})
);
// Total: 1 + N queries
For 100 orders = 101 database queries!
// ✅ CORRECT: Single query with JOIN
// Sequelize
const orders = await Order.findAll({
include: [{ model: Customer, attributes: ['name'] }]
});
// 1 query with JOIN
// Prisma
const orders = await prisma.order.findMany({
include: { customer: { select: { name: true } } }
});
// TypeORM
const orders = await orderRepository.find({
relations: ['customer']
});
// Raw SQL
const orders = await db.query(`
SELECT o.*, c.name as customer_name
FROM orders o
JOIN customers c ON o.customer_id = c.id
`);
// ❌ N+1
posts.map(post => await User.findById(post.authorId));
// ✅ Eager load
Post.findAll({ include: [User] });
// ❌ N+1
users.map(user => await Order.count({ where: { userId: user.id } }));
// ✅ Subquery or GROUP BY
User.findAll({
attributes: {
include: [[sequelize.fn('COUNT', sequelize.col('orders.id')), 'orderCount']]
},
include: [{ model: Order, attributes: [] }],
group: ['User.id']
});
// ❌ N+1 (multiple)
orders.map(order => {
await Customer.findByPk(order.customerId);
await Product.findAll({ where: { orderId: order.id } });
});
// ✅ Eager load all
Order.findAll({
include: [Customer, Product]
});
// Log query count per request
let queryCount = 0;
db.on('query', () => queryCount++);
app.use((req, res, next) => {
queryCount = 0;
res.on('finish', () => {
if (queryCount > 10) {
console.warn(`N+1 alert: ${req.path} made ${queryCount} queries`);
}
});
next();
});
Pressure: "We only have 10 orders"
Response: 10 becomes 100 becomes 10,000. Fix it now.
Action: Always use eager loading regardless of current data size.
Pressure: "Each query takes 1ms"
Response: 1ms × 1000 = 1 second. Network overhead adds more.
Action: One 5ms query beats 1000 × 1ms queries.
Pressure: "Redis will cache the results"
Response: Cache misses still hit the DB. First requests are slow. Cache adds complexity.
Action: Fix the query. Cache if still needed.
Pressure: "Looping is easier to understand"
Response: Simple code that's 100x slower isn't simple.
Action: Learn your ORM's eager loading syntax.
await inside .map() or .forEach()All of these mean: Refactor to eager loading.
| N+1 (Bad) | Eager Loading (Good) |
|---|---|
| Loop + query | JOIN / include |
| 1 + N queries | 1 query |
| O(N) round trips | O(1) round trips |
| Slower with more data | Constant query count |
| Excuse | Reality |
|---|---|
| "Few items" | Data grows. Fix now. |
| "Fast query" | N slow > 1 medium. |
| "We'll cache" | Cache doesn't fix bad queries. |
| "It's simpler" | Slow isn't simple. |
| "ORM handles it" | ORMs default to lazy loading. |
One query for the list. One query for related data. Never query in a loop.
Use eager loading (include/join) to fetch related data. Watch query counts. Any query inside a loop is a bug waiting to scale.