---
id: 134c150de56c188c925476664026a1ff
author: Lemon Mint
title: 'gosuda.org/sqlite3 : pilote database/sql avec CGO_ENABLED=0 reposant sur l''IPC par mémoire partagée'
description: Pilote SQLite 3.54.1 compatible avec database/sql pour Go, sans CGO (CGO_ENABLED=0), avec isolation des workers en mémoire partagée HQQ, préchargement des lignes par lots de 64 Kio et binaires certifiés SLSA.
language: fr
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` est un pilote `database/sql` pour Go avec `CGO_ENABLED=0` qui exécute l'amalgamation C SQLite `3.54.1` en amont dans un processus de travail isolé, via des anneaux de mémoire partagée [HQQ](https://gosuda.org/hqq). Il s'enregistre sous le nom de pilote standard `"sqlite3"`, prend en charge les paramètres de requête DSN de `github.com/mattn/go-sqlite3`, embarque des binaires serveur préconstruits et attestés SLSA pour six cibles de systèmes d'exploitation et d'architectures, et utilise un pipelining des requêtes avec un préchargement par lots de 64 KiB afin de surpasser le `cgo` en processus sur les requêtes renvoyant plusieurs lignes.

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

## Pourquoi un Autre Pilote SQLite pour Go ?

SQLite est l'un des logiciels les plus déployés au monde, pourtant son utilisation depuis Go a historiquement contraint les ingénieurs à un choix inconfortable entre simplicité de construction et fidélité au moteur.

D'un côté se trouve `github.com/mattn/go-sqlite3`, référence communautaire de longue date. Il encapsule directement l'amalgamation C officielle de SQLite via `cgo`. On obtient ainsi le véritable planificateur de requêtes C, la sémantique complète de concurrence WAL et une gestion mémoire C prévisible. Le coût réside dans `CGO_ENABLED=1` : les compilations locales et les pipelines CI ralentissent, les images de conteneurs minimales (scratch ou distroless) exigent des bibliothèques d'exécution C correspondantes, la compilation croisée vers un autre système d'exploitation ou une autre architecture processeur nécessite une chaîne d'outils C croisée complète, et toute corruption mémoire en C partage l'espace d'adressage du processus Go.

De l'autre côté se trouvent les portages purs Go de SQLite, générés par une traduction mécanique de l'amalgamation C en code source Go, sur un runtime C émulé (`libc`). Ils rétablissent `CGO_ENABLED=0`, ce qui rend la compilation triviale. Sous des charges soutenues, cependant, le tas C traduit et les allocations Go par ligne augmentent la pression sur le ramasse-miettes, et des cas limites subtils liés au verrouillage, aux délais d'attente occupés (busy timeouts) ou à l'interruption d'instructions peuvent diverger des compilations C natives.

Nous avons conçu `gosuda.org/sqlite3` afin que les applications Go n'aient pas à choisir entre `CGO_ENABLED=0` et le moteur C SQLite réel. En compilant l'amalgamation C officielle de SQLite `3.54.1` dans un serveur dédié à la mémoire partagée (`hqq-sqlite-server`) et en communiquant via `gosuda.org/hqq`, votre binaire Go se compile avec `CGO_ENABLED=0` tout en exécutant un SQLite C non modifié, optimisé avec `-O3`, à travers une frontière de processus.

## Exécution sans Configuration : De `go get` à `sql.Open`

Remplacer `github.com/mattn/go-sqlite3` dans une base de code Go existante ne demande que de modifier le chemin d'importation anonyme. Le pilote s'enregistre sous le nom `"sqlite3"` (et `"hqq-sqlite"` pour une sélection explicite) et accepte les mêmes chaînes de requête 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)
}
```

### Binaires de Travail Embarqués et Attestés SLSA

Une question naturelle lorsqu'on apprend qu'une bibliothèque utilise un processus de travail C est : _« Dois-je installer ou livrer un binaire C séparé avec mon programme Go ? »_

Non. `gosuda.org/sqlite3` embarque des binaires `hqq-sqlite-server` préconstruits pour les six cibles principales, directement dans des fichiers Go marqués par plateforme (`internal/embedded/embed_<goos>_<goarch>.go`). Comme les contraintes de compilation de Go n'incluent que le fichier correspondant à vos `GOOS` et `GOARCH` cibles, une compilation croisée `GOOS=linux GOARCH=arm64 go build` n'embarque automatiquement que le binaire serveur `linux/arm64` d'environ 1,4 MiB.

| Système cible | Architecture cible (`GOARCH`) | Binaire embarqué                      | Chaîne de compilation et provenance               |
| :------------ | :---------------------------- | :------------------------------------ | :------------------------------------------------ |
| **Linux**     | `amd64`, `arm64`              | `hqq-sqlite-server_linux_<arch>`      | GCC `-O3`, provenance SLSA Build L3               |
| **macOS**     | `arm64`, `amd64`              | `hqq-sqlite-server_darwin_<arch>`     | AppleClang `-O3`, provenance SLSA Build L3        |
| **Windows**   | `amd64`                       | `hqq-sqlite-server_windows_amd64.exe` | MSVC `-O2`, provenance SLSA Build L3              |
| **Windows**   | `arm64`                       | `hqq-sqlite-server_windows_arm64.exe` | `zig cc -target aarch64-windows-gnu -O3`, SLSA L3 |

Lorsque `sql.Open` établit sa première connexion, `autolaunch.go` matérialise le binaire embarqué dans le répertoire de cache utilisateur (`$XDG_CACHE_HOME/hqq/bin/<sha256>/`), vérifie son empreinte cryptographique SHA-256 par rapport à la table `ReleaseHashes` épinglée dans `hashes.go` via `gosuda.org/hqq/secureexec`, puis lance le travailleur avec `--parent-stdin` afin que sa durée de vie soit strictement liée à votre processus Go.

### Compatibilité DSN Clean-Room avec `mattn/go-sqlite3`

Pour fonctionner comme un véritable remplacement direct, l'analyseur DSN (`dsn.go`) implémente une réplique clean-room des options de connexion et des pragmas utilisés dans les bases de code `mattn/go-sqlite3` :

| Paramètre de requête DSN                                                                      | Valeurs acceptées                                       | Effet SQLite                                                                                         |
| :-------------------------------------------------------------------------------------------- | :------------------------------------------------------ | :--------------------------------------------------------------------------------------------------- |
| `_busy_timeout` / `_timeout`                                                                  | Entier en millisecondes (ex. `5000`)                    | Appelle `sqlite3_busy_timeout(db, ms)` à l'ouverture de la session.                                  |
| `_journal_mode` / `_journal`                                                                  | `DELETE`, `TRUNCATE`, `PERSIST`, `MEMORY`, `WAL`, `OFF` | Exécute `PRAGMA journal_mode = <mode>`.                                                              |
| `_txlock`                                                                                     | `immediate`, `exclusive`, `deferred`                    | Sélectionne `BEGIN IMMEDIATE`, `BEGIN EXCLUSIVE` ou `BEGIN DEFERRED` pour `db.BeginTx`.              |
| `_foreign_keys` / `_fk`                                                                       | `on`, `true`, `1`, `off`, `false`, `0`                  | Exécute `PRAGMA foreign_keys = ON/OFF`.                                                              |
| `_synchronous` / `_sync`                                                                      | `0`/`OFF`, `1`/`NORMAL`, `2`/`FULL`, `3`/`EXTRA`        | Exécute `PRAGMA synchronous = <niveau>`.                                                             |
| `_cache_size`                                                                                 | Entier en pages ou `-KiB` (ex. `-20000`)                | Exécute `PRAGMA cache_size = <val>`.                                                                 |
| `_loc`                                                                                        | `auto` ou fuseau horaire IANA (ex. `UTC`, `Asia/Seoul`) | Analyse les colonnes texte `DATETIME`/`TIMESTAMP` en `*time.Time` dans le fuseau cible.              |
| `mode`                                                                                        | `ro`, `rw`, `rwc`, `memory`                             | Définit `SQLITE_OPEN_READONLY`, `SQLITE_OPEN_READWRITE`, `SQLITE_OPEN_CREATE` ou `SQLITE_OPEN_MEMORY`. |
| `_case_sensitive_like`, `_query_only`, `_recursive_triggers`, `_secure_delete`, `_AutoVacuum` | Booléen ou entiers de mode                              | Applique le `PRAGMA` de session correspondant lors de l'initialisation de la connexion.              |

## Transformer l'Isolation des Processus en Avantage de Débit

À première vue, exécuter SQLite dans un processus séparé semble forcément plus lent que d'appeler `sqlite3_step` en processus via `cgo`. En pratique, c'est l'inverse dès qu'une requête renvoie plus d'une ligne.

### Le Coût Caché des Appels `cgo` Ligne par Ligne

Dans un pilote SQLite classique basé sur `cgo`, `database/sql` pilote l'itération une ligne et une colonne à la fois. La lecture de 1 000 lignes à 4 colonnes déclenche :

- 1 000 appels `cgo` à `sqlite3_step`
- 4 000 appels `cgo` à `sqlite3_column_type` et `sqlite3_column_int64` / `sqlite3_column_text`
- Des milliers de franchissements de frontière individuels entre l'ordonnanceur Go et la pile C

### Pipelining des Requêtes et Préchargement par Lots de 64 KiB

`gosuda.org/sqlite3` élimine les franchissements de frontière par ligne en repensant le protocole filaire autour de deux mécanismes situés dans `stmt.go`, `rows.go` et `sqlite3/server/sqlite3_hooks.c` :

```text
Client Go (stmt.go / rows.go)                       Travailleur C11 (sqlite3_hooks.c)
-----------------------------                       ---------------------------------
1. Pipeline en 1 envoi :
   ResetAsync + BindValuesAsync
   + StmtInfoAsync + FetchRowsAsync(128)  =======>  reset -> bind -> stmt_info ->
                                                    boucle sqlite3_step() jusqu'à 128 lignes
                                          <=======  Lot 1 (lignes 1..128 empaquetées dans un slot de 64 KiB)
2. Déclenchement immédiat du préchargement :
   FetchRowsAsync(128) (Lot 2)            =======>  boucle sqlite3_step() pour les lignes 129..256
   Pendant que C exécute le Lot 2, l'appelant
   Go parcourt les lignes 1..128 du Lot 1 !  <=======  Lot 2 prêt en mémoire partagée
```

1. **Pipelining de requête en vol unique (`stmt.go`)** : Lorsque `QueryContext` ou `Stmt.QueryContext` exécute une instruction en lecture seule, `s.query()` soumet de manière asynchrone `reset`, `bind_values`, `stmt_info` (à la première exécution) et `fetch_rows` dans un seul envoi en mémoire partagée. Les quatre opérations s'exécutent successivement sur le travailleur C et reviennent en un seul aller-retour.
2. **Boucles de lots C serrées et préchargement asynchrone (`rows.go`)** : Dans `fetch_rows` (`sqlite3_hooks.c`), le travailleur C exécute `sqlite3_step(stmt)` dans une boucle C native, encodant jusqu'à `FetchSize` lignes (par défaut 128) directement dans le slot de réponse de 64 KiB en mémoire partagée, jusqu'à atteindre la limite du lot, le remplissage du slot ou `SQLITE_DONE`. Dès que `rows.ingest()` décode le Lot 1, il déclenche `FetchRowsAsync` pour le Lot 2 avant de renvoyer la première ligne à l'appelant. Pendant que votre code Go parcourt le Lot 1, le travailleur C exécute déjà le Lot 2 sur un autre cœur de processeur.

Sur Apple M4 (`darwin/arm64`), la comparaison entre `gosuda.org/sqlite3` (`CGO_ENABLED=0`) et `github.com/mattn/go-sqlite3` (`CGO_ENABLED=1`) sur la même base de données SQLite montre l'effet du regroupement par lots et du pipelining :

| Charge de travail                    | `gosuda.org/sqlite3` (`CGO_ENABLED=0`) | `mattn/go-sqlite3` (`CGO_ENABLED=1`) | Vitesse relative                       |
| :----------------------------------- | -------------------------------------: | -----------------------------------: | :------------------------------------- |
| **Sélection ponctuelle (`QueryRow` par PK)** |            `3,52 µs/op` (9 B, 1 alloc) |       `3,56 µs/op` (232 B, 11 allocs) | **1,01x (parité, 11x moins d'allocs)** |
| **Parcours de 100 lignes (`rows.Next`)**     |    `20,41 µs/op` (30,5 KB, 211 allocs) |   `36,34 µs/op` (21,8 KB, 309 allocs) | **1,78x plus rapide**                  |
| **Parcours de 1 000 lignes (`rows.Next`)**   |  `125,92 µs/op` (296 KB, 2 030 allocs) | `349,10 µs/op` (216 KB, 3 009 allocs) | **2,77x plus rapide**                  |
| **Insertion préparée (Autocommit)**          |           `5,33 µs/op` (0 B, 0 allocs) |        `5,88 µs/op` (120 B, 5 allocs) | **1,10x plus rapide (0 allocs)**       |
| **Lot de transaction de 100 lignes**         |         `189,99 µs/op` (16 B, 1 alloc) |  `191,50 µs/op` (12,1 KB, 503 allocs) | **1,01x (500x moins d'allocs)**        |

## Annulation, Cloisonnement des Chemins et Récupération du Pool de Connexions

L'exécution de SQLite dans un travailleur isolé renforce également la manière dont le pilote gère les délais d'attente, le SQL non fiable et les frontières du système de fichiers :

- **Annulation de contexte déterministe (`conn.go` et `errors.go`)** : Chaque appel au pilote arme une garde légère `context.AfterFunc` (`cn.guard`). Si le `context.Context` de l'appelant est annulé ou si son délai expire pendant qu'une requête récursive lente ou qu'une attente de verrou s'exécute sur le serveur, la garde marque la connexion comme défectueuse, ferme le lien de session local et envoie une trame `Cancel()` hors bande. Le travailleur C invoque immédiatement `sqlite3_interrupt(db)`, débloquant `sqlite3_step`, tandis que le pilote Go traduit le statut `SQLITE_INTERRUPT` résultant en `context.Canceled` ou `context.DeadlineExceeded` et demande à `database/sql` de remplacer la connexion dans le pool.
- **Cloisonnement des répertoires (`sqlsrv_pathrules.c`)** : Le travailleur C applique les règles de chemin `--allow-dir` à chaque appel `open_v2`, en résolvant les liens symboliques et en rejetant les traversées relatives `..` hors du répertoire autorisé. `sqlite3_load_extension` et `SQLITE_LIMIT_ATTACHED` sont désactivés par défaut afin qu'une injection SQL ne puisse ni sortir du répertoire de la base de données ni charger d'objets partagés arbitraires.

## Disposition du Dépôt

Le dépôt est structuré de sorte que le paquet de niveau supérieur fournisse le pilote `database/sql` directement utilisable, tandis que `launcher/` et `sqlite3/` restent accessibles pour les déploiements avancés de serveurs personnalisés :

```text
gosuda.org/sqlite3
├── driver.go / api.go         # Enregistrement du pilote database/sql ("sqlite3") et API Connector
├── dsn.go                     # Analyseur DSN clean-room mattn/go-sqlite3 et configuration des pragmas
├── conn.go / tx.go            # Cycle de vie de session, garde de contexte et machine d'état des transactions
├── stmt.go / rows.go          # Pipelining de requêtes en vol unique et préchargement par lots de 64 KiB
├── errors.go                  # Codes d'erreur SQLite primaires/étendus typés et correspondance de sentinelles
├── autolaunch.go / hashes.go  # Matérialisation du serveur embarqué sans configuration et épinglage SHA-256
├── internal/embedded/         # Binaires //go:embed marqués par plateforme (linux, darwin, windows)
├── launcher/                  # Superviseur de processus configurable pour déploiements de serveurs personnalisés
└── sqlite3/                   # Client Go hqq-bindgen généré et code source du serveur C11 SQLite
    └── server/                # Implémentation du serveur C11 (sqlite3_hooks.c, sqlsrv_policy.c)
```

## Premiers Pas

Installez le module avec `go get` et ouvrez votre base de données avec `sql.Open("sqlite3", dsn)` :

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

- **Dépôt GitHub** : [https://github.com/gosuda/hqq-sqlite3](https://github.com/gosuda/hqq-sqlite3)
- **Référence DSN et pragmas** : [docs/DSN.md](https://github.com/gosuda/hqq-sqlite3/blob/main/docs/DSN.md)
- **Architecture et protocole filaire** : [docs/ARCHITECTURE.md](https://github.com/gosuda/hqq-sqlite3/blob/main/docs/ARCHITECTURE.md)
