SQL_DRIVER_GUIDE.md 9.8 KB

database/sql 驱动(vrtxsql)

DB/driver 包实现了 Go 标准库 database/sql/driver 接口,让任意基于 database/sql 的工具与 ORM(xorm、sqlx、自研 ORM 等)直接对接 VRTX 引擎, 无需引入任何第三方依赖(解析器为内置的 ORM 子集翻译器)。

import (
	"database/sql"
	_ "git.ycl.pub/yl32940/TsDB2/DB/driver" // 注册 "vrtx" 驱动
)

db, err := sql.Open("vrtx", "/path/to/data.db") // DSN = 数据库文件路径
if err != nil { ... }
defer db.Close()

db.SetMaxOpenConns(1) // 引擎单写者模型: 1 路连接即可 (见下文"连接与并发")

同一进程内相同路径的多个 sql.DB 共享同一个引擎实例(注册表 + 引用计数), 不会出现一个库被打开成两个实例的撕裂。

完整可运行示例见 demo/sql_driver。


支持的语句

SELECT

SELECT * FROM t
SELECT 列1, 列2 FROM t                    -- 投影 (AS 别名可写, 展示以表列名为准)
SELECT ... FROM t WHERE 条件
SELECT ... FROM t WHERE 条件 ORDER BY 列 [ASC|DESC], ... LIMIT n [OFFSET m]
SELECT COUNT(*) | COUNT(列) | SUM(列) | MIN(列) | MAX(列) | AVG(列) FROM t [WHERE ...]

JOIN(INNER / LEFT [OUTER],等值条件,链式多表):

SELECT 列清单 FROM 表1 [AS] a
  JOIN 表2 [AS] b ON a.x = b.y            -- INNER JOIN 可省 INNER
  LEFT JOIN 表3 [AS] c ON b.m = c.n
  [WHERE 条件] [ORDER BY ...] [LIMIT n [OFFSET m]]

列清单: * | t.* | 列名 | t.列名           -- WHERE/ORDER BY 同样支持限定名

执行策略:左表全扫描(本表条件下推索引)→ 逐行取 ON 左列值对右表做 Eq 索引点查的嵌套循环;INNER 无匹配丢行、LEFT 补全 NULL;跨表条件 (列比较 / 跨表 OR)作残余谓词在组合行上过滤;投影后内存排序再 LIMIT。 非等值 ON / RIGHT / FULL JOIN 明确报错;JOIN+聚合暂不支持。

WHERE 条件族(? 为参数占位符,字面量同样可用):

语法 说明
= != <> < <= > >= 比较;NULL 参与比较恒不成立(SQL 语义)
AND OR NOT 逻辑组合,支持 ( ... ) 分组
列 IN (v1, v2, ...) / NOT IN (...) 走索引展开定位,不退化全表扫描
列 IS NULL / 列 IS NOT NULL 按行内 NULL 位图判定
列 LIKE '前缀%' / NOT LIKE 通配符 % _

INSERT

INSERT INTO t VALUES (...)
INSERT INTO t (列...) VALUES (...), (...)    -- 多行
INSERT INTO t (列...) VALUES (?, ?)          -- 参数占位
  • 提供了主键列的整数值 → 该行就使用该主键号(LastInsertId 返回它); 未提供 → 引擎自增分配。
  • 省略的列自动置 NULL:可空列写入 NULL 位;非空列缺值报错(与 SQL 一致)。

UPDATE / DELETE

UPDATE t SET 列=值, ... [WHERE 条件]
DELETE FROM t [WHERE 条件]

WHERE 定位到行号后逐行生效,RowsAffected 真实。省略 WHERE = 作用于全表。

DDL

CREATE TABLE [IF NOT EXISTS] t (
	列  类型 [(参数)]  [NULL|NOT NULL] [UNIQUE] [PRIMARY KEY] [DEFAULT NULL] ...,
	[PRIMARY KEY (列)],
	[KEY|INDEX 名 (列)],
	[UNIQUE KEY 名 (列)]
)
CREATE [UNIQUE] INDEX 名 ON t (列)
DROP TABLE [IF EXISTS] t

列类型映射:

SQL 类型 引擎类型 备注
BIGINT (+UNSIGNED) Int64 / Uint64 主键列必须是整数类型
INT/INTEGER SMALLINT TINYINT (+UNSIGNED) Int32/16/8 / Uint*
DOUBLE FLOAT Float64/32
BOOL BOOLEAN Bool
VARCHAR(n) CHAR(n) String(定长档) n ≤ 2048;超出请用 TEXT
TEXT 动态列 不可建索引;单值上限 16360 字节,超出请用 BLOB
BLOB Blob 列 行内 8B 引用 + 链页(单值 ~4GB);SELECT 自动物化读出内容字节
DECIMAL(p,s) Decimal s 落列级参数
DATETIME TIMESTAMP Uint64 约定:毫秒时间戳
UUID UUID

列级 AUTO_INCREMENT / COMMENT '...' 可写(被忽略);DEFAULT 仅支持 DEFAULT NULL,其它默认值报错(避免静默错配)。

维护语句

VACUUM

整库重建:回收孤儿页 / 重建全部索引 / 收缩文件。行数据从旧库逐行复制, 索引从行数据重新生成,所以它也是"索引出问题时"的修复手段(等价于 SQLite 的 REINDEX + VACUUM 合体)。注意:

  • 走 Exec 执行(db.Exec("VACUUM")),RowsAffected 返回复制的行数;
  • 执行期间引擎会做 Close/Open 句柄切换,并发语句自动排队(驱动串行锁),无需停应用;
  • 活跃事务中拒绝(ErrTxnActive)——先提交或回滚再执行;
  • 只读(force-inspect)模式下报错,不会动文件。

不支持的语句(明确报错,不静默)

类别 内容
多表 除 INNER/LEFT 等值 JOIN 外的连接形式(RIGHT/FULL JOIN、笛卡尔积)
嵌套 子查询、INSERT ... SELECT
分组 GROUP BY、HAVING、DISTINCT
函数 WHERE/SELECT 中的任意函数表达式(UPPER/CASE WHEN/COALESCE(SUM(..)) 等)
索引 多列(复合)索引、复合主键 PRIMARY KEY (a, b)
变更 ALTER TABLE 全部
语法糖 BETWEEN(用 >= AND <= 代替)、ON DUPLICATE KEY UPDATE、REPLACE INTO
DDL 附加 DEFAULT 非 NULL 值、VIEW/TRIGGER/过程
其它 事务隔离级别设置、`

语义与类型约定

  • NULL:省略列 = NULL;NULL 参与任何比较恒不成立;聚合的 AVG 分母 是"该列非 NULL 的行数";COUNT(*) 数全行。字符串结果统一以 []byte 返回 (Scan 到 string / []byte 均可),NULL 扫描为 nil。
  • 时间:time.Time 参数按毫秒时间戳存入 DATETIME 列;读出走引擎类型。
  • 事务:db.Begin() / tx.Commit() / tx.Rollback(),语义即引擎的 BeginTransaction;BEGIN 会把此前自动提交挂起的修改先落盘(SQL 标准语义: BEGIN 是"之前工作已提交"的边界)。事务内的 DDL 报错(引擎 DDL 隐式提交语义)。
  • 错误传播:唯一索引冲突、非空约束、语法错误、不支持的语句——全部以 error 返回,错误信息含具体列/索引/语法点。

连接与并发

引擎是单写者模型。驱动内所有操作经一把全局锁串行化:同一时刻只有一个 操作在跑,事务期间其它连接的操作排队等待(拥有事务的连接自身不会被卡死)。 因此 SetMaxOpenConns(1) 是最优配置;不设也能正确运行,只是并发请求会排队。

已知限制

  • SELECT 结果集全量物化进内存(大结果集占用内存,后续可换流式)。
  • 动态列单值上限 16360 字节(types.MaxStringSize):TEXT 列的内容整块落进一个 堆页,更大的值写入即报错。两条出路:新表大文本直接建模成 BLOB 列;存量库走 migrate 迁移时,采样发现 TEXT 源列超过该上限会自动落成 Blob 链式列(行内 8B 引用 + 链页,单值 ~4GB),并在迁移报告里写明。
  • BLOB 列 SELECT 自动物化:行内只有 8B 链引用,驱动在持锁期间读链页把内容 字节放进结果集(单表流式与 JOIN 两条路径都接上了),Scan 到 []byte/string 均可;压缩标志不持久化,驱动按不压缩落链、同值读出(聚合 MIN/MAX 命中 BLOB 列仍明确报错)。引擎层面空 blob 与 NULL 同态(行内位图承载 NULL,空引用视为 "读不到"),读出均为 NULL。
  • 时间值约定:驱动与 gormvrtx 的 DATETIME 列是引擎的 DateTime 类型 (行内 uint64 毫秒);GORM 把时间值内联成 SQL 字面量传入,驱动会解析 'YYYY-MM-DD HH:MM:SS[.fff]' 形式,零时间(0001-01-01...)按 NULL 写入(软删除 DeletedAt 语义);纯毫秒数值同样接受。
  • 时序表(行存储为时序索引)同样可经驱动写入:主键列声明 DATETIME 且按时间非递减写入即可。

GORM 接入(gormvrtx 模块)

仓库提供官方风格的 GORM Dialector:gormvrtx/ 是独立子模块(自带 go.mod,主仓库保持零依赖):

import (
	"gorm.io/gorm"
	vrtx "git.ycl.pub/yl32940/TsDB2/gormvrtx"
)

db, err := gorm.Open(vrtx.Open("/path/to/data.db"), &gorm.Config{})
  • CRUD / 查询 / 事务 / Preload:走 GORM 生成 SQL → vrtx 驱动执行, Joins()(等值)同样可用。
  • AutoMigrate 真正可用:gormvrtx 实现了 GORM 的 Migrator 接口, schema 自省与变更直连引擎元数据 API(不经 SQL):建表 / 加列 / 删列 / 唯一与普通索引的自动比对与补齐都能跑通(AlterColumn 改类型、 重命名等少数操作返回"不支持")。无 size 的 string 字段默认落成 BLOB 链式列 (不定长大文本不再撞 16360 上限;代价是失去 LIKE/索引能力,需要可搜索文本时 给 size:1..2048 走定长档);带索引标签却落成不可索引列(动态列 / Blob 列)的字段,建表阶段即给出可操作的中文提示。
  • 整型主键零值自动预分配并回填对象(对齐自增语义)。
  • 版本与 Save:按 gorm.io/gorm v1.31 开发验证;Save() 在 v1.31 生成 INSERT ... ON CONFLICT upsert 语法,驱动不支持(明确报错), 请用 Select("*").Updates(...) 代替。

演示:cd gormvrtx/demo && go run main.go。

测试与演示

  • 驱动测试:go test ./DB/driver/(CRUD/查询全链路、事务、不支持语法、 共享引擎共 4 组)。
  • 演示:cd demo/sql_driver && go run main.go —— M1/M2/M3 全特性走查。