go college3日目に参加してまいりました。今回はデータベース周りのことを勉強しました。そこでgormと素のSQLクエリで実装した際に、どのくらいパフォーマンスに差が生まれるのかというところが気になりました。
前提
今回は単純にユースケース毎にGORMとSQLでの違いを図りたいだけなので、
- スループット、
- 一回の処理当たりの時間比較
- 単位時間当たり
を比較をすることにしました。
両者の間に差が生まれるのはなぜか?の部分は今回は考えないことにします。
仮説
1:単発クエリでは差が出る
コードを見たところgormではCreate()でも複数処理を行っています
なので単発では処理時間に大きく差が出ると予想しました
2:大量フェッチ,高負荷では差が縮む
大量フェッチとなるとボトルネックはDB側、Disk I/oなどになり、支配要因が変わると考えました
調べたユースケース
- GetUserByID: usersをidで1件
- ListPostsByUser: user_idでpostsをcreated_at descでlimit 20
- ListPostsWithCommentsCount: postsとcommentsを集計(join+group)してlimit 50
- CreatePostWith2CommentsTx: 1Txでpost insert + comment 2件 insert
- UpdatePostStatus: status更新(where id=?)
検証結果
2026/03/03 20:47:34 benchmark initialized: users=2000 posts=20042
goos: linux
goarch: amd64
pkg: benchmark
cpu: AMD Ryzen 5 7530U with Radeon Graphics
BenchmarkGetUserByID/raw_sql-12 876 1310943 ns/op 762.8 ops/s 1515 B/op 39 allocs/op
BenchmarkGetUserByID/gorm-12 925 1256619 ns/op 795.8 ops/s 4961 B/op 84 allocs/op
BenchmarkListPostsByUser/raw_sql-12 853 1421061 ns/op 703.7 ops/s 4110 B/op 156 allocs/op
BenchmarkListPostsByUser/gorm-12 862 1331018 ns/op 751.3 ops/s 11058 B/op 276 allocs/op
BenchmarkListPostsWithCommentsCount/raw_sql-12 6 184076876 ns/op 5.433 ops/s 10920 B/op 514 allocs/op
BenchmarkListPostsWithCommentsCount/gorm-12 6 178349810 ns/op 5.607 ops/s 41466 B/op 932 allocs/op
BenchmarkCreatePostWith2CommentsTx/raw_sql-12 151 7647868 ns/op 130.8 ops/s 1870 B/op 44 allocs/op
BenchmarkCreatePostWith2CommentsTx/gorm-12 140 7826742 ns/op 127.8 ops/s 13442 B/op 155 allocs/op
BenchmarkUpdatePostStatus/raw_sql-12 666 1868220 ns/op 535.3 ops/s 359 B/op 13 allocs/op
BenchmarkUpdatePostStatus/gorm-12 207 6829219 ns/op 146.4 ops/s 6720 B/op 80 allocs/op
PASS
ok benchmark 19.55
1.GetUserByID
予想通り、単発クエリでは差が出ました。
特にスループットを見るとrawSQLの方が低く、やはりgORMの内部処理分だけオーバーヘッドが存在していると考えられます。またallocs/opを見ると、
- raw SQL: 39 allocs/op
- GORM: 84 allocs/op
となっており、GORM側ではメモリアロケーションが約2倍発生していました。
今回の結果から、単純に1件取得するようなケースでは、gORMの内部処理の分だけコストが追加されることが確認できました。
しかしns/op自体は大きな差にはならなかったため、実際にはDBアクセス時間の影響も大きいと感じました。
2. ListPostsByUser
このケースでは予想とは少し異なり、大きな差は見られませんでした。
スループット・処理時間ともにほぼ同程度で、むしろ GORM の方がわずかに良い結果になっています。
大量データ取得になると、
- DB 側の処理
- データ転送
- ソート処理
などの影響が強くなり、アプリケーション側の実装差が目立たなくなると考えられます。
一方でallocs/opは依然としてGORMの方が多く、内部で追加処理が行われていることは確認できました。
3. ListPostsWithCommentsCount
JOINとGROUP BYを含む集計クエリでは、両者の差はほぼありませんでした。
処理時間はどちらも約180ms程度となり、gormとrawSQLの違いはほとんど見られませんでした。
このケースでは明らかにボトルネックがDB側にありGo側の処理差は結果にほぼ影響しないと考えられます。
ただしメモリ使用量を見ると、GORMの方がallocs/opが多くなっており、内部処理のコスト自体は存在していることが分かります。
4. CreatePostWith2CommentsTx
トランザクションを含むinsert処理では、両者の差はかなり小さくなりました。
処理時間はほぼ同じであり、トランザクションコミットやDB書き込み処理がボトルネックになっていると考えれます。
そのためwrite処理ではgormを使用してもパフォーマンス差は大きくならないと感じました。
ただしallocs/opはGORMの方が多く、アプリケーション側の処理量は増えていることが確認できます。
5. UpdatePostStatus
今回もっとも差が出たのがこのケースでした。
- raw SQL: 約1.8ms
- GORM: 約6.8ms
となり、GORMの方が大きく時間がかかる結果となりました。
単純な更新処理ではDB側の処理が軽いため、gormの内部処理コストがそのまま処理時間の差として現れたと考えられます。
まとめ
今回の検証結果は
- 単純な1件取得や更新では差が出やすい
- データ量が増えるほど差は小さくなる
- JOINやトランザクションを含む処理ではほぼ差がなくなる
となりました。
ORM の影響が大きく出るのはアプリケーション側の処理時間が支配的なケースであり、DB 処理が重くなるほど差は小さくなるという結果になりました。
感想
今回実際に計測してみて、gormの内部処理のコストはユースケースによって影響度が変わることが分かりました。
特に単純な更新や高頻度で呼ばれる処理では素のSQLを使うメリットがありそうですが、複雑なクエリやトランザクションを伴う処理ではGORMを使っても大きな問題はないと感じました。
使用したコード: