---
id: 134c150de56c188c925476664026a1ff
author: Lemon Mint
title: 'gosuda.org/sqlite3: 共有メモリIPCを介したCGO_ENABLED=0対応のdatabase/sqlドライバ'
description: CGO_ENABLED=0 を用いた Go 向けのドロップイン型 database/sql SQLite 3.54.1 ドライバーであり、HQQ 共有メモリによるワーカー分離、64 KiB 単位のバッチ行プリフェッチ、SLSA 認証済みバイナリを特徴とします。
language: ja
date: 2026-10-18T14:02:16.803040007Z
path: /sqlite3
go_package: gosuda.org/sqlite3
go_repourl: https://github.com/gosuda/hqq-sqlite3.git
category: database
---

`gosuda.org/sqlite3` は、`CGO_ENABLED=0` で動作する Go 向けの `database/sql` ドライバであり、アップストリームの C 言語版 SQLite `3.54.1` アマルガメーションを、[HQQ](https://gosuda.org/hqq) の共有メモリリングを介して分離されたワーカープロセス内で実行します。標準の `"sqlite3"` ドライバ名で登録され、`github.com/mattn/go-sqlite3` の DSN クエリパラメータをサポートし、6 つの OS およびアーキテクチャ向けに SLSA 証明付きのビルド済みサーバーバイナリを埋め込み、リクエストのパイプライン化と 64 KiB のバッチプリフェッチを用いることで、複数行クエリにおいて in-process の `cgo` を上回る性能を発揮します。

```bash
go get gosuda.org/sqlite3@v0.3.3540100
```

## なぜ別の Go SQLite ドライバなのか?

SQLite は世界で最も広く展開されているソフトウェアの一つですが、Go からこれを利用する場合、従来はビルドの簡便さとエンジンの忠実性のどちらかを選ばざるを得ないという、扱いにくい選択を技術者に強いてきました。

一方には、長年のコミュニティ標準である `github.com/mattn/go-sqlite3` があります。これは公式の C 言語版 SQLite アマルガメーションを `cgo` を介して直接ラップしています。利用者は実際の C クエリプランナ、完全な WAL の並行性セマンティクス、そして予測可能な C のメモリ管理を得られます。その代償は `CGO_ENABLED=1` であり、ローカルビルドおよび CI パイプラインが遅くなり、最小構成の scratch イメージや distroless コンテナイメージでは対応する C ランタイムライブラリが必要となり、別の OS や CPU アーキテクチャへのクロスコンパイルには完全な C のクロスツールチェーンが必要となり、さらに C 側のメモリ破壊はGo プロセスのアドレス空間を共有してしまう点です。

もう一方には、C 言語版アマルガメーションを、エミュレートされた C ランタイム (`libc`) 上で Go のソースコードへ機械的に変換して生成された純 Go 実装の SQLite ポートがあります。これらは `CGO_ENABLED=0` を復活させ、ビルドを極めて容易にします。しかしながら、持続的なワークロードの下では、変換された C ヒープおよび行ごとの Go のメモリ割り当てがガベージコレクションの負荷を増大させ、ロック、busy タイムアウト、あるいはステートメントの中断に関する微妙なエッジケースが、ネイティブ C ビルドとは異なる振る舞いを示すことがあります。

私たちは `gosuda.org/sqlite3` を、Go アプリケーションが `CGO_ENABLED=0` と本物の C 言語版 SQLite エンジンのいずれかを選ばなくてもよいように構築しました。公式 SQLite `3.54.1` の C アマルガメーションを専用の共有メモリサーバー (`hqq-sqlite-server`) にコンパイルし、`gosuda.org/hqq` を介して通信させることで、お使いの Go バイナリは `CGO_ENABLED=0` でコンパイルされながら、プロセス境界を越えて、改変されていない `-O3` 最適化済みの C 言語版 SQLite を実行します。

## ゼロコンフィグ実行: `go get` から `sql.Open` まで

既存の Go コードベースにおいて `github.com/mattn/go-sqlite3` を置き換えるには、ブランクインポートのパスを変更するだけで十分です。このドライバは `"sqlite3"` (および明示的な選択のための `"hqq-sqlite"`) として登録され、同じ DSN クエリ文字列を受け付けます。

```go
package main

import (
	"context"
	"database/sql"
	"fmt"
	"log"

	_ "gosuda.org/sqlite3"
)

func main() {
	dsn := "file:app.db?_journal_mode=WAL&_busy_timeout=5000&_txlock=immediate&_foreign_keys=on"
	db, err := sql.Open("sqlite3", dsn)
	if err != nil {
		log.Fatal(err)
	}
	defer db.Close()

	ctx := context.Background()
	if _, err := db.ExecContext(ctx, `CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY, name TEXT NOT NULL)`); err != nil {
		log.Fatal(err)
	}
	if _, err := db.ExecContext(ctx, `INSERT INTO users (name) VALUES (?)`, "Ada Lovelace"); err != nil {
		log.Fatal(err)
	}

	var id int64
	var name string
	if err := db.QueryRowContext(ctx, `SELECT id, name FROM users WHERE name = ?`, "Ada Lovelace").Scan(&id, &name); err != nil {
		log.Fatal(err)
	}
	fmt.Printf("user #%d: %s\n", id, name)
}
```

### SLSA 証明付きの埋め込みワーカーバイナリ

ライブラリが C のワーカープロセスを使用すると聞いたときに自然に浮かぶ疑問は、_「C のバイナリを Go プログラムと一緒に別途インストールまたは配布する必要があるのか?」_というものでしょう。

答えは否です。`gosuda.org/sqlite3` は、6 つの主要ターゲットすべてに対するビルド済みの `hqq-sqlite-server` バイナリを、プラットフォームタグ付きの Go ファイル (`internal/embedded/embed_<goos>_<goarch>.go`) の内部に直接埋め込んでいます。Go のビルド制約により、対象の `GOOS` と `GOARCH` に一致するファイルのみがコンパイルされるため、`GOOS=linux GOARCH=arm64 go build` を実行すると、約 1.4 MiB の `linux/arm64` サーバーバイナリのみが自動的に埋め込まれます。

| 対象 OS     | 対象アーキテクチャ (`GOARCH`) | 埋め込みバイナリ                       | ビルドツールチェーンと来歴                         |
| :---------- | :---------------------------- | :------------------------------------ | :------------------------------------------------ |
| **Linux**   | `amd64`, `arm64`              | `hqq-sqlite-server_linux_<arch>`      | GCC `-O3`, SLSA Build L3 来歴                     |
| **macOS**   | `arm64`, `amd64`              | `hqq-sqlite-server_darwin_<arch>`     | AppleClang `-O3`, SLSA Build L3 来歴              |
| **Windows** | `amd64`                       | `hqq-sqlite-server_windows_amd64.exe` | MSVC `-O2`, SLSA Build L3 来歴                    |
| **Windows** | `arm64`                       | `hqq-sqlite-server_windows_arm64.exe` | `zig cc -target aarch64-windows-gnu -O3`, SLSA L3 |

`sql.Open` が最初の接続を確立すると、`autolaunch.go` が埋め込みバイナリをユーザーのキャッシュディレクトリ (`$XDG_CACHE_HOME/hqq/bin/<sha256>/`) に展開し、`gosuda.org/hqq/secureexec` を介して、その暗号学的 SHA-256 ダイジェストを `hashes.go` 内に固定された `ReleaseHashes` マップと照合して検証したうえで、`--parent-stdin` を指定してワーカーを起動します。これにより、ワーカーの存続期間は Go プロセスに厳密に束縛されます。

### `mattn/go-sqlite3` DSN 互換性のためのクリーンルーム実装

真のドロップイン代替として機能させるため、DSN パーサ (`dsn.go`) は、`mattn/go-sqlite3` を利用するコードベース全体で使用される接続およびプラグマの設定項目について、クリーンルームによる再実装を提供しています。

| DSN クエリパラメータ                                                                          | 受け付ける値                                            | SQLite への影響                                                                                      |
| :-------------------------------------------------------------------------------------------- | :------------------------------------------------------ | :--------------------------------------------------------------------------------------------------- |
| `_busy_timeout` / `_timeout`                                                                  | ミリ秒単位の整数 (例: `5000`)                           | セッション open 時に `sqlite3_busy_timeout(db, ms)` を呼び出す。                                     |
| `_journal_mode` / `_journal`                                                                  | `DELETE`, `TRUNCATE`, `PERSIST`, `MEMORY`, `WAL`, `OFF` | `PRAGMA journal_mode = <mode>` を実行する。                                                          |
| `_txlock`                                                                                     | `immediate`, `exclusive`, `deferred`                    | `db.BeginTx` に対して `BEGIN IMMEDIATE`, `BEGIN EXCLUSIVE`, または `BEGIN DEFERRED` を選択する。     |
| `_foreign_keys` / `_fk`                                                                       | `on`, `true`, `1`, `off`, `false`, `0`                  | `PRAGMA foreign_keys = ON/OFF` を実行する。                                                          |
| `_synchronous` / `_sync`                                                                      | `0`/`OFF`, `1`/`NORMAL`, `2`/`FULL`, `3`/`EXTRA`        | `PRAGMA synchronous = <level>` を実行する。                                                          |
| `_cache_size`                                                                                 | ページ数の整数または `-KiB` (例: `-20000`)              | `PRAGMA cache_size = <val>` を実行する。                                                             |
| `_loc`                                                                                        | `auto` または IANA タイムゾーン (例: `UTC`, `Asia/Seoul`) | `DATETIME`/`TIMESTAMP` のテキスト列を、対象のロケーションにおける `*time.Time` へ解析する。         |
| `mode`                                                                                        | `ro`, `rw`, `rwc`, `memory`                             | `SQLITE_OPEN_READONLY`, `SQLITE_OPEN_READWRITE`, `SQLITE_OPEN_CREATE`, または `SQLITE_OPEN_MEMORY` を設定する。 |
| `_case_sensitive_like`, `_query_only`, `_recursive_triggers`, `_secure_delete`, `_AutoVacuum` | 真偽値またはモードの整数                                | 接続初期化時に対応するセッション `PRAGMA` を適用する。                                               |

## プロセス分離をスループットの優位性へ転換する

一見すると、SQLite を別プロセスで実行することは、`cgo` を介して in-process で `sqlite3_step` を呼び出すよりも遅くなるはずに思えます。しかし実際には、クエリが単一行を超える行を返した時点で、逆の現象が起こります。

### 行ごとの `cgo` 呼び出しに隠れたコスト

従来の `cgo` ベースの SQLite ドライバでは、`database/sql` が反復処理を行と列の単位で一つずつ進めます。4 列を持つ 1,000 行を読み込むと、次のような処理が発生します。

- `sqlite3_step` に対する 1,000 回の `cgo` 呼び出し
- `sqlite3_column_type` および `sqlite3_column_int64` / `sqlite3_column_text` に対する 4,000 回の `cgo` 呼び出し
- Go スケジューラと C スタックの間における、数千回に及ぶ個別の境界横断

### リクエストのパイプライン化と 64 KiB バッチプリフェッチ

`gosuda.org/sqlite3` は、`stmt.go`、`rows.go`、および `sqlite3/server/sqlite3_hooks.c` に含まれる 2 つの仕組みを中心にワイヤプロトコルを再設計することで、行ごとの境界横断を排除します。

```text
Go クライアント (stmt.go / rows.go)                 C11 ワーカー (sqlite3_hooks.c)
-----------------------------                       ----------------------------
1. 1 フライトでパイプライン化:
   ResetAsync + BindValuesAsync
   + StmtInfoAsync + FetchRowsAsync(128)  =======>  reset -> bind -> stmt_info ->
                                                    最大 128 行まで sqlite3_step() をループ
                                          <=======  バッチ 1 (行 1..128 を 64 KiB スロットに格納)
2. 直ちにプリフェッチを発行:
   FetchRowsAsync(128) (バッチ 2)         =======>  行 129..256 に対して sqlite3_step() をループ
   C がバッチ 2 をステップ実行している間、
   Go の呼び出し元はバッチ 1 の行 1..128 を走査!  <=======  バッチ 2 が共有メモリに準備完了
```

1. **シングルフライトのクエリパイプライン化 (`stmt.go`)**: `QueryContext` または `Stmt.QueryContext` が読み取り専用ステートメントを実行する際、`s.query()` は `reset`、`bind_values`、`stmt_info` (初回実行時)、および `fetch_rows` を、単一の共有メモリフライトにおいて非同期に送信します。これら 4 つの操作はすべて C ワーカー上で連続して実行され、1 回のラウンドトリップで返されます。
2. **緊密な C バッチループと非同期プリフェッチ (`rows.go`)**: `fetch_rows` (`sqlite3_hooks.c`) の内部では、C ワーカーがネイティブ C のループ内で `sqlite3_step(stmt)` をステップ実行し、バッチ上限、スロットの満杯、または `SQLITE_DONE` に達するまで、最大 `FetchSize` 行 (デフォルトは 128) を 64 KiB の共有メモリ応答スロットに直接エンコードします。`rows.ingest()` がバッチ 1 をデコードするとすぐに、最初の行を呼び出し元へ返す前に、バッチ 2 のために `FetchRowsAsync` を発行します。Go コードがバッチ 1 を走査している間、C ワーカーは別の CPU コア上で既にバッチ 2 をステップ実行しています。

Apple M4 (`darwin/arm64`) において、同一の SQLite データベースに対して `gosuda.org/sqlite3` (`CGO_ENABLED=0`) と `github.com/mattn/go-sqlite3` (`CGO_ENABLED=1`) を比較すると、バッチ処理とパイプライン化の効果が次のように示されます。

| ワークロード                        | `gosuda.org/sqlite3` (`CGO_ENABLED=0`) | `mattn/go-sqlite3` (`CGO_ENABLED=1`) | 相対速度                             |
| :---------------------------------- | -------------------------------------: | ------------------------------------: | :----------------------------------- |
| **ポイントセレクト (PK による `QueryRow`)** |            `3.52 µs/op` (9 B, 1 alloc) |       `3.56 µs/op` (232 B, 11 allocs) | **1.01 倍 (同等、割り当て 11 分の 1)** |
| **100 行スキャン (`rows.Next`)**    |    `20.41 µs/op` (30.5 KB, 211 allocs) |   `36.34 µs/op` (21.8 KB, 309 allocs) | **1.78 倍高速**                      |
| **1,000 行スキャン (`rows.Next`)**  |  `125.92 µs/op` (296 KB, 2,030 allocs) | `349.10 µs/op` (216 KB, 3,009 allocs) | **2.77 倍高速**                      |
| **準備済み INSERT (オートコミット)** |           `5.33 µs/op` (0 B, 0 allocs) |        `5.88 µs/op` (120 B, 5 allocs) | **1.10 倍高速 (割り当て 0)**         |
| **100 行トランザクションバッチ**    |         `189.99 µs/op` (16 B, 1 alloc) |  `191.50 µs/op` (12.1 KB, 503 allocs) | **1.01 倍 (割り当て 500 分の 1)**    |

## キャンセル、パスのサンドボックス化、および接続プールの回復

SQLite を分離されたワーカーで実行することは、タイムアウト、信頼されていない SQL、およびファイルシステムの境界の扱いも強化します。

- **決定論的なコンテキストキャンセル (`conn.go` および `errors.go`)**: ドライバのすべての呼び出しは、軽量な `context.AfterFunc` ガード (`cn.guard`) を設定します。呼び出し元の `context.Context` がキャンセルされるか、その期限が切れた場合、サーバー上で遅いリカーシブクエリまたはロック待機が実行中であれば、ガードは接続を不良とマークし、ローカルのセッションリンクを終了させ、帯域外の `Cancel()` フレームを送信します。C ワーカーは直ちに `sqlite3_interrupt(db)` を呼び出して `sqlite3_step` のブロックを解除し、一方 Go ドライバは、結果として生じた `SQLITE_INTERRUPT` ステータスを `context.Canceled` または `context.DeadlineExceeded` に対応付け、`database/sql` にプール内の接続を置き換えるよう指示します。
- **ディレクトリのサンドボックス化 (`sqlsrv_pathrules.c`)**: C ワーカーは、すべての `open_v2` 呼び出しに対して `--allow-dir` のパスルールを適用し、シンボリックリンクを解決して、許可されたディレクトリ外への相対的な `..` によるトラバーサルを拒否します。`sqlite3_load_extension` および `SQLITE_LIMIT_ATTACHED` はデフォルトで無効化されており、SQL インジェクションによってデータベースディレクトリの外へ脱出したり、任意の共有オブジェクトを読み込んだりすることを防ぎます。

## リポジトリの構成

リポジトリは、トップレベルのパッケージがドロップイン型の `database/sql` ドライバを提供し、一方で `launcher/` と `sqlite3/` が高度なカスタムサーバー構成のために引き続き利用できるように構成されています。

```text
gosuda.org/sqlite3
├── driver.go / api.go         # database/sql ドライバの登録 ("sqlite3") と Connector API
├── dsn.go                     # クリーンルーム実装の mattn/go-sqlite3 DSN パーサおよびプラグマ設定
├── conn.go / tx.go            # セッションのライフサイクル、コンテキストガード、およびトランザクション状態機械
├── stmt.go / rows.go          # シングルフライトのリクエストパイプライン化と 64 KiB バッチプリフェッチ
├── errors.go                  # 型付けされた SQLite の基本および拡張エラーコードとセンチネルマッチング
├── autolaunch.go / hashes.go  # ゼロコンフィグの埋め込みサーバー展開と SHA-256 固定
├── internal/embedded/         # プラットフォームタグ付きの //go:embed バイナリ (linux, darwin, windows)
├── launcher/                  # カスタムサーバー配備のための構成可能なプロセス監視機構
└── sqlite3/                   # 生成された hqq-bindgen Go クライアントおよび C11 SQLite サーバーのソース
    └── server/                # C11 サーバーの実装 (sqlite3_hooks.c, sqlsrv_policy.c)
```

## はじめに

`go get` でモジュールをインストールし、`sql.Open("sqlite3", dsn)` でデータベースを開いてください。

```bash
go get gosuda.org/sqlite3@v0.3.3540100
```

- **GitHub リポジトリ**: [https://github.com/gosuda/hqq-sqlite3](https://github.com/gosuda/hqq-sqlite3)
- **DSN およびプラグマのリファレンス**: [docs/DSN.md](https://github.com/gosuda/hqq-sqlite3/blob/main/docs/DSN.md)
- **アーキテクチャおよびワイヤプロトコル**: [docs/ARCHITECTURE.md](https://github.com/gosuda/hqq-sqlite3/blob/main/docs/ARCHITECTURE.md)
