JOIN FETCH 可能比 N+1 更慢:可复现的 Doctrine 基准测试(每实体 1 行的 JSON 聚合)
发布: (2025年12月18日 GMT+8 13:54)
2 min read
原文: Dev.to
Source: Dev.to
实际问题:多个 OneToMany JOIN 导致行数爆炸
对多个 OneToMany 关联进行 JOIN 会使 SQL 结果集成倍增长(笛卡尔积)。
示例
- 3 张图片 × 5 条评论 = 每个产品 15 行
- 对于 2000 个产品 → 约 30,000 行 从数据库传输并处理
Doctrine 的身份映射在 PHP 中隐藏了重复项,但数据库仍然返回了乘积后的行集。这即使在查询次数很少的情况下也会严重影响性能。
链接(包 + 可复现的基准)
- 基准仓库: https://github.com/rgalstyan/doctrine-aggregated-queries-benchmark
- Bundle 仓库: (原文中省略链接)
可复现的基准(Symfony + PostgreSQL)
我发布了一个独立的 Symfony 基准项目,包含 fixtures 和 CLI 命令,任何人都可以在本地复现结果:
https://github.com/rgalstyan/doctrine-aggregated-queries-benchmark
示例运行(limit = 2000)
注意:计时取决于你的机器/数据库/缓存状态。关键是趋势。
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
PRODUCTS PERFORMANCE TEST
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Dataset size: 2000 products
TRADITIONAL DOCTRINE (2000 records)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Time: 327.73ms
Memory: 44559.6 KB (43.52 MB)
Queries: 43
Result: 2000 Product entities
DOCTRINE JOIN FETCH (entities) (2000 records)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Time: 816.37ms
Memory: 39795.9 KB (38.86 MB)
Queries: 2
DB rows: ~30000 (Cartesian product in SQL)
Result: 2000 Product entities
SIMPLE JOINS (naive) (2000 records)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Time: 103.14ms
Memory: 11659.4 KB (11.39 MB)
Queries: 1
DB rows: 30000 (Cartesian product!)
Result: 2000 products (after deduplication)
AGGREGATED QUERIES (2000 records)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Time: 66.18ms
Memory: 13989.1 KB (13.66 MB)
Queries: 1
DB rows: 2000
Result: 2000 products (arrays)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
COMPARISON
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
对比表
| 方法 | 返回 | 时间 (ms) | 内存 (KB) | 查询次数 | 数据库行数 | 产品数 |
|---|---|---|---|---|---|---|
| 传统 Doctrine | 实体 | 327.73 | 44559.6 | 43 | N/A | 2000 |
| Doctrine JOIN fetch | 实体 | 816.37 | 39795.9 | 2 | 30000 | 2000 |
| 简单 JOIN(朴素) | 数组 | 103.14 | 11659.4 | 1 | 30000 | 2000 |
| JSON 聚合 | 数组 | 66.18 | 13989.1 | 1 | 2000 | 2000 |