---
id: 134c150de56c188c925476664026a1ff
author: Lemon Mint
title: 'gosuda.org/sqlite3: Controlador database/sql con CGO_ENABLED=0 sobre IPC de memoria compartida'
description: Controlador SQLite 3.54.1 compatible con database/sql para Go con CGO_ENABLED=0, aislamiento de workers mediante memoria compartida HQQ, prefetch de filas por lotes de 64 KiB y binarios con atestación SLSA.
language: es
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` es un controlador `database/sql` para Go con `CGO_ENABLED=0` que ejecuta la amalgama C de SQLite `3.54.1` upstream dentro de un proceso trabajador aislado, comunicándose a través de anillos de memoria compartida de [HQQ](https://gosuda.org/hqq). Se registra bajo el nombre de controlador estándar `"sqlite3"`, admite los parámetros de consulta del DSN de `github.com/mattn/go-sqlite3`, incluye binarios de servidor precompilados con atestación SLSA para seis objetivos de sistema operativo y arquitectura, y utiliza segmentación de solicitudes (pipelining) con precarga de lotes de 64 KiB para superar a `cgo` en proceso en consultas de múltiples filas.

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

## ¿Por qué otro controlador SQLite para Go?

SQLite es uno de los componentes de software más desplegados del mundo; sin embargo, su uso desde Go ha obligado históricamente a los ingenieros a elegir entre la simplicidad de compilación y la fidelidad del motor.

Por un lado se encuentra `github.com/mattn/go-sqlite3`, el estándar comunitario de larga trayectoria. Envuelve directamente la amalgama oficial de C de SQLite mediante `cgo`. Se obtiene el planificador de consultas real de C, la semántica completa de concurrencia de WAL y una gestión de memoria en C predecible. El costo es `CGO_ENABLED=1`: las compilaciones locales y las canalizaciones de CI se ralentizan, las imágenes de contenedor mínimas (scratch o distroless) requieren bibliotecas de tiempo de ejecución de C compatibles, la compilación cruzada hacia otro sistema operativo o arquitectura de CPU exige una cadena de herramientas cruzada de C completa, y cualquier corrupción de memoria en C comparte el espacio de direcciones del proceso Go.

Por otro lado, existen los ports de SQLite puros en Go, generados mediante la traducción mecánica de la amalgama de C a código fuente Go sobre un entorno de ejecución de C emulado (`libc`). Estos restablecen `CGO_ENABLED=0`, lo que hace trivial la compilación. Sin embargo, bajo cargas de trabajo sostenidas, el heap de C traducido y las asignaciones de Go por fila aumentan la presión sobre el recolector de basura, y casos límite sutiles en el bloqueo, los tiempos de espera ocupados (busy timeouts) o la interrupción de sentencias pueden divergir de las compilaciones nativas en C.

Creamos `gosuda.org/sqlite3` para que las aplicaciones Go no tengan que elegir entre `CGO_ENABLED=0` y el motor real de C SQLite. Al compilar la amalgama oficial de C de SQLite `3.54.1` en un servidor dedicado de memoria compartida (`hqq-sqlite-server`) y comunicarse a través de `gosuda.org/hqq`, su binario Go se compila con `CGO_ENABLED=0` mientras ejecuta C SQLite sin modificaciones y optimizado con `-O3` a través de un límite de proceso.

## Ejecución sin configuración: desde `go get` hasta `sql.Open`

Reemplazar `github.com/mattn/go-sqlite3` en una base de código Go existente requiere cambiar únicamente la ruta de importación en blanco. El controlador se registra como `"sqlite3"` (y como `"hqq-sqlite"` para una selección explícita) y acepta las mismas cadenas de consulta 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)
}
```

### Binarios de trabajador embebidos con atestación SLSA

Una pregunta natural al saber que una biblioteca usa un proceso trabajador en C es: _"¿Tengo que instalar o distribuir un binario de C separado junto a mi programa Go?"_

No. `gosuda.org/sqlite3` incorpora binarios precompilados de `hqq-sqlite-server` para los seis objetivos principales directamente dentro de archivos Go con etiquetas de plataforma (`internal/embedded/embed_<goos>_<goarch>.go`). Dado que las restricciones de compilación de Go solo compilan el archivo que coincide con su `GOOS` y `GOARCH` de destino, la compilación cruzada con `GOOS=linux GOARCH=arm64 go build` incorpora automáticamente únicamente el binario de servidor `linux/arm64` de aproximadamente 1,4 MiB.

| Sistema operativo objetivo | Arquitectura objetivo (`GOARCH`) | Binario embebido                      | Cadena de herramientas y procedencia              |
| :------------------------- | :------------------------------- | :------------------------------------ | :------------------------------------------------ |
| **Linux**                  | `amd64`, `arm64`                 | `hqq-sqlite-server_linux_<arch>`      | GCC `-O3`, procedencia SLSA Build L3              |
| **macOS**                  | `arm64`, `amd64`                 | `hqq-sqlite-server_darwin_<arch>`     | AppleClang `-O3`, procedencia SLSA Build L3       |
| **Windows**                | `amd64`                          | `hqq-sqlite-server_windows_amd64.exe` | MSVC `-O2`, procedencia SLSA Build L3             |
| **Windows**                | `arm64`                          | `hqq-sqlite-server_windows_arm64.exe` | `zig cc -target aarch64-windows-gnu -O3`, SLSA L3 |

Cuando `sql.Open` establece su primera conexión, `autolaunch.go` materializa el binario embebido en el directorio de caché del usuario (`$XDG_CACHE_HOME/hqq/bin/<sha256>/`), verifica su resumen criptográfico SHA-256 frente al mapa fijado `ReleaseHashes` de `hashes.go` mediante `gosuda.org/hqq/secureexec`, y lanza el trabajador con `--parent-stdin` para que su tiempo de vida quede estrictamente vinculado a su proceso Go.

### Compatibilidad de DSN con `mattn/go-sqlite3` mediante reimplementación limpia

Para funcionar como un reemplazo directo real, el analizador de DSN (`dsn.go`) implementa una réplica de diseño limpio de los parámetros de conexión y de pragmas utilizados en las bases de código de `mattn/go-sqlite3`:

| Parámetro de consulta DSN                                                                     | Valores aceptados                                       | Efecto en SQLite                                                                                     |
| :-------------------------------------------------------------------------------------------- | :------------------------------------------------------ | :--------------------------------------------------------------------------------------------------- |
| `_busy_timeout` / `_timeout`                                                                  | Entero en milisegundos (p. ej., `5000`)                 | Invoca `sqlite3_busy_timeout(db, ms)` al abrir la sesión.                                            |
| `_journal_mode` / `_journal`                                                                  | `DELETE`, `TRUNCATE`, `PERSIST`, `MEMORY`, `WAL`, `OFF` | Ejecuta `PRAGMA journal_mode = <mode>`.                                                              |
| `_txlock`                                                                                     | `immediate`, `exclusive`, `deferred`                    | Selecciona `BEGIN IMMEDIATE`, `BEGIN EXCLUSIVE` o `BEGIN DEFERRED` para `db.BeginTx`.                |
| `_foreign_keys` / `_fk`                                                                       | `on`, `true`, `1`, `off`, `false`, `0`                  | Ejecuta `PRAGMA foreign_keys = ON/OFF`.                                                              |
| `_synchronous` / `_sync`                                                                      | `0`/`OFF`, `1`/`NORMAL`, `2`/`FULL`, `3`/`EXTRA`        | Ejecuta `PRAGMA synchronous = <level>`.                                                              |
| `_cache_size`                                                                                 | Entero en páginas o `-KiB` (p. ej., `-20000`)           | Ejecuta `PRAGMA cache_size = <val>`.                                                                 |
| `_loc`                                                                                        | `auto` o zona horaria IANA (p. ej., `UTC`, `Asia/Seoul`) | Analiza las columnas de texto `DATETIME`/`TIMESTAMP` y las convierte en `*time.Time` en la ubicación de destino. |
| `mode`                                                                                        | `ro`, `rw`, `rwc`, `memory`                             | Establece `SQLITE_OPEN_READONLY`, `SQLITE_OPEN_READWRITE`, `SQLITE_OPEN_CREATE` o `SQLITE_OPEN_MEMORY`. |
| `_case_sensitive_like`, `_query_only`, `_recursive_triggers`, `_secure_delete`, `_AutoVacuum` | Booleano o enteros de modo                              | Aplica el `PRAGMA` de sesión correspondiente en la inicialización de la conexión.                    |

## Convertir el aislamiento de procesos en una ventaja de rendimiento

A primera vista, ejecutar SQLite en un proceso separado parece que debe ser más lento que invocar `sqlite3_step` en proceso mediante `cgo`. En la práctica, ocurre lo contrario en cuanto una consulta devuelve más de una fila.

### El costo oculto de las llamadas `cgo` fila por fila

En un controlador SQLite convencional basado en `cgo`, `database/sql` recorre el resultado una fila y una columna a la vez. Leer 1.000 filas con 4 columnas provoca:

- 1.000 llamadas `cgo` a `sqlite3_step`
- 4.000 llamadas `cgo` a `sqlite3_column_type` y `sqlite3_column_int64` / `sqlite3_column_text`
- Miles de cruces de frontera individuales entre el planificador de Go y la pila de C

### Segmentación de solicitudes y precarga de lotes de 64 KiB

`gosuda.org/sqlite3` elimina los cruces de frontera por fila rediseñando el protocolo de comunicación en torno a dos mecanismos en `stmt.go`, `rows.go` y `sqlite3/server/sqlite3_hooks.c`:

```text
Cliente Go (stmt.go / rows.go)                      Trabajador C11 (sqlite3_hooks.c)
------------------------------                      --------------------------------
1. Segmentar en 1 vuelo:
   ResetAsync + BindValuesAsync
   + StmtInfoAsync + FetchRowsAsync(128)  =======>  reset -> bind -> stmt_info ->
                                                    bucle sqlite3_step() hasta 128 filas
                                          <=======  Lote 1 (filas 1..128 empaquetadas en slot de 64 KiB)
2. Disparar inmediatamente la precarga:
   FetchRowsAsync(128) (Lote 2)           =======>  bucle sqlite3_step() para filas 129..256
   Mientras C avanza el Lote 2, el código
   Go escanea las filas 1..128 del Lote 1!  <=======  Lote 2 listo en memoria compartida
```

1. **Segmentación de consultas de vuelo único (`stmt.go`)**: Cuando `QueryContext` o `Stmt.QueryContext` ejecutan una sentencia de solo lectura, `s.query()` envía de forma asíncrona `reset`, `bind_values`, `stmt_info` (en la primera ejecución) y `fetch_rows` en un único vuelo de memoria compartida. Las cuatro operaciones se ejecutan consecutivamente en el trabajador C y devuelven el resultado en un solo viaje de ida y vuelta.
2. **Bucles de lote C ajustados y precarga asíncrona (`rows.go`)**: Dentro de `fetch_rows` (`sqlite3_hooks.c`), el trabajador C ejecuta `sqlite3_step(stmt)` en un bucle C nativo, codificando hasta `FetchSize` filas (por defecto 128) directamente en el slot de respuesta de memoria compartida de 64 KiB hasta alcanzar el límite del lote, llenar el slot o producirse `SQLITE_DONE`. En cuanto `rows.ingest()` decodifica el Lote 1, dispara `FetchRowsAsync` para el Lote 2 antes de devolver la primera fila al llamador. Mientras su código Go escanea el Lote 1, el trabajador C ya está avanzando el Lote 2 en otro núcleo de CPU.

En Apple M4 (`darwin/arm64`), la comparación de `gosuda.org/sqlite3` (`CGO_ENABLED=0`) frente a `github.com/mattn/go-sqlite3` (`CGO_ENABLED=1`) sobre la misma base de datos SQLite muestra el efecto del agrupamiento por lotes y la segmentación:

| Carga de trabajo                     | `gosuda.org/sqlite3` (`CGO_ENABLED=0`) | `mattn/go-sqlite3` (`CGO_ENABLED=1`) | Velocidad relativa                   |
| :----------------------------------- | -------------------------------------: | -----------------------------------: | :----------------------------------- |
| **Selección puntual (`QueryRow` por PK)** |            `3.52 µs/op` (9 B, 1 asignación) |       `3.56 µs/op` (232 B, 11 asignaciones) | **1.01x (paridad, 11x menos asignaciones)** |
| **Escaneo de 100 filas (`rows.Next`)**    |    `20.41 µs/op` (30.5 KB, 211 asignaciones) |   `36.34 µs/op` (21.8 KB, 309 asignaciones) | **1.78x más rápido**                 |
| **Escaneo de 1.000 filas (`rows.Next`)**  |  `125.92 µs/op` (296 KB, 2.030 asignaciones) | `349.10 µs/op` (216 KB, 3.009 asignaciones) | **2.77x más rápido**                 |
| **Inserción preparada (autocommit)**      |           `5.33 µs/op` (0 B, 0 asignaciones) |        `5.88 µs/op` (120 B, 5 asignaciones) | **1.10x más rápido (0 asignaciones)** |
| **Lote de transacción de 100 filas**      |         `189.99 µs/op` (16 B, 1 asignación) |  `191.50 µs/op` (12.1 KB, 503 asignaciones) | **1.01x (500x menos asignaciones)**  |

## Cancelación, aislamiento de rutas y recuperación del pool de conexiones

Ejecutar SQLite en un trabajador aislado también fortalece la forma en que el controlador gestiona los tiempos de espera, el SQL no confiable y los límites del sistema de archivos:

- **Cancelación de contexto determinista (`conn.go` y `errors.go`)**: Cada llamada del controlador activa un guardia ligero `context.AfterFunc` (`cn.guard`). Si el `context.Context` del llamador se cancela o expira su plazo mientras una consulta recursiva lenta o una espera de bloqueo se ejecuta en el servidor, el guardia marca la conexión como inválida, cierra el enlace de sesión local y envía un marco `Cancel()` fuera de banda. El trabajador C invoca `sqlite3_interrupt(db)` de inmediato, desbloqueando `sqlite3_step`, mientras el controlador Go traduce el estado `SQLITE_INTERRUPT` resultante a `context.Canceled` o `context.DeadlineExceeded` e indica a `database/sql` que reemplace la conexión en el pool.
- **Aislamiento de directorios (`sqlsrv_pathrules.c`)**: El trabajador C aplica las reglas de ruta `--allow-dir` en cada llamada a `open_v2`, resolviendo enlaces simbólicos y rechazando el recorrido relativo `..` fuera del directorio permitido. `sqlite3_load_extension` y `SQLITE_LIMIT_ATTACHED` están deshabilitados por defecto, de modo que una inyección SQL no pueda salir del directorio de la base de datos ni cargar objetos compartidos arbitrarios.

## Estructura del repositorio

El repositorio está organizado de modo que el paquete de nivel superior proporciona el controlador `database/sql` de reemplazo directo, mientras que `launcher/` y `sqlite3/` permanecen accesibles para despliegues avanzados con servidor personalizado:

```text
gosuda.org/sqlite3
├── driver.go / api.go         # Registro del controlador database/sql ("sqlite3") y API Connector
├── dsn.go                     # Analizador DSN de diseño limpio compatible con mattn/go-sqlite3 y configuración de pragmas
├── conn.go / tx.go            # Ciclo de vida de la sesión, guardia de contexto y máquina de estados de transacciones
├── stmt.go / rows.go          # Segmentación de solicitudes de vuelo único y precarga de lotes de 64 KiB
├── errors.go                  # Códigos de error primarios/extendidos de SQLite tipados y coincidencia de centinelas
├── autolaunch.go / hashes.go  # Materialización del servidor embebido sin configuración y fijación SHA-256
├── internal/embedded/         # Binarios //go:embed con etiquetas de plataforma (linux, darwin, windows)
├── launcher/                  # Supervisor de procesos configurable para despliegues de servidor personalizados
└── sqlite3/                   # Cliente Go hqq-bindgen generado y código fuente del servidor C11 de SQLite
    └── server/                # Implementación del servidor C11 (sqlite3_hooks.c, sqlsrv_policy.c)
```

## Primeros pasos

Instale el módulo con `go get` y abra su base de datos con `sql.Open("sqlite3", dsn)`:

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

- **Repositorio de GitHub**: [https://github.com/gosuda/hqq-sqlite3](https://github.com/gosuda/hqq-sqlite3)
- **Referencia de DSN y pragmas**: [docs/DSN.md](https://github.com/gosuda/hqq-sqlite3/blob/main/docs/DSN.md)
- **Arquitectura y protocolo de comunicación**: [docs/ARCHITECTURE.md](https://github.com/gosuda/hqq-sqlite3/blob/main/docs/ARCHITECTURE.md)
