---
id: 134c150de56c188c925476664026a1ff
author: Lemon Mint
title: 'gosuda.org/sqlite3: CGO_ENABLED=0 database/sql Driver чрез Shared-Memory IPC'
description: Драйвер за SQLite 3.54.1 за Go, съвместим с database/sql, който работи с CGO_ENABLED=0, осигурява изолация на работните процеси чрез споделена памет HQQ, предварително извличане на редове на партиди от 64 KiB и бинарни файлове с SLSA атестация.
language: bg
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` е драйвер за `database/sql` на Go с `CGO_ENABLED=0`, който изпълнява upstream C SQLite `3.54.1` amalgamation в изолиран работен процес (worker process) през споделени кръгови буфери в паметта на [HQQ](https://gosuda.org/hqq). Той се регистрира под стандартното име на драйвера `"sqlite3"`, поддържа параметрите в DSN заявките на `github.com/mattn/go-sqlite3`, включва предварително компилирани сървърни двоични файлове за шест комбинации от операционна система и архитектура, атестирани по SLSA, и използва конвейеризиране на заявки (request pipelining) с предварително извличане на партиди от 64 KiB, за да надмине in-process `cgo` при заявки, връщащи множество редове.

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

## Защо още един SQLite драйвер за Go?

SQLite е един от най-широко внедрените софтуерни компоненти в света, но използването му от Go исторически е принуждавало инженерите да избират между простота на изграждането и вярност към самия енджин.

От едната страна стои `github.com/mattn/go-sqlite3`, дългогодишният общностен стандарт. Той обвива официалното C SQLite amalgamation директно чрез `cgo`. Получавате истинския C планировчик на заявки, пълната семантика на WAL конкурентността и предвидимото управление на C паметта. Цената е `CGO_ENABLED=1`: локалните сглобки и CI конвейерите се забавят, минималните scratch или distroless контейнерни образи изискват съвпадащи C рънтайм библиотеки, кръстосаната компилация за друга операционна система или архитектура на процесора изисква пълна C кръстосана верига от инструменти, а всяко повреждане на паметта в C споделя адресното пространство на Go процеса.

От другата страна са чисто Go портовете на SQLite, генерирани чрез механично превеждане на C amalgamation в Go изходен код върху емулирана C рънтайм среда (`libc`). Те възстановяват `CGO_ENABLED=0`, което прави изграждането тривиално. При продължителни натоварвания обаче преведената C куп памет и алокациите в Go за всеки ред увеличават натиска върху събирането на боклук (garbage collection), а фини гранични случаи при заключването, времената за изчакване (busy timeouts) или прекъсването на оператори могат да се отклонят от нативните C сборки.

Създадохме `gosuda.org/sqlite3`, за да не се налага Go приложенията да избират между `CGO_ENABLED=0` и истинския C енджин на SQLite. Чрез компилиране на официалния C amalgamation на SQLite `3.54.1` в специализиран сървър със споделена памет (`hqq-sqlite-server`) и комуникация чрез `gosuda.org/hqq`, вашият Go двоичен файл се компилира с `CGO_ENABLED=0`, докато изпълнява непроменен, оптимизиран с `-O3` C SQLite през граница на процесите.

## Изпълнение без конфигурация: от `go get` до `sql.Open`

Замяната на `github.com/mattn/go-sqlite3` в съществуваща Go кодова база изисква промяна единствено на импорта с празно име (blank import). Драйверът се регистрира като `"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` включва предварително компилирани двоични файлове `hqq-sqlite-server` за всичките шест основни цели директно във файлове на Go с платформени етикети (`internal/embedded/embed_<goos>_<goarch>.go`). Тъй като ограниченията за компилация на Go компилират само файла, съответстващ на вашите `GOOS` и `GOARCH`, кръстосаното компилиране с `GOOS=linux GOARCH=arm64 go build` автоматично вгражда само двоичния файл на сървъра `linux/arm64` с размер около 1,4 MiB.

| Целева ОС   | Целева архитектура (`GOARCH`) | Вграден двоичен файл                  | Инструментална верига и произход                  |
| :---------- | :---------------------------- | :------------------------------------ | :------------------------------------------------ |
| **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 |

Когато `sql.Open` установи първата си връзка, `autolaunch.go` материализира вградения двоичен файл в потребителската кеш директория (`$XDG_CACHE_HOME/hqq/bin/<sha256>/`), проверява криптографския му SHA-256 дайджест спрямо фиксирания map `ReleaseHashes` в `hashes.go` чрез `gosuda.org/hqq/secureexec` и стартира работния процес с `--parent-stdin`, така че животът му да бъде строго свързан с вашия Go процес.

### Съвместимост с DSN на `mattn/go-sqlite3` по метода "clean-room"

За да работи като истински драйверен заместител, DSN парсерът (`dsn.go`) реализира клийн-рум реплика на параметрите за връзка и PRAGMA, използвани в кодовите бази на `mattn/go-sqlite3`:

| Параметър в DSN заявката                                                                      | Приемани стойности                                      | Ефект в SQLite                                                                                       |
| :-------------------------------------------------------------------------------------------- | :------------------------------------------------------ | :--------------------------------------------------------------------------------------------------- |
| `_busy_timeout` / `_timeout`                                                                  | Цяло число в милисекунди (напр. `5000`)                 | Извиква `sqlite3_busy_timeout(db, ms)` при отваряне на сесията.                                      |
| `_journal_mode` / `_journal`                                                                  | `DELETE`, `TRUNCATE`, `PERSIST`, `MEMORY`, `WAL`, `OFF` | Изпълнява `PRAGMA journal_mode = <mode>`.                                                            |
| `_txlock`                                                                                     | `immediate`, `exclusive`, `deferred`                    | Избира `BEGIN IMMEDIATE`, `BEGIN EXCLUSIVE` или `BEGIN DEFERRED` за `db.BeginTx`.                    |
| `_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 в отделен процес изглежда задължително по-бавно от извикването на `sqlite3_step` в процеса чрез `cgo`. На практика се случва обратното, веднага щом една заявка върне повече от един ред.

### Скритата цена на извикванията на `cgo` ред по ред

В конвенционален `cgo` драйвер за SQLite `database/sql` управлява итерацията по един ред и една колона наведнъж. Четенето на 1000 реда с 4 колони предизвиква:

- 1000 `cgo` извиквания към `sqlite3_step`
- 4000 `cgo` извиквания към `sqlite3_column_type` и `sqlite3_column_int64` / `sqlite3_column_text`
- Хиляди отделни преминавания на границата между планировчика на Go и C стека

### Конвейеризиране на заявки и предварително извличане на партиди от 64 KiB

`gosuda.org/sqlite3` премахва преминаванията на граница за всеки ред, като преосмисля протокола за обмен около два механизма в `stmt.go`, `rows.go` и `sqlite3/server/sqlite3_hooks.c`:

```text
Go Client (stmt.go / rows.go)                       C11 Worker (sqlite3_hooks.c)
-----------------------------                       ----------------------------
1. Pipeline in 1 flight:
   ResetAsync + BindValuesAsync
   + StmtInfoAsync + FetchRowsAsync(128)  =======>  reset -> bind -> stmt_info ->
                                                    loop sqlite3_step() up to 128 rows
                                          <=======  Batch 1 (rows 1..128 packed in 64 KiB slot)
2. Immediately fire prefetch:
   FetchRowsAsync(128) (Batch 2)          =======>  loop sqlite3_step() for rows 129..256
   While C steps Batch 2, Go caller
   scans rows 1..128 from Batch 1!        <=======  Batch 2 ready in shared memory
```

1. **Конвейеризиране на заявки в един полет (`stmt.go`)**: Когато `QueryContext` или `Stmt.QueryContext` изпълни оператор само за четене, `s.query()` изпраща асинхронно `reset`, `bind_values`, `stmt_info` (при първото изпълнение) и `fetch_rows` в един споделен полет в паметта. И четирите операции се изпълняват последователно на C работния процес и се връщат при едно обръщение.
2. **Плътни C цикли за партиди и асинхронно предварително извличане (`rows.go`)**: Вътре в `fetch_rows` (`sqlite3_hooks.c`) C работният процес изпълнява `sqlite3_step(stmt)` в нативен C цикъл, кодирайки до `FetchSize` реда (по подразбиране 128) директно в 64 KiB слота за отговор в споделената памет, докато не бъде достигнат лимитът на партидата, слотът не се запълни или не настъпи `SQLITE_DONE`. Веднага щом `rows.ingest()` декодира Партида 1, той изпраща `FetchRowsAsync` за Партида 2, преди да върне първия ред на извикващия. Докато вашият Go код сканира Партида 1, C работният процес вече изпълнява стъпки за Партида 2 на друго ядро на процесора.

На Apple M4 (`darwin/arm64`) сравнението на `gosuda.org/sqlite3` (`CGO_ENABLED=0`) с `github.com/mattn/go-sqlite3` (`CGO_ENABLED=1`) върху една и съща SQLite база данни показва ефекта от партидната обработка и конвейеризирането:

| Натоварване                         | `gosuda.org/sqlite3` (`CGO_ENABLED=0`) | `mattn/go-sqlite3` (`CGO_ENABLED=1`) | Относително ускорение                |
| :---------------------------------- | -------------------------------------: | -----------------------------------: | :----------------------------------- |
| **Точкова заявка (`QueryRow` по PK)** |           `3.52 µs/op` (9 B, 1 alloc) |      `3.56 µs/op` (232 B, 11 allocs) | **1.01x (паритет, 11x по-малко алокации)** |
| **Сканиране на 100 реда (`rows.Next`)** |   `20.41 µs/op` (30.5 KB, 211 allocs) |  `36.34 µs/op` (21.8 KB, 309 allocs) | **1.78x по-бързо**                   |
| **Сканиране на 1000 реда (`rows.Next`)** | `125.92 µs/op` (296 KB, 2,030 allocs) | `349.10 µs/op` (216 KB, 3,009 allocs) | **2.77x по-бързо**                   |
| **Подготвено INSERT (Autocommit)**  |          `5.33 µs/op` (0 B, 0 allocs) |       `5.88 µs/op` (120 B, 5 allocs) | **1.10x по-бързо (0 алокации)**      |
| **Партидна транзакция от 100 реда** |        `189.99 µs/op` (16 B, 1 alloc) | `191.50 µs/op` (12.1 KB, 503 allocs) | **1.01x (500x по-малко алокации)**   |

## Отмяна, изолация на пътищата и възстановяване на пула от връзки

Изпълнението на SQLite в изолиран работен процес също укрепва начина, по който драйверът обработва времевите ограничения, недоверения SQL и границите на файловата система:

- **Детерминирана отмяна на контекст (`conn.go` и `errors.go`)**: Всяко извикване на драйвера активира лек страж `context.AfterFunc` (`cn.guard`). Ако `context.Context` на извикващия бъде отменен или срокът му изтече, докато на сървъра се изпълнява бавна рекурсивна заявка или чакане на заключване, стражът маркира връзката като невалидна, прекъсва локалната сесийна връзка и изпраща извънлентов (out-of-band) фрейм `Cancel()`. C работният процес незабавно извиква `sqlite3_interrupt(db)`, което освобождава `sqlite3_step`, а Go драйверът преобразува получения статус `SQLITE_INTERRUPT` в `context.Canceled` или `context.DeadlineExceeded` и указва на `database/sql` да замени връзката в пула.
- **Изолиране в директория (`sqlsrv_pathrules.c`)**: C работният процес прилага правилата за пътища `--allow-dir` при всяко извикване на `open_v2`, разрешавайки символните връзки и отхвърляйки относителни пътища `..`, които излизат извън разрешената директория. `sqlite3_load_extension` и `SQLITE_LIMIT_ATTACHED` са деактивирани по подразбиране, така че SQL инжекция не може да излезе извън директорията на базата данни или да зареди произволни споделени обекти.

## Структура на хранилището

Хранилището е структурирано така, че основният пакет предоставя драйвера за `database/sql` като заместител, докато `launcher/` и `sqlite3/` остават достъпни за разширени внедрявания с персонализиран сървър:

```text
gosuda.org/sqlite3
├── driver.go / api.go         # Регистрация на драйвера за database/sql ("sqlite3") и API на Connector
├── dsn.go                     # Clean-room DSN парсер за mattn/go-sqlite3 и конфигурация на PRAGMA
├── conn.go / tx.go            # Жизнен цикъл на сесията, контекстен страж и машина на състоянията за транзакции
├── stmt.go / rows.go          # Конвейеризиране на заявки в един полет и предварително извличане на партиди от 64 KiB
├── errors.go                  # Типизирани първични/разширени кодове за грешки на SQLite и съпоставяне на sentinel грешки
├── autolaunch.go / hashes.go  # Материализиране на вграден сървър без конфигурация и фиксиране на SHA-256
├── internal/embedded/         # Двоични файлове с платформени етикети //go:embed (linux, darwin, windows)
├── launcher/                  # Конфигурируем супервайзър на процеси за внедрявания с персонализиран сървър
└── sqlite3/                   # Генериран Go клиент hqq-bindgen и 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 и PRAGMA**: [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)
