ORM 压测对比报告 · Node / Bun / PHP

各框架官方推荐 ORM 的 1U1G 压测,共 16 条引擎×栈组合:

  • Hono → Drizzle(drizzle-orm@0.45.4 + mysql2)——Bun、Node
  • Fastify → Drizzle(fastify@5 + 同款 Drizzle/mysql2)——Bun、Node(Fastify 无内置 ORM,配 Drizzle 与 Hono 对齐)
  • Midway v4 → TypeORM(typeorm@0.3.31 + mysql2)——仅 Node
  • Hyperf 3.2 → hyperf/database(Eloquent + pdo_mysql,Swoole 6.2)——PHP 8.3
  • ThinkPHP 8.1 → think-orm(v4.0 + pdo_mysql,Apache mod_php prefork + opcache)——PHP 8.3
  • Laravel 11.57 → Eloquent/hyf/database(Illuminate\Database + pdo_mysql,Apache mod_php prefork + opcache)——PHP 8.3
  • Laravel 11.57 + Octane → Eloquent(同上代码,laravel/octane 首方常驻内存服务器 + Swoole 6.2,workers=16)——PHP 8.3(Octane 默认关协程,并发=worker 进程数)
  • Yii2 2.0.51 → ActiveRecord(yiisoft/yii2 + pdo_mysql,Apache mod_php prefork + opcache)——PHP 8.3
  • Yii3(yiisoft/app)→ yiisoft/active-record(yiisoft/db-mysql + pdo_mysql,Apache mod_php prefork + opcache)——PHP 8.3
  • Webman 2.1 → Eloquent(webman/database+illuminate/database@11,Workerman 5.1 常驻多进程 + opcache)——PHP 8.3(非协程)
  • Webman 2.1 → Eloquent(同上,Workerman + Swoole 6.2 协程事件循环、连接池,单进程)——PHP 8.3(协程)

同一份 CRUD、同表 hono_bench.items、同后端 MySQL、同 1U1G。表中加粗=该列严格最优(并列不标)。


1. 环境与方法

项 配置
沙箱 WSL2 Ubuntu + podman,--cpus=1 --memory=1g(cgroup v2 硬限)
后端/库 主机 MySQL 8.0.36;隔离库 hono_bench、最小权限账号 bench,主键 1..2048
运行时 Node 22 / Bun latest / PHP 8.3.31+Swoole 6.2(Hyperf)/ PHP 8.3.31+Apache mod_php(ThinkPHP、Laravel、Yii2、Yii3,opcache)/ PHP 8.3.31+Workerman(Webman:非协程 Select 循环、协程 Swoole 6.2.3 循环,opcache)/ PHP 8.3.31+Octane·Swoole(Laravel Octane 常驻内存)
公平性 TS 只编译一次、Bun/Node 跑同字节 JS;规格/依赖/后端一致。Hyperf WORKER_NUM=1;Apache prefork 系(ThinkPHP/Laravel/Yii2/Yii3)预热 16、上限 50;Webman 非协程 count=50、协程 count=1+Swoole;Laravel Octane 与 Apache 轮同一份代码/库,仅把 HTTP 服务器换成 octane:start --server=swoole,workers=16、task-workers=4、max-requests=0(Octane 默认 enable_coroutine=false,并发=worker 进程数;cgroup 不改 /proc,cpuCount() 返 18,故显式钉 worker 免失控);opcache validate_timestamps=0;连接池统一 16
工具/档位 autocannon 7;低 = 1 连接×15s,高 = 50 连接×30s
采样 CPU=cpu.stat 增量÷时长;内存=memory.current(前=空闲、后=峰值)

场景:health(不碰 DB)、get(主键查一行)、post(插入)。16 组合 × 3 场景 × 2 档 = 96 组采样全部 errors/timeouts/non2xx = 0。


2. 结果总表

2.1 较高负载(50 连接 · 30s)— rps(高优)与 p50/p99 ms(低优)

栈 / 引擎 health rps get rps post rps health p50/p99 get p50/p99 post p50/p99
Hono + Drizzle / Bun 103,055 14,660 3,364 0 / 1 2 / 8 13 / 30
Hono + Drizzle / Node 38,938 6,433 3,116 1 / 4 6 / 31 14 / 41
Hono + 裸 mysql2 / Bun 85,225 17,984 3,165 0 / 2 2 / 6 14 / 31
Hono + 裸 mysql2 / Node 39,388 10,478 2,624 1 / 2 3 / 24 17 / 46
Fastify + Drizzle / Bun 72,294 8,123 2,668 0 / 2 5 / 21 15 / 93
Fastify + Drizzle / Node 27,580 2,843 3,086 1 / 6 13 / 57 15 / 38
Hyperf + hyperf/database / PHP·Swoole 12,934 3,935 2,668 3 / 9 12 / 19 18 / 33
Midway + TypeORM / Node 5,201 2,577 1,267 7 / 39 15 / 58 32 / 111
Midway + 裸 mysql2 / Node 5,331 3,377 2,525 7 / 33 10 / 49 16 / 57
ThinkPHP + think-orm / PHP·Apache 1,089 332 377 125 / 502 198 / 1101 200 / 732
Laravel + Eloquent / PHP·Apache 406 199 201 196 / 798 209 / 793 209 / 801
Laravel + Eloquent / PHP·Octane·Swoole(常驻16进程) 512 340 308 97 / 297 105 / 306 181 / 351
Yii2 + ActiveRecord / PHP·Apache 1,616 459 275 192 / 490 198 / 595 199 / 699
Yii3 + active-record / PHP·Apache 275 103 186 196 / 991 409 / 2948 216 / 799
Webman + Eloquent / PHP·Workerman(非协程 50 进程) 49,470 2,109 2,862 0 / 2 2 / 111 9 / 93
Webman + Eloquent / PHP·Swoole(协程 1 进程池16) 25,245 6,710 2,907 1 / 4 7 / 12 17 / 29

2.2 低负载(1 连接 · 15s)— rps(高优)

栈 / 引擎 health get post
Hono + Drizzle / Bun 17,031 2,247 579
Hono + Drizzle / Node 13,278 1,887 506
Hono + 裸 mysql2 / Bun 10,134 2,053 436
Hono + 裸 mysql2 / Node 13,205 2,585 316
Fastify + Drizzle / Bun 12,288 1,628 295
Fastify + Drizzle / Node 9,963 656 292
Hyperf + hyperf/database / PHP·Swoole 7,163 1,000 445
Midway + TypeORM / Node 4,315 1,433 292
Midway + 裸 mysql2 / Node 4,754 1,576 428
ThinkPHP + think-orm / PHP·Apache 1,384 320 204
Laravel + Eloquent / PHP·Apache 466 176 134
Laravel + Eloquent / PHP·Octane·Swoole 980 385 176
Yii2 + ActiveRecord / PHP·Apache 2,415 527 266
Yii3 + active-record / PHP·Apache 349 152 114
Webman + Eloquent / PHP·Workerman(非协程) 17,558 1,801 565
Webman + Eloquent / PHP·Swoole(协程) 12,011 1,606 512

2.3 资源占用(1U1G)— 内存 MB(低优)· 高负载 CPU

栈 / 引擎 空闲 RSS 高负载峰值 RSS 高负载 CPU
Hono + Drizzle / Bun 18 MB 52 MB 0.95
Hono + Drizzle / Node 34 MB 56 MB 0.95
Hono + 裸 mysql2 / Bun 14 MB 45 MB 0.97
Hono + 裸 mysql2 / Node 26 MB 51 MB 0.98
Fastify + Drizzle / Bun 32 MB 81 MB 0.94
Fastify + Drizzle / Node 33 MB 56 MB 0.94
Hyperf + hyperf/database / PHP·Swoole 23 MB 28 MB 0.95
Midway + TypeORM / Node 49 MB 77 MB 0.96
Midway + 裸 mysql2 / Node 38 MB 66 MB 0.98
ThinkPHP + think-orm / PHP·Apache 25 MB 92 MB 0.98
Laravel + Eloquent / PHP·Apache 38 MB 131 MB 0.98
Laravel + Eloquent / PHP·Octane·Swoole(常驻16进程) 372 MB 448 MB 0.95
Yii2 + ActiveRecord / PHP·Apache 24 MB 104 MB 0.98
Yii3 + active-record / PHP·Apache 31 MB 120 MB 0.95
Webman + Eloquent / PHP·Workerman(非协程 50 进程) 75 MB 123 MB 0.98
Webman + Eloquent / PHP·Swoole(协程 1 进程) 22 MB 29 MB 0.95

高负载 CPU 全部 ≈1.0(单核饱和,rps 即天花板)。内存空闲 RSS 普遍偏低,峰值随 process-per-request 的 worker 数攀升——Apache prefork 系(ThinkPHP/Laravel/Yii2/Yii3)峰值 92131MB 最高;常驻单/少进程系最省(Hyperf 28、Webman 协程 29、Node/Bun 系 4581)。Laravel Octane 例外:虽常驻,但 Octane 每个 Swoole worker 各自 boot 一份完整 Laravel(非纯 COW 共享),16 worker 空闲即 372MB、峰值 448MB,是全场最高——用 worker 进程数换并发,代价是内存。


3. 关键结论

① ORM 开销(同引擎 mysql2 → 推荐 ORM,高负载 rps):

对照 health get post
Hono/Node → Drizzle −1% −39% +19%*
Hono/Bun → Drizzle +21%* −18% +6%*
Midway/Node → TypeORM −2% −24% −50%

health 持平(ORM 只作用 SQL 编组/映射);读慢 18~39%;TypeORM 写 create()+save() 最重(−50%)。* 受 MySQL/池抖动,方向”持平或略降”。

② 引擎 Bun vs Node:接不接 ORM 结论不变——Bun 单核吞吐约 Node 的 2.3~2.6×、常驻内存约一半。

③ 框架层光谱(同为 Node+Drizzle,高负载 health):Hono 38,938 > Fastify 27,580 > Midway 5,201。极简路由层差在走 DB 后被 ORM+MySQL 掩盖(Fastify get/post ≈ Hono)。

④ Hyperf(Swoole 单 worker):读写全面优于 Midway+TypeORM(get 3,935 / post 2,668 vs 2,577 / 1,267);峰值内存 28MB 全场最低。

⑤ Webman:协程 vs 非协程(同代码/镜像,仅事件循环不同):

指标(高负载) 非协程(Workerman 50 进程) 协程(Swoole 1 进程·池16)
health rps 49,470 25,245
get rps 2,109 6,710
空闲/峰值内存 75 / 123 MB 22 / 29 MB

CPU 型 health 非协程胜(多进程摊薄、无协程切换);I/O 型 get 协程胜(16 连接池重叠 MySQL 等待,p99 111→12ms、内存仅 1/3)。纯计算走非协程,DB 密集走协程。

⑥ PHP 传统全栈框架(Apache prefork:ThinkPHP / Laravel / Yii2 / Yii3):process-per-request + 每请求框架引导把单核打死(cores ≈1),加连接不涨=纯 CPU 天花板,读写全面落后于常驻协程/事件循环系。内部次序 Yii2(health 1,616 / get 459 / post 275)> ThinkPHP(1,089/332/377)> Laravel(406/199/201)> Yii3(275/103/186,全场 16 栈最低,因每请求从零重建 DI 容器+AR 读路径对象水合)。

⑦ 常驻内存的收益与边界(同代码 Apache → Octane·Swoole):Laravel 只把 HTTP 服务器换成 Octane 常驻,health 406→512、get 199→340、post 201→308(约 1.5~1.7×,省掉每请求框架引导),但远不及把应用改写为协程/连接池的 Hyperf(get 3,935)与 Webman 协程(get 6,710)。根因:Octane 默认 enable_coroutine=false,并发=worker 进程数,1 核上 16 worker 对 50 连接仍要排队(high p99 ~300ms);且每个 worker 各自 boot 完整 Laravel,内存 372/448MB 为全场最高。结论:Octane 是”老 Laravel 项目的低成本提速”,但要 PHP 侧 I/O 吞吐与低内存,仍需为协程重写(Hyperf/Webman 协程)。Yii3 无同级的首方常驻 runner,故本轮只有 Laravel 有 Octane 常驻数据。