63 lines
1.6 KiB
Markdown
63 lines
1.6 KiB
Markdown
---
|
||
title: "N+1 Query Prevention"
|
||
type: concept
|
||
tags: [database, performance, n+1, postgresql, query-optimization]
|
||
last_updated: 2026-05-01
|
||
---
|
||
|
||
# N+1 Query Prevention
|
||
|
||
## Definition
|
||
|
||
N+1 查询问题是指应用程序在获取一组主对象后,对每个对象分别发起一次额外查询来获取关联数据的反模式。假设获取 N 个用户,每个用户查一次帖子,总共产生 1 + N 次数据库往返。
|
||
|
||
## The Problem
|
||
|
||
```typescript
|
||
// ❌ Bad: N+1 pattern
|
||
const users = await db.query("SELECT * FROM users LIMIT 10");
|
||
for (const user of users) {
|
||
user.posts = await db.query(
|
||
"SELECT * FROM posts WHERE user_id = $1",
|
||
[user.id]
|
||
);
|
||
}
|
||
// 1 + 10 = 11 queries
|
||
```
|
||
|
||
## The Solution
|
||
|
||
```typescript
|
||
// ✅ Good: Single query with aggregation
|
||
const usersWithPosts = await db.query(`
|
||
SELECT
|
||
u.id, u.email, u.name,
|
||
COALESCE(
|
||
json_agg(
|
||
json_build_object('id', p.id, 'title', p.title)
|
||
) FILTER (WHERE p.id IS NOT NULL),
|
||
'[]'
|
||
) as posts
|
||
FROM users u
|
||
LEFT JOIN posts p ON p.user_id = u.id
|
||
GROUP BY u.id
|
||
LIMIT 10
|
||
`);
|
||
// 1 query
|
||
```
|
||
|
||
## Key Techniques
|
||
|
||
- **JOIN + json_agg**:PostgreSQL 下用 `json_agg` 聚合嵌套关联数据
|
||
- **批量查询(Batch Loading)**:拆分为 2-3 次查询(IN 子句分批)
|
||
- **预加载(Eager Loading)**:ORM 的 `include` / `preload` 机制
|
||
- **物化视图**:对稳定结构做预计算
|
||
|
||
## Source
|
||
- [[engineering-database-optimizer]]
|
||
|
||
## Connections
|
||
- [[QueryPlanAnalysis]] — 通过 EXPLAIN ANALYZE 识别 N+1
|
||
- [[engineering-backend-architect]] — 架构设计时需避免 N+1
|
||
- [[engineering-sre]] — N+1 是生产环境性能问题的常见根源
|