导致我们的 API 变慢至爬行的 N+1 插入循环
发布: (2026年1月3日 GMT+8 04:06)
3 min read
原文: Dev.to
Source: Dev.to

问题
// ❌ The pattern that killed our performance
async function importUsers(users) {
for (const user of users) {
await pool.query(
'INSERT INTO users (name, email) VALUES ($1, $2)',
[user.name, user.email]
);
}
}
对于 1 000 条用户:
- 1 000 次往返数据库
- 每个查询约 50 ms
- ≈ 50 秒 总计
为什么重要
| 行数 | N+1 时间 | 批量时间 | 加速比 |
|---|---|---|---|
| 100 | 5 s | 50 ms | 100× |
| 1 000 | 50 s | 100 ms | 500× |
| 10 000 | 500 s | 500 ms | 1 000× |
正确的模式:批量插入
// ✅ Single query, any number of rows
async function importUsers(users) {
const values = users
.map((_, i) => `($${i * 2 + 1}, $${i * 2 + 2})`)
.join(', ');
const params = users.flatMap(u => [u.name, u.email]);
await pool.query(`INSERT INTO users (name, email) VALUES ${values}`, params);
}
更佳方案:使用 unnest()
// ✅ PostgreSQL unnest pattern
async function importUsers(users) {
await pool.query(
`INSERT INTO users (name, email)
SELECT * FROM unnest($1::text[], $2::text[])`,
[users.map(u => u.name), users.map(u => u.email)]
);
}
规则:pg/no-batch-insert-loop
此模式由 eslint-plugin-pg 中的 pg/no-batch-insert-loop 规则检测。
让 ESLint 捕获它
npm install --save-dev eslint-plugin-pg
使用推荐配置(所有规则)
import pg from 'eslint-plugin-pg';
export default [pg.configs.recommended];
只启用此规则
import pg from 'eslint-plugin-pg';
export default [
{
plugins: { pg },
rules: {
'pg/no-batch-insert-loop': 'error',
},
},
];
你会看到的结果
当检测到 N+1 循环时,ESLint 会报告:
src/import.ts
5:3 error ⚡ CWE-1049 | Database query loop detected. | HIGH
Fix: Batch queries using arrays and "UNNEST" or a single batched INSERT. |
https://use-the-index-luke.com/sql/joins/nested-loops-join-n1-problem
检测模式
pg/no-batch-insert-loop 规则会捕获:
query('INSERT...')出现在for、for…of、for…in循环中query('INSERT...')出现在while和do…while循环中query('INSERT...')出现在forEach、map、reduce、filter回调中query('UPDATE...')出现在任何循环结构中query('DELETE...')出现在任何循环结构中
其他批量模式
批量更新
// ✅ Update with unnest
await pool.query(
`
UPDATE users SET status = data.status
FROM unnest($1::int[], $2::text[]) AS data(id, status)
WHERE users.id = data.id
`,
[ids, statuses]
);
批量删除
// ✅ Delete with ANY
await pool.query('DELETE FROM users WHERE id = ANY($1)', [userIds]);
快速安装
npm install --save-dev eslint-plugin-pg
import pg from 'eslint-plugin-pg';
export default [pg.configs.recommended];
将 50 秒的导入时间压缩至约 100 ms。
📦 npm: eslint-plugin-pg
📖 规则文档: pg/no-batch-insert-loop
⭐ GitHub: https://github.com/ofri-peretz/eslint