Files
ChuSQL/docs/architecture.md
T
ChuNa7a 6996e19694
build / build (push) Blocked by required conditions
tests / build (push) Waiting to run
tests / tests (push) Waiting to run
修bug,完善功能
2026-10-16 23:52:12 +08:00

22 KiB
Raw Blame History

架构与组件

← 返回 README

本页内容:

本页面向需要理解或修改 ChuSQL 的开发者,说明组件职责、查询执行过程及存储边界。SQL 的使用方法见 SQL 语法。

ChuSQL 采用客户端 / 服务端架构。正常运行时,只有数据库服务直接访问数据文件;离线初始化与恢复由 bootstrap 完成。

阅读实现细节前,先了解本文使用的术语:

术语 含义
schema(表结构) 列名、类型和约束等定义;语义检查与优化以此为依据
catalog(系统目录) 记录数据库对象及其结构的元数据;判断表是否存在以此为准
绑定与槽位 绑定将列引用对应到输入行的位置,这个位置称为槽位
结构守卫 在执行前和存储操作锁内复查依赖,拒绝使用已经失效的表结构
DOMAIN(领域类型) 为基础类型命名,并可附加默认值和约束的自定义类型
WAL / LSN WAL 是恢复用的预写日志,LSN 是标识日志进度的递增序号
DML / DDL DML 包括插入、更新和删除数据;DDL 包括创建、修改和删除结构

组件与访问路径

flowchart LR
    CLI[CLI / 其他客户端] -->|TCP| Server[chusql-server]
    Browser[浏览器] -->|HTTP| Web[chusql-web]
    Web -->|TCP| Server
    Server --> Engine[Haskell SQL 引擎]
    Engine --> Storage[Rust 存储层]
    Storage --> Disk[(磁盘)]
组件 职责
chusql-server 登录、会话、鉴权、SQL 执行和事务
chusql-web Web 管理界面,通过 TCP 访问服务
csql 命令行客户端,通过 TCP 执行 SQL
chusql-core/engine 解析、语义检查、优化及执行
chusql-core/storage catalog、页面、B+ 树、缓存、WAL 与持久化
csql-bootstrap 安装引导及离线系统目录修复、恢复、重置

服务默认监听 127.0.0.1:7777,Web 默认监听 127.0.0.1:7778。配置位置和平台数据目录见配置选项。

存储名称校验

存储层解析库限定名后,物理表名、声明列名和写入行顶层字段名使用同一规则:名称非空,只含 ASCII 字母、数字和下划线。SQL 标识符另遵守解析器规则。

单行、批量、整表替换、结构替换及事务载荷在处理请求时拒绝非法字段名,不修改数据和 WAL。校验不遍历文本内容或复合值内部的键。

旧数据不自动改名,原始扫描与历史 WAL 重放保留兼容;后续重写需显式提供合法字段名。

数据目录

[storage] data_dir 保存持久化数据,log_files 单独指定诊断日志目录。

位置 内容
system/ 身份、凭据、角色、授权和预定义类型目录
databases/<数据库名>/ 业务数据库的 catalog、数据与索引
WAL 与检查点文件 已提交写入的恢复记录及刷盘进度

安装只初始化系统数据库,业务数据库由用户创建。表存在性以 catalog 为准,不能仅凭数据文件判断。

会话与并发

  • 每条 TCP 连接拥有独立会话,当前数据库和事务互不影响。
  • 服务端业务请求共享全局权限门,数据写语句和提交独占目标数据库的执行门,不同数据库可并行写;权限、DDL 和管理员存储请求独占全局门。库门按名称排序获取并在取消时释放,避免跨库等待环。存储层仍维护每库读写门及表锁。
  • Web 与 CLI 共享服务端的权限和执行规则,不直接读写数据目录。
  • Web 登录会话分别持有普通 API / SQL 连接与表格编辑连接,编辑事务按页面标识固定数据库。表格读取携带编辑标识时使用该事务快照;普通查询不进入编辑事务。
  • 同一 Web Cookie 的请求串行执行。编辑提交以固定保存点重放本地编辑集,失败整体回退;成功回执按提交标识去重。SQL 控制台事务另按页面与标签标识限制连接访问。

SQL 查询处理

查询依次经过解析、基于 schema 的语义检查、优化和执行。库名、表名、列名及别名在语义层解析;存储层使用精确的表名键。

解析后的查询表示

解析器将 SQL 转为抽象语法树(AST)。其中 Select 的字段与辅助函数分工如下:

名称 用途
selectItems :: [(String, Expr)] 保存输出名称与投影表达式,即查询要返回的列或计算结果
selectHaving 保存分组条件
OrderKey 区分按名称、输出序号和表达式排序
Col 表示普通列;别名、计算和聚合使用相同的投影项结构
makeSelect 接受列名列表并构造投影项

类型能力与复合值运算

运行时类型描述保存相等比较(Eq)、排序(Ord)和哈希(Hash)能力,复合类型按参数递归派生。语义检查按运算要求能力,DOMAIN 沿用基础类型。值排序和哈希按结构执行,不使用显示文本;嵌套浮点正负零保持相等与哈希一致。

值与类型编码保持版本 1,能力由代码中的类型描述提供,不写入已有值载荷。索引键编码是独立约束,运行时复合键不进入索引探测。SQL 行为见 复合值的比较、排序与分组。

绑定查询与结构复查

绑定查询保存依赖对象编号、schema 版本、列定义和直接及间接依赖的 DOMAIN。执行前复查,真实存储的每次读取还在相关库读锁内验证全部依赖;结构改变或对象替换明确报错。守卫不提供整条多请求查询的数据快照;旧目录的未知身份/版本保留兼容校验,旧存储未确认守卫则拒绝执行。

查询由 Runtime.Query.bindQuery 统一准备并绑定,输出按位置校验,保留重复列名。优化完成后将表达式、排序及分组键绑定到输入布局;物理列顺序不同会在存储边界整理,缺列或位置不符明确报错。

ORDER BY 输出序号保留到计划生成,选择对应投影表达式或 DISTINCT 输出槽位,不通过别名再次查找。非排序投影仍在 LIMIT 后求值。

聚合与排序的求值顺序

聚合计划合并投影、HAVING 和排序表达式中的当前层聚合,重复调用只计算一次。内部列使用 SQL 标识符无法声明的名称。

HAVING 强制形成分组,无分组键时将输入作为整体组。HAVING 过滤位于聚合算子 Aggregate 之后、去重与排序截断之前。

表达式排序先计算隐藏排序键并保留投影所需列,再排序、截断和投影。DISTINCT 排序映射到已有输出槽位。HAVING 和排序子查询同样参与结构依赖守卫与 SELECT 权限检查。

相关子查询的列查找

相关子查询会引用外层查询的列,因此列查找必须区分内外层作用域。

相关子查询按最近到最远的独立列作用域查找,最近层歧义不向外回退;二段限定引用只在最近声明该来源的层解析,裸列可继续向外查找。

BoundSubquery 保存父布局中的取值表达式,缓存按槽位和值区分;重绑定保留局部和外层身份,原位置失效时只允许唯一同名列重定位。

执行期外层行仍扁平化,尚无独立作用域或来源编号,结构改写须在绑定前完成。

写入求值与约束检查

写语句按目标 schema 绑定输入表达式和赋值目标,求值与校验遵循以下规则:

  • INSERT 拒绝重复目标列,按输入顺序求值,再按 schema 顺序补默认值、NULL 和自增值。
  • UPDATE 的重复赋值读取前一次修改结果。全部赋值后检查 CHECK,再检查最终行集的 NOT NULL、UNIQUE 和 PRIMARY KEY。
  • 唯一性检查保留整数与整值浮点的一致语义,多个 NULL 不冲突。检查通过前不发布修改。
  • CHECK、非空和唯一性按列槽位取值。INSERT 已有行及引擎 ALTER 输入行按原 schema 整理,缺列不跳过。

可复用的写计划

INSERT、UPDATE、DELETE 和已有表的结构变更由 Engine.bindWrite 生成不保存数据行的 BoundWrite,executeWriteM 在执行前复查结构,允许跨数据及统计变化复用。

DML 在绑定阶段固定 INSERT 输入槽位、UPDATE 目标槽位、DELETE 行布局和 CHECK / NOT NULL / UNIQUE / PRIMARY KEY 约束;复用时不重新解析或绑定这些内容,自增起点与行集约束仍取当前数据。

结构变更则在执行时对变更后的 schema 绑定并检查约束。

计划携带目标表、嵌套查询及传递 DOMAIN 依赖;ADD COLUMN / ALTER TYPE 还包含新声明类型的 DOMAIN。

建表、建删库、DOMAIN 管理和会话命令继续走各自入口,不属于已有表写计划。

DOMAIN 约束的变更

DOMAIN 的 CHECK 在独立 VALUE 槽位绑定,继承默认值和非空限制按当前领域目录合成;表列保留原始声明。创建依赖 DOMAIN 的表或领域也通过结构守卫复查定义。

ALTER DOMAIN 的校验与提交按以下顺序执行:

  1. 在存储库读锁内取得完整快照,校验直接及间接依赖和已有行。
  2. 以库身份和 WAL 检查点作为令牌,在写锁内再次比较。
  3. 令牌一致时,通过 ReplaceSchema 提交;期间任何普通提交都使令牌失效,并返回 serialization_failure。

约束变更复用现有目录版本、WAL 和恢复路径,不引入通用结构事务。

存储操作的结构守卫

写入前先发送只读的 validate_write 请求,确认存储支持写守卫并校验依赖;未确认时不发送实际修改请求。

实际读取在相关库共享门内复查,实际写入在目标库独占门及其他依赖库共享门内复查,跨库按库名排序取门。

对象替换或结构版本变化使旧绑定失效,包括结构改变后恢复原定义。

守卫保证结构校验与单次存储操作之间不发生 DDL,不提供整条写语句的数据快照或数据冲突检测。

修改表结构时的约束校验

CHECK 定义和执行共用类型、函数及列槽位绑定入口,拒绝聚合、子查询和非布尔条件。修改表结构时还需遵守以下规则:

  • DROP COLUMN 校验保留下来的 CHECK。
  • RENAME COLUMN 不自动改写约束文本,引用旧名称的 CHECK 会使改名被拒绝。
  • ALTER TYPE 依据新 schema 校验 CHECK、默认值和键能力,空表也不跳过定义校验。通过定义校验后仍检查已有行,再发布结构变更。

查询优化的能力与边界

查询优化支持以下能力,统计信息只影响计划选择,优化不能改变查询结果语义:

能力 实现与边界
派生表 内层独立解析,必须有别名,不能引用外层列;输出列按别名暴露
WITH / CTE 解析时展开为派生表;相同执行子树在查询内物化复用,不支持递归
ANY / ALL 按 OR / AND 的三值逻辑求值;空集的 ANY 为假、ALL 为真
子查询复用 按查询定义及外层绑定值物化结果,跨过滤、投影、排序与 JOIN 复用
半连接 / 反连接 独立 IN / NOT IN 与简单等值相关 EXISTS / NOT EXISTS 转为哈希存在性匹配,只输出左行
成本估算 使用行数、不同值数和数值列的 16 桶等宽直方图
索引选择 优化器判断适用条件与成本,存储层管理实际索引

连接重排与投影下推

派生表可根据 schema 和透明算子估计行数并参与安全的连接重排;缺统计或可能报错时保守处理。输出列投影可下推,但保留所有计算项的求值,避免隐藏错误。内存存储提供实时行数和封顶去重统计。

子查询结果的复用

执行器用查询内状态保存首次使用时成功求出的子查询行;键包含已解析查询定义与具名外层绑定值,跨不同父布局复用,原始名称调用保守包含整个外层行。

派生表按执行子树和外层行缓存,应用来源别名前复用,优化后子树不同则分别执行。标量、IN、EXISTS 和 ANY / ALL 各自处理共享行集,保留基数错误和三值逻辑。

缓存在每次执行时清空,不跨会话或数据修改保留;既有表达式分组求值顺序保持,JOIN 空行对仍不触发子查询。

尚无磁盘溢出或物化内存预算,也不提供整条查询的数据快照。

用存在性匹配加速子查询

半连接与反连接根据右侧是否存在匹配行来筛选左行,只输出左侧列。SemiJoin 分别绑定两侧键,只暴露左侧布局;执行左侧为空时不求右侧,其他情况完整求右侧并构建键集合,按原左行顺序过滤,右侧重复键不会放大结果。

独立 IN 保存原子查询身份,继续共享其他消费者的物化结果;NOT IN 跟踪右侧 NULL,NOT EXISTS 的空值键则视作不匹配。

大整数与浮点混合比较超出安全哈希范围时退回既有逐值比较。

重写只覆盖单独过滤谓词:IN 左键限普通列或简单字面量;相关 EXISTS 限单表、唯一外层等值引用和普通列/简单字面量投影。

组合谓词、复杂相关条件、聚合、排序截断及可能报错的相关投影继续走原路径,避免改变错误时序和词法作用域。

完整 SQL 写法及限制见 SQL 语法。

快照事务与行锁

MVCC 是多版本并发控制:每条逻辑行有不复用的内部编号和版本链,事务读取开始时可见的版本及自身修改。库内提交序号使用 WAL 提交 LSN;mvcc.json 保存版本目录,在堆与索引同步后原子发布,再推进库检查点。旧目录首次写入或开始事务时导入版本,原页格式保留。

阶段 行为
BEGIN 注册存储事务和快照引用,向引擎返回一致的结构与行集
事务内执行 基于开始快照执行,修改行和唯一键在语句成功前取得锁
SAVEPOINT 保存工作行集和读依赖;回滚同步存储计划,已有锁保留到事务结束
COMMIT 在短提交门内合并最新版本、校验权限、对象、读写依赖及最终约束,再经 WAL 发布
ROLLBACK / 断线 清除计划、快照引用、锁和等待边;保存点不能跨连接恢复

行锁覆盖内部行编号及旧、新唯一值,NULL 不争用唯一值锁。同一库中不同行可以同时准备修改,等待行锁不占库提交门。等待图检测环并中止形成环的等待者;总等待上限为 5 秒,分别返回 deadlock_detected 或 lock_timeout。版本冲突返回 serialization_failure。调用方须 ROLLBACK,再从 BEGIN 重做,不能只重发 COMMIT。保存点回滚不释放此前取得的锁。

默认 BEGIN 提供快照隔离,复查写入行的内容版本。SERIALIZABLE 写事务另外按表复查所有读依赖,包括子查询及幻行,因此不同记录的变化也可能导致保守冲突。只读事务可完成原快照读取。事务内禁止 DDL 和切换数据库。

自动提交的查询与数据写语句也使用存储事务。权限和 DDL 使用全局独占门;数据准备使用共享门,同库提交校验和 WAL 发布仍按顺序完成,以保留连续的已应用提交前缀。原始管理员存储写入不取得 SQL 事务行锁,但会发布版本,旧事务随后按版本冲突规则拒绝提交。

SQL 工作行集仍在 Haskell 中物化;版本目录发布也会读取整库并写出版本文件。行版本支持隔离与回收,不表示已有按需取行、增量版本日志或吞吐量保证。结束事务释放内存中的旧版本,后续发布写回回收结果;旧版本及整库快照会增加内存、磁盘和 I/O 成本。

在线空间整理

显式 compact 将快照中的存活行复制到独立代次的影子堆与索引,复制期间继续接受 SQL 数据写入。切换前短暂独占库门,保留最新行集的有序前缀,删除影子中的过期行并按最新顺序追加其余行;并发变化较多时,切换窗口可能重写较长后缀。

影子文件同步完成后,以 WAL 的 Migrate 操作发布 catalog 中的 file_generation,逻辑对象编号和结构版本不变。旧快照结束后删除退役文件;重启先恢复切换日志,再清理未发布与退役代次。启动时隐藏列整理仍使用离线重写。备份须包含整个库目录及页侧文件,不能只复制 catalog 指向的新堆。

WAL 与崩溃恢复

预写日志记录已提交的修改,检查点记录已完成刷盘的位置。写入和恢复遵循以下顺序:

  1. 一组写操作与提交标记写入 WAL,每条记录带递增 LSN。
  2. 数据页和索引页覆盖前,完整页镜像与校验和先同步到对应 .page-wal 文件。
  3. 数据文件同步后,原子替换 .pages 中的页 LSN 和写序号,再清理镜像日志。
  4. 数据、页版本和 catalog 刷盘后,同步并原子发布行版本目录,再将库检查点推进到已完成提交的 LSN。
  5. 按检查点截断日志前缀,保留尚未确认刷盘的记录。
  6. 重启时先按有效全页镜像修复撕裂写,再重放库检查点之后的已提交组,丢弃未提交组,并进行幂等检查。

页镜像有独立递增写序号,允许同一事务多次刷新同一页;不能仅凭相同页 LSN 跳过后一次写入。未完整写出的尾帧丢弃,完整帧校验失败则明确报错并保留材料。镜像写入或检查点失败后,当前页句柄拒绝继续刷新,析构也不能绕过恢复。

已提交组恢复前丢弃受影响表的派生索引,再从堆数据重建,避免残缺索引根阻断逻辑重放。整表日志直接重写,不先依赖可能不完整的行集判断是否已应用。

建表、删表、建索引、删索引、账号和数据修改均进入带提交点的 WAL。LSN 跨重启续号,SystemStatus 的 last_lsn 表示检查点 LSN。

若系统目录损坏导致服务无法启动,使用离线 bootstrap。repair、recover 与 reset 的范围及操作步骤见系统引导与恢复。

页面与空间回收

  • 删除行先将槽位标记为墓碑。
  • 死空间达到页大小的四分之一时,随删除整理页内活行。
  • 整理保留槽位编号,索引中的页号与槽号仍有效。
  • 显式 compact 在线复制存活行、补齐并发修改并经 WAL 切换文件代次,回收跨页空洞及隐藏列字节,同时重建索引位置。

跨页整理的复制阶段允许并发数据写入,最终追平与切换仍独占库门。旧快照引用阻止退役文件回收。整理保留切换时的逻辑行序;应用应使用 ORDER BY 获得确定结果。

身份、授权与系统目录

系统表统一使用 _system 前缀,身份与凭据目录对普通语句隐藏。SUPERUSER 拥有最高权限,system_catalog_manager 提供受限目录管理能力;两者不通过角色成员关系继承。

机制 行为
角色继承 沿成员关系取得权限并集;拒绝自继承与环
对象授权 数据库 CONNECT / CREATE,表 SELECT / INSERT / UPDATE / DELETE;ALL 按作用域展开
转授权 WITH GRANT OPTION 允许将持有的权限授予其他身份
撤销 授予者撤销自身来源;所有者或最高权限身份管理对象授权,级联清理无合法来源的下游
稳定对象 catalog 分配数据库及表编号,授权与所有者绑定对象和身份编号,删除后不复用
所有者 建表者获得表所有权;启用角色可继承所有权,数据库所有权不隐含他人表的数据权限

对象权限目录 __system_object_acl 对普通语句和存储请求隐藏。

权限快照按字符分块,在系统库写锁内比较版本并通过同一 WAL 组替换;服务启动恢复建表前持久化的创建者声明,旧名称授权只迁移一次到现存稳定对象。

类型目录说明见类型与类型目录,身份字段与权限边界见用户、角色与属性。

权限变更与会话失效

账号、属性和授权变化会标记受影响的在线连接。角色变化先展开成员及转授权来源关系,确定涉及的登录身份。共享读写门允许查询并行,并将数据写入、提交、授权及对象结构变更与查询排序;事务提交再次验证权限和稳定对象编号。

TCP 连接在下一次请求时返回 unauthorized 并关闭;空闲连接不被强行中断。权限逐条查询,避免继续使用旧授权。Web 会话还通过账号版本校验,口令或身份变更使旧凭据失效。

扩展方向

Note

以下是规划,不能作为当前 SQL、事务或恢复能力使用。进度与验收见 .agents/context/progress.txt。

查询内部表示的后续完善

已有列槽位、类型化函数与 BoundQuery;后续补齐多级词法作用域、来源/列身份,以及写语句与约束的统一绑定。迁移保留同名遮蔽、NULL / DOMAIN、输出布局和错误时序,结构改写应在绑定前完成或重新绑定。

查询扩展

方向 前置条件与边界
物化资源控制 在查询内物化基础上增加内存预算、磁盘溢出及取消;与完整查询快照协调
递归 CTE 先实现类型一致的迭代关系,明确去重、循环终止、轮次与行数预算;不隐含支持写入型 CTE

存储扩展

行版本、行锁、同库数据准备与在线整理的边界见上文。后续优化须根据测量减少整库物化、版本文件写放大和最终切换窗口;WAL 发布顺序、版本可见性与恢复约束保持一致。