Go言語の SQLite ドライバー (mattn/go-sqlite3
と modernc.org/sqlite
) の SELECT パフォーマンスを比較検証します。
- 対象テーブル:
monster
(1000 レコード)id
(INTEGER PRIMARY KEY)name
(TEXT)hp
(INTEGER)attack
(INTEGER)
- 検証クエリ:
SELECT id, name, hp, attack FROM monster WHERE id = ?
(SelectByID)SELECT id, name, hp, attack FROM monster
(SelectAll)
- 実行環境:
- OS: darwin
- Arch: arm64
- CPU: Apple M3 Max
- Go Version: (1.23.1)
- ベンチマーク設定:
-benchmem -count=5
以下の設定パターンを、3つの主要なGo SQLiteドライバー (mattn/go-sqlite3
, modernc.org/sqlite
, glebarez/go-sqlite
) で比較します。
- P1: Default: ドライバーのデフォルト設定 (cache=shared, read only)
- P2: P1 + WAL: WAL ジャーナルモード有効化 (cache=shared, read only)
- P3: P2 + Immutable: P2 の設定に
_immutable=1
を追加 - P4: P3 + Mmap: P3 の設定に
_pragma=mmap_size(256MB)
を追加 - P5: P2 + Mmap + OpenMode最適化: P2 に
_pragma=mmap_size(256MB)
とmode=ro+shared+nomutex
を追加(Openフラグ最適化の効果検証) - P6: P5 + PRAGMA最適化①: P5 に
_pragma=locking_mode(EXCLUSIVE)
と_pragma=synchronous(OFF)
を追加(ファイルロックと同期削減による速度向上) - P7: P6 + PRAGMA最適化②: P6 に
_pragma=temp_store(MEMORY)
と_pragma=cache_size(10000)
を追加(キャッシュメモリ効果測定) - P8: P7 + ANALYZE: P7 に
_pragma=analysis_limit(1000)
と_pragma=optimize
を追加(統計情報によるクエリプラン最適化効果) - P9: メモリDB: ファイルDBからデータをメモリにコピーして完全にインメモリで実行 (
_journal_mode=OFF
,_synchronous=OFF
,_cache_size=10000
,_locking_mode=EXCLUSIVE
)
P9(メモリDB)の設定では、以下のステップでベンチマークを実行します:
- ファイルDBに接続
- メモリ上にDBを作成
- ファイルDBをATTACH
- メモリにテーブルをコピー
- ファイルDBをDETACH
- 性能向上のためのPRAGMA設定
- メモリ上のデータに対してクエリを実行
// メモリDBの作成と設定例
fileDB, err := sql.Open("sqlite3", "file:benchmark.db?mode=ro&cache=shared")
defer fileDB.Close()
memDB, err := sql.Open("sqlite3", "file::memory:?cache=shared")
_, err = memDB.Exec("ATTACH DATABASE 'benchmark.db' AS filedb")
_, err = memDB.Exec("CREATE TABLE monster AS SELECT * FROM filedb.monster")
_, err = memDB.Exec("DETACH DATABASE filedb")
// PRAGMA設定
_, err = memDB.Exec("PRAGMA journal_mode=OFF")
_, err = memDB.Exec("PRAGMA synchronous=OFF")
_, err = memDB.Exec("PRAGMA cache_size=10000")
_, err = memDB.Exec("PRAGMA locking_mode=EXCLUSIVE")
各パターンについて、ns/op
(オペレーションあたりのナノ秒、小さいほど速い) の平均値を記載します。
(B/op: オペレーションあたりのメモリ確保量, allocs/op: オペレーションあたりのメモリアロケーション回数)
SelectByID (100並列/1000並列)
Pattern | Driver | Options | Avg ns/op (100) | Avg B/op (100) | Avg allocs/op (100) | Avg ns/op (1000) | Avg B/op (1000) | Avg allocs/op (1000) |
---|---|---|---|---|---|---|---|---|
P1 | mattn/go-sqlite3 | cache=shared, read only | 23,715 | 967 | 36.2 | 33,601 | 1,246 | 42.6 |
P1 | modernc.org/sqlite | cache=shared, read only | 16,898.2 | 697.2 | 25 | 49,398 | 1,619.4 | 38.8 |
P1 | glebarez/go-sqlite | cache=shared, read only | 16,299 | 770 | 31 | 258,395.8 | 2,840.8 | 75.8 |
P2 | mattn/go-sqlite3 | P1 + WAL | 21,166 | 958.6 | 36.8 | 30,177 | 1,174.2 | 42.4 |
P2 | modernc.org/sqlite | P1 + WAL | 14,809.4 | 698.4 | 25.2 | 62,188.6 | 1,865.4 | 44.2 |
P2 | glebarez/go-sqlite | P1 + WAL | 16,395.4 | 765 | 31 | 73,066.2 | 2,696 | 73.8 |
P3 | mattn/go-sqlite3 | P2 + Immutable | 20,113.6 | 954.2 | 36.6 | 39,436.6 | 1,265.8 | 45.2 |
P3 | modernc.org/sqlite | P2 + Immutable | 16,038.6 | 706.2 | 26 | 56,366 | 1,764.4 | 43.8 |
P3 | glebarez/go-sqlite | P2 + Immutable | 17,325.4 | 767.4 | 31 | 154,430.8 | 2,819.8 | 77.4 |
P4 | mattn/go-sqlite3 | P3 + Mmap | 19,586.2 | 953.8 | 36.4 | 44,945 | 1,329.8 | 47.2 |
P4 | modernc.org/sqlite | P3 + Mmap | 17,059.4 | 702.8 | 26 | 70,149.2 | 1,997.8 | 51.2 |
P4 | glebarez/go-sqlite | P3 + Mmap | 16,563.2 | 763.2 | 31 | 155,914.6 | 2,861.6 | 81.6 |
P5 | mattn/go-sqlite3 | P2 + Mmap + OpenMode最適化 | 20,382 | 959 | 36.6 | 36,747 | 1,300 | 45.2 |
P5 | modernc.org/sqlite | P2 + Mmap + OpenMode最適化 | 17,013 | 711 | 26 | 64,518 | 2,153 | 58.4 |
P6 | mattn/go-sqlite3 | P5 + PRAGMA最適化① | 21,585 | 959 | 36.6 | 30,034 | 1,180 | 42.8 |
P6 | modernc.org/sqlite | P5 + PRAGMA最適化① | 19,249 | 740 | 26.4 | 79,329 | 2,939 | 77.2 |
P7 | mattn/go-sqlite3 | P6 + PRAGMA最適化② | 19,241 | 963 | 36.6 | 57,234 | 1,421 | 48.8 |
P7 | modernc.org/sqlite | P6 + PRAGMA最適化② | 13,939 | 723 | 26 | 70,927 | 2,770 | 78.6 |
P8 | mattn/go-sqlite3 | P7 + ANALYZE | 18,885 | 962 | 36.6 | 82,928 | 1,627 | 54.6 |
P8 | modernc.org/sqlite | P7 + ANALYZE | 14,525 | 740 | 27 | 82,923 | 3,794 | 97 |
P9 | mattn/go-sqlite3 | メモリDB | 19,534 | 964 | 36.6 | 46,748 | 1,405 | 47.4 |
P9 | modernc.org/sqlite | メモリDB | 15,940 | 706 | 25 | 16,792 | 1,060 | 35 |
P9 | glebarez/go-sqlite | メモリDB | 16,544 | 740 | 31 | 154,236 | 1,838 | 57 |
注: 上記の Avg ns/op
, Avg B/op
, Avg allocs/op
は5回の実行結果の単純平均です。
SelectAll (100並列/1000並列)
Pattern | Driver | Options | Avg ns/op (100) | Avg B/op (100) | Avg allocs/op (100) | Avg ns/op (1000) | Avg B/op (1000) | Avg allocs/op (1000) |
---|---|---|---|---|---|---|---|---|
P1 | mattn/go-sqlite3 | cache=shared, read only | 1,150,724 | 105,552 | 6,950 | 1,073,780 | 105,837 | 6,969 |
P1 | modernc.org/sqlite | cache=shared, read only | 681,935 | 122,911 | 8,915 | 605,996 | 123,072 | 8,935 |
P1 | glebarez/go-sqlite | cache=shared, read only | 2,482,862 | 137,447 | 9,959 | 2,462,973 | 139,864 | 10,022 |
P2 | mattn/go-sqlite3 | P1 + WAL | 1,080,779 | 105,131 | 6,953 | 1,058,356 | 105,788 | 6,971 |
P2 | modernc.org/sqlite | P1 + WAL | 609,229 | 122,085 | 8,916 | 612,405 | 122,975 | 8,935 |
P2 | glebarez/go-sqlite | P1 + WAL | 2,642,251 | 137,254 | 9,961 | 2,463,109 | 139,515 | 10,017 |
P3 | mattn/go-sqlite3 | P2 + Immutable | 1,088,846 | 105,062 | 6,953 | 1,109,677 | 105,888 | 6,973 |
P3 | modernc.org/sqlite | P2 + Immutable | 613,335 | 122,175 | 8,918 | 601,144 | 122,972 | 8,937 |
P3 | glebarez/go-sqlite | P2 + Immutable | 2,463,061 | 137,314 | 9,962 | 2,426,073 | 139,548 | 10,019 |
P4 | mattn/go-sqlite3 | P3 + Mmap | 1,080,392 | 105,104 | 6,954 | 1,115,784 | 105,808 | 6,973 |
P4 | modernc.org/sqlite | P3 + Mmap | 615,536 | 122,184 | 8,922 | 589,299 | 123,149 | 8,944 |
P4 | glebarez/go-sqlite | P3 + Mmap | 2,848,841 | 137,837 | 9,968 | 2,771,080 | 139,769 | 10,028 |
P5 | mattn/go-sqlite3 | P2 + Mmap + OpenMode最適化 | 1,101,117 | 106,211 | 6,955 | 1,001,443 | 105,808 | 6,973 |
P5 | modernc.org/sqlite | P2 + Mmap + OpenMode最適化 | 671,349 | 122,965 | 8,933 | 599,452 | 123,953 | 8,959 |
P6 | mattn/go-sqlite3 | P5 + PRAGMA最適化① | 1,068,819 | 105,148 | 6,955 | 1,050,044 | 105,863 | 6,974 |
P6 | modernc.org/sqlite | P5 + PRAGMA最適化① | 691,863 | 122,895 | 8,942 | 638,836 | 124,212 | 8,973 |
P7 | mattn/go-sqlite3 | P6 + PRAGMA最適化② | 1,002,754 | 105,229 | 6,955 | 1,124,167 | 105,971 | 6,974 |
P7 | modernc.org/sqlite | P6 + PRAGMA最適化② | 738,011 | 123,211 | 8,952 | 663,591 | 124,375 | 8,984 |
P8 | mattn/go-sqlite3 | P7 + ANALYZE | 1,041,525 | 105,305 | 6,957 | 1,091,693 | 105,979 | 6,974 |
P8 | modernc.org/sqlite | P7 + ANALYZE | 739,762 | 123,871 | 8,965 | 647,132 | 125,252 | 8,999 |
P9 | mattn/go-sqlite3 | メモリDB | 915,191 | 104,893 | 6,954 | 1,033,372 | 105,836 | 6,978 |
P9 | modernc.org/sqlite | メモリDB | 609,228 | 122,134 | 8,929 | 570,468 | 123,132 | 8,954 |
P9 | glebarez/go-sqlite | メモリDB | 2,562,978 | 136,108 | 9,941 | 2,428,093 | 138,409 | 9,999 |
注: 上記の Avg ns/op
, Avg B/op
, Avg allocs/op
は5回の実行結果の単純平均です。
SelectByID
は、mattn/go-sqlite3 が最も高速(約4.9μs)、modernc.org/sqlite が約6.0μs、glebarez/go-sqlite が約6.5μs。SelectAll
は、modernc.org/sqlite が最も高速(約0.43ms)、次いで glebarez/go-sqlite (約0.55ms)、mattn/go-sqlite3 は約0.66ms。- コネクションプーリングや WAL、Immutable、Mmap オプションの効果はドライバによってわずかで、modernc.org/sqlite と glebarez/go-sqlite はほとんど差が見られなかった。
- P5〜P8の最適化設定については、modernc.org/sqliteでは特に向上が見られず、一部のケースでは逆にパフォーマンスが低下している(P7, P8)。
- mattn/go-sqlite3では、P6〜P7で若干の性能向上が見られるが、高並列時(1000並列)では効果が薄く、安定していない。
- glebarez/go-sqlite では、P5以降の最適化設定でout of memoryエラーが発生したため、これらの設定は現実的に使用できない可能性がある。
- 全体として、並列数が増えると(100→1000)、modernc.org/sqliteの優位性が保たれるが、性能差は縮小する傾向がある。
- PRAGMA設定の最適化はクエリ実行に追加のオーバーヘッドをもたらす可能性があり、これがP5〜P8での性能向上の制限となっている可能性がある。
-
全体的な傾向: メモリDBは通常のディスクベースのDBと比較して優れたパフォーマンスを示しますが、ドライバーごとに特性が大きく異なります。
-
ドライバー別パフォーマンス:
-
modernc.org/sqlite: メモリDBで最も優れたパフォーマンスを発揮します。特に1000並列実行時のSelectByIDでは劇的な速度向上(約16.8ms)を達成し、他のドライバーを大きく引き離します。SelectAllでも570ms程度と安定して高速です。Pure Goの実装でありながら、C実装のmattnドライバーよりも高並列環境で優れたスケーラビリティを示しています。
-
mattn/go-sqlite3: 単一スレッド実行ではメモリDBで最速ですが、並列数が増えるにつれてパフォーマンスが低下します。1000並列でのSelectByIDは約47msと中程度のパフォーマンスです。SelectAllは約1秒と比較的安定していますが、modernc.org/sqliteより40%ほど遅いです。
-
glebarez/go-sqlite: 単一または少数並列では良好なパフォーマンスですが、1000並列では著しく性能が低下します(SelectByIDで約154ms、SelectAllで約2.4秒)。高並列アクセスには向いていない可能性があります。
-
-
スケーラビリティ:
- modernc.org/sqliteが最も高いスケーラビリティを示し、並列数が1000に増えてもパフォーマンスが大きく低下しません。
- mattn/go-sqlite3は中程度のスケーラビリティで、1000並列では性能が2倍程度低下します。
- glebarez/go-sqliteは高並列環境での性能低下が最も著しく、スケーラビリティに課題があります。
-
メモリ使用量とアロケーション:
- modernc.org/sqliteは比較的少ないメモリアロケーション回数(35回前後)を示し、効率的なメモリ使用を実現しています。
- mattn/go-sqlite3もメモリ使用量は適切ですが、B/opの値はmodernc.org/sqliteよりやや大きいです。
- glebarez/go-sqliteは他の2つと比較して最もメモリ使用量が多く、特に高並列環境ではその差が顕著になります。
-
リアルワールドへの適用:
- 一時的なデータ処理や高速なキャッシュが必要なアプリケーションでは、modernc.org/sqliteのメモリDBが最適な選択肢となります。
- 単一スレッドの処理が主体のアプリケーションではmattn/go-sqlite3も有効ですが、高並列処理が必要な場合はmodernc.org/sqliteが明らかに優位です。
- すべてのドライバーにおいて、メモリDBはディスクベースのDBと比較して性能向上が見られますが、その向上率はmodernc.org/sqliteで最も顕著です(1000並列SelectByIDで約3倍の高速化)。
-
結論: メモリDBを使用する場合、modernc.org/sqliteが最も優れたパフォーマンスとスケーラビリティを提供します。特に高並列環境や大量のSQLite接続を必要とするアプリケーションでは、modernc.org/sqliteの使用を強く推奨します。
各ドライバーのベンチマークを実行するには、対応するディレクトリに移動し、go test
コマンドを実行します。
事前に go mod tidy
を実行して依存関係を解決してください。
データベースファイル (benchmark.db
) はプロジェクトルートに生成されます。
mattn/go-sqlite3
:cd mattn_bench go mod tidy go test -bench=. -benchmem -count=5 > ../mattn_results.txt cd ..
modernc.org/sqlite
:cd modernc_bench go mod tidy go test -bench=. -benchmem -count=5 > ../modernc_results.txt cd ..
glebarez/go-sqlite
:cd glebarez_bench go mod tidy go test -bench=. -benchmem -count=5 > ../glebarez_results.txt cd ..
各ドライバーについて、メモリDB(P9)のベンチマークだけを実行する場合は以下のコマンドを使用します:
mattn/go-sqlite3
:cd mattn_bench go test -run=none -bench=MemoryDB -benchmem -count=5 > ../mattn_memory_results.txt cd ..
modernc.org/sqlite
:cd modernc_bench go test -run=none -bench=MemoryDB -benchmem -count=5 > ../modernc_memory_results.txt cd ..
glebarez/go-sqlite
:cd glebarez_bench go test -run=none -bench=MemoryDB -benchmem -count=5 > ../glebarez_memory_results.txt cd ..