---
id: 134c150de56c188c925476664026a1ff
author: Lemon Mint
title: 'gosuda.org/sqlite3: database/sql-driver uden CGO (CGO_ENABLED=0) over delt hukommelse-IPC'
description: Drop-in database/sql SQLite 3.54.1-driver til Go med CGO_ENABLED=0, HQQ-delt hukommelsesisolering af workers, batch-prefetching af rækker på 64 KiB og SLSA-attesterede binære filer.
language: da
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` er en `database/sql`-driver til Go med `CGO_ENABLED=0`, som kører det upstream C SQLite `3.54.1`-amalgamation i en isoleret workerproces over [HQQ](https://gosuda.org/hqq)-shared-memory-ringbuffere. Den registreres under det standardiserede driver-navn `"sqlite3"`, understøtter DSN-forespørgselsparametre fra `github.com/mattn/go-sqlite3`, indlejrer SLSA-attesterede, prækompilerede serverbinære filer for seks operativsystem- og arkitekturmål og anvender request pipelining med 64 KiB batch-prefetching for at yde bedre end in-process `cgo` ved forespørgsler, der returnerer flere rækker.

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

## Hvorfor endnu en Go SQLite-driver?

SQLite er et af de mest udbredte softwareprodukter i verden, men brugen heraf fra Go har historisk tvunget udviklere til et ubehageligt valg mellem byggesimpilicitet og motorens troværdighed.

På den ene side står `github.com/mattn/go-sqlite3`, den mangeårige fællesskabsstandard. Den indpakker den officielle C SQLite-amalgamation direkte via `cgo`. Man opnår den reelle C-forespørgselsplanlægger, fuld WAL-samtidighedssemantik og forudsigelig C-hukommelseshåndtering. Prisen er `CGO_ENABLED=1`: lokale builds og CI-pipelines bliver langsommere, minimale scratch- eller distroless-containerbilleder kræver matchende C-runtimebiblioteker, krydskompilering til et andet operativsystem eller en anden CPU-arkitektur kræver en fuld C-krydsværktøjskæde, og enhver hukommelseskorruption i C deler adresserummet med Go-processen.

På den anden side findes rene Go-porteringer af SQLite, som er genereret ved mekanisk at oversætte C-amalgamationen til Go-kildekode over et emuleret C-runtime (`libc`). De genetablerer `CGO_ENABLED=0`, hvilket gør builds trivielle. Under vedvarende arbejdsbyrder øger den oversatte C-heap og allokeringer pr. række i Go dog presset på garbage collection, og subtile randtilfælde i låsning, busy-timeouts eller afbrydelse af statements kan afvige fra native C-builds.

Vi byggede `gosuda.org/sqlite3`, så Go-applikationer ikke behøver at vælge mellem `CGO_ENABLED=0` og den reelle C SQLite-motor. Ved at kompilere den officielle SQLite `3.54.1` C-amalgamation til en dedikeret shared-memory-server (`hqq-sqlite-server`) og kommunikere over `gosuda.org/hqq` kompileres din Go-binær med `CGO_ENABLED=0`, mens uændret, `-O3`-optimeret C SQLite eksekveres på tværs af en procesgrænse.

## Konfigurationsfri eksekvering: Fra `go get` til `sql.Open`

Udskiftning af `github.com/mattn/go-sqlite3` i en eksisterende Go-kodebase kræver kun ændring af den blanke import-sti. Driveren registreres som `"sqlite3"` (og `"hqq-sqlite"` for eksplicit valg) og accepterer de samme DSN-forespørgselsstrenge:

```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)
}
```

### Indlejrede SLSA-attesterede worker-binære filer

Et naturligt spørgsmål, når man hører, at et bibliotek anvender en C-workerproces, er: _"Skal jeg installere eller distribuere en separat C-binær fil sammen med mit Go-program?"_

Nej. `gosuda.org/sqlite3` indlejrer prækompilerede `hqq-sqlite-server`-binære filer for alle seks største mål direkte i platformsmærkede Go-filer (`internal/embedded/embed_<goos>_<goarch>.go`). Da Gos build-begrænsninger kun kompilerer filen, der matcher dit mål-`GOOS` og `GOARCH`, indlejrer krydskompilering med `GOOS=linux GOARCH=arm64 go build` automatisk kun den ca. 1,4 MiB store `linux/arm64`-serverbinær.

| Målsystem   | Målarkitektur (`GOARCH`) | Indlejret binær                       | Build-værktøjskæde & proveniens                   |
| :---------- | :----------------------- | :------------------------------------ | :------------------------------------------------ |
| **Linux**   | `amd64`, `arm64`         | `hqq-sqlite-server_linux_<arch>`      | GCC `-O3`, SLSA Build L3 Provenance               |
| **macOS**   | `arm64`, `amd64`         | `hqq-sqlite-server_darwin_<arch>`     | AppleClang `-O3`, SLSA Build L3 Provenance        |
| **Windows** | `amd64`                  | `hqq-sqlite-server_windows_amd64.exe` | MSVC `-O2`, SLSA Build L3 Provenance              |
| **Windows** | `arm64`                  | `hqq-sqlite-server_windows_arm64.exe` | `zig cc -target aarch64-windows-gnu -O3`, SLSA L3 |

Når `sql.Open` etablerer sin første forbindelse, materialiserer `autolaunch.go` den indlejrede binær i brugerens cache-mappe (`$XDG_CACHE_HOME/hqq/bin/<sha256>/`), verificerer dens kryptografiske SHA-256-digest mod den fastlåste `ReleaseHashes`-map i `hashes.go` via `gosuda.org/hqq/secureexec` og starter workeren med `--parent-stdin`, så dens levetid er strengt bundet til din Go-proces.

### Clean-room-kompatibilitet med `mattn/go-sqlite3` DSN

For at fungere som en reel drop-in-erstatning implementerer DSN-parseren (`dsn.go`) en clean-room-replika af de forbindelses- og pragma-indstillinger, der anvendes på tværs af `mattn/go-sqlite3`-kodebaser:

| DSN-forespørgselsparameter                                                                    | Accepterede værdier                                     | SQLite-effekt                                                                                        |
| :-------------------------------------------------------------------------------------------- | :------------------------------------------------------ | :--------------------------------------------------------------------------------------------------- |
| `_busy_timeout` / `_timeout`                                                                  | Heltal i millisekunder (f.eks. `5000`)                  | Kalder `sqlite3_busy_timeout(db, ms)` ved sessionsåbning.                                            |
| `_journal_mode` / `_journal`                                                                  | `DELETE`, `TRUNCATE`, `PERSIST`, `MEMORY`, `WAL`, `OFF` | Udfører `PRAGMA journal_mode = <mode>`.                                                              |
| `_txlock`                                                                                     | `immediate`, `exclusive`, `deferred`                    | Vælger `BEGIN IMMEDIATE`, `BEGIN EXCLUSIVE` eller `BEGIN DEFERRED` for `db.BeginTx`.                 |
| `_foreign_keys` / `_fk`                                                                       | `on`, `true`, `1`, `off`, `false`, `0`                  | Udfører `PRAGMA foreign_keys = ON/OFF`.                                                              |
| `_synchronous` / `_sync`                                                                      | `0`/`OFF`, `1`/`NORMAL`, `2`/`FULL`, `3`/`EXTRA`        | Udfører `PRAGMA synchronous = <level>`.                                                              |
| `_cache_size`                                                                                 | Heltal i sider eller `-KiB` (f.eks. `-20000`)           | Udfører `PRAGMA cache_size = <val>`.                                                                 |
| `_loc`                                                                                        | `auto` eller IANA-tidszone (f.eks. `UTC`, `Asia/Seoul`) | Parser `DATETIME`/`TIMESTAMP`-tekstkolonner til `*time.Time` i målplaceringen.                       |
| `mode`                                                                                        | `ro`, `rw`, `rwc`, `memory`                             | Sætter `SQLITE_OPEN_READONLY`, `SQLITE_OPEN_READWRITE`, `SQLITE_OPEN_CREATE` eller `SQLITE_OPEN_MEMORY`. |
| `_case_sensitive_like`, `_query_only`, `_recursive_triggers`, `_secure_delete`, `_AutoVacuum` | Boolesk værdi eller tilstandsheltal                     | Anvender den tilsvarende session-`PRAGMA` ved forbindelsesinitialisering.                            |

## Gør procesisolation til en gennemstrømningsfordel

Ved første øjekast lyder det, som om kørsel af SQLite i en separat proces nødvendigvis må være langsommere end at kalde `sqlite3_step` in-process via `cgo`. I praksis sker det modsatte, så snart en forespørgsel returnerer mere end en enkelt række.

### De skjulte omkostninger ved rækkevise `cgo`-kald

I en konventionel `cgo`-SQLite-driver driver `database/sql` iterationen en række og en kolonne ad gangen. Læsning af 1.000 rækker med 4 kolonner udløser:

- 1.000 `cgo`-kald til `sqlite3_step`
- 4.000 `cgo`-kald til `sqlite3_column_type` og `sqlite3_column_int64` / `sqlite3_column_text`
- Tusindvis af individuelle grænseovergange mellem Go-scheduleren og C-stakken

### Request pipelining og 64 KiB batch-prefetching

`gosuda.org/sqlite3` eliminerer grænseovergange pr. række ved at omdesigne wire-protokollen omkring to mekanismer i `stmt.go`, `rows.go` og `sqlite3/server/sqlite3_hooks.c`:

```text
Go-klient (stmt.go / rows.go)                       C11-worker (sqlite3_hooks.c)
-----------------------------                       ----------------------------
1. Pipeline i 1 flight:
   ResetAsync + BindValuesAsync
   + StmtInfoAsync + FetchRowsAsync(128)  =======>  reset -> bind -> stmt_info ->
                                                    loop sqlite3_step() op til 128 rækker
                                          <=======  Batch 1 (rækker 1..128 pakket i 64 KiB slot)
2. Udløs straks prefetch:
   FetchRowsAsync(128) (Batch 2)          =======>  loop sqlite3_step() for rækker 129..256
   Mens C kører trin for Batch 2, scanner
   Go-kalderen rækkerne 1..128 fra Batch 1!  <=======  Batch 2 klar i shared memory
```

1. **Single-flight forespørgselspipelining (`stmt.go`)**: Når `QueryContext` eller `Stmt.QueryContext` kører et skrivebeskyttet statement, indsender `s.query()` asynkront `reset`, `bind_values`, `stmt_info` (ved første udførelse) og `fetch_rows` i ét enkelt shared-memory-flight. Alle fire operationer udføres lige efter hinanden på C-workeren og returneres i én round trip.
2. **Stramme C-batch-løkker og asynkron prefetch (`rows.go`)**: Inde i `fetch_rows` (`sqlite3_hooks.c`) kører C-workeren `sqlite3_step(stmt)` i en native C-løkke og koder op til `FetchSize` rækker (standard 128) direkte ind i det 64 KiB store shared-memory-svarslot, indtil batchgrænsen er nået, slotten er fuld, eller `SQLITE_DONE` opstår. Så snart `rows.ingest()` har afkodet Batch 1, udløser den `FetchRowsAsync` for Batch 2, før den returnerer den første række til kalderen. Mens din Go-kode scanner Batch 1, kører C-workeren allerede trin for Batch 2 på en anden CPU-kerne.

På Apple M4 (`darwin/arm64`) viser sammenligningen mellem `gosuda.org/sqlite3` (`CGO_ENABLED=0`) og `github.com/mattn/go-sqlite3` (`CGO_ENABLED=1`) på den samme SQLite-database effekten af batching og pipelining:

| Arbejdsbyrde                        | `gosuda.org/sqlite3` (`CGO_ENABLED=0`) | `mattn/go-sqlite3` (`CGO_ENABLED=1`) | Relativ hastighed                    |
| :---------------------------------- | -------------------------------------: | ------------------------------------: | :----------------------------------- |
| **Punktselect (`QueryRow` på PK)**  |            `3,52 µs/op` (9 B, 1 alloc) |       `3,56 µs/op` (232 B, 11 allocs) | **1,01x (paritet, 11x færre allocs)** |
| **100-rækkers scanning (`rows.Next`)** |    `20,41 µs/op` (30,5 KB, 211 allocs) |   `36,34 µs/op` (21,8 KB, 309 allocs) | **1,78x hurtigere**                  |
| **1.000-rækkers scanning (`rows.Next`)** |  `125,92 µs/op` (296 KB, 2.030 allocs) | `349,10 µs/op` (216 KB, 3.009 allocs) | **2,77x hurtigere**                  |
| **Prepared insert (autocommit)**    |           `5,33 µs/op` (0 B, 0 allocs) |        `5,88 µs/op` (120 B, 5 allocs) | **1,10x hurtigere (0 allocs)**       |
| **100-rækkers transaktionsbatch**   |         `189,99 µs/op` (16 B, 1 alloc) |  `191,50 µs/op` (12,1 KB, 503 allocs) | **1,01x (500x færre allocs)**        |

## Annullering, stisandboxing og genopretning af connection pool

At køre SQLite i en isoleret worker styrker også, hvordan driveren håndterer timeouts, utroværdig SQL og filsystemgrænser:

- **Deterministisk annullering af context (`conn.go` og `errors.go`)**: Hvert driverkald aktiverer en letvægts `context.AfterFunc`-vagt (`cn.guard`). Hvis kaldekonteksten `context.Context` annulleres, eller dens deadline udløber, mens en langsom rekursiv forespørgsel eller låsevent kører på serveren, markerer vagten forbindelsen som ubrugelig, lukker den lokale sessionsforbindelse og sender en out-of-band `Cancel()`-ramme. C-workeren kalder straks `sqlite3_interrupt(db)`, hvilket frigør `sqlite3_step`, mens Go-driveren oversætter den resulterende `SQLITE_INTERRUPT`-status til `context.Canceled` eller `context.DeadlineExceeded` og instruerer `database/sql` om at erstatte forbindelsen i poolen.
- **Mappesandboxing (`sqlsrv_pathrules.c`)**: C-workeren håndhæver `--allow-dir`-stiregler ved hvert `open_v2`-kald, løser symlinks og afviser relativ `..`-traversering uden for den tilladte mappe. `sqlite3_load_extension` og `SQLITE_LIMIT_ATTACHED` er deaktiveret som standard, så SQL-injektion ikke kan undslippe databasemappen eller indlæse vilkårlige delte objekter.

## Repository-struktur

Repositoryet er struktureret således, at pakken på øverste niveau leverer den drop-in `database/sql`-driver, mens `launcher/` og `sqlite3/` forbliver tilgængelige for avancerede brugerdefinerede serverdeployments:

```text
gosuda.org/sqlite3
├── driver.go / api.go         # Registrering af database/sql-driver ("sqlite3") og Connector-API
├── dsn.go                     # Clean-room DSN-parser for mattn/go-sqlite3 og pragma-konfiguration
├── conn.go / tx.go            # Sessionslivscyklus, kontekstvagt og transaktionstilstandsmaskine
├── stmt.go / rows.go          # Single-flight-forespørgselspipelining og 64 KiB batch-prefetching
├── errors.go                  # Typede SQLite primære/udvidede fejlkoder og sentinel-matching
├── autolaunch.go / hashes.go  # Konfigurationsfri materialisering af indlejret server og SHA-256-fastlåsning
├── internal/embedded/         # Platformsmærkede //go:embed-binære filer (linux, darwin, windows)
├── launcher/                  # Konfigurerbar procesovervågning til brugerdefinerede serverdeployments
└── sqlite3/                   # Genereret hqq-bindgen Go-klient og C11 SQLite-serverkilde
    └── server/                # C11-serverimplementering (sqlite3_hooks.c, sqlsrv_policy.c)
```

## Kom i gang

Installer modulet med `go get` og åbn din database med `sql.Open("sqlite3", dsn)`:

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

- **GitHub-repository**: [https://github.com/gosuda/hqq-sqlite3](https://github.com/gosuda/hqq-sqlite3)
- **DSN- og pragma-reference**: [docs/DSN.md](https://github.com/gosuda/hqq-sqlite3/blob/main/docs/DSN.md)
- **Arkitektur og wire-protokol**: [docs/ARCHITECTURE.md](https://github.com/gosuda/hqq-sqlite3/blob/main/docs/ARCHITECTURE.md)
