Files
ChuBlog/docs/architecture.md
T
LIm1tZz7 8ce4239452 v26.10.1 正式版
修复部分问题,优化性能,进一步推进类型系统
2026-10-05 22:22:23 +08:00

235 lines
17 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 架构与组件
ChuSQL 采用客户端 / 服务端架构。正常运行时,只有数据库服务直接访问数据文件;离线初始化与恢复由 bootstrap 完成。
## 组件与访问路径
~~~text
Web / CLI / 其他客户端
↓ TCP
chusql-server
↓
SQL 引擎 → Rust 存储层 → 磁盘
~~~
| 组件 | 职责 |
| --- | --- |
| 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。配置位置和平台数据目录见 [config.md](config.md)。
## 数据目录
[storage] data_dir 是持久化数据的根目录,诊断日志由 log_files 单独指定。
| 位置 | 内容 |
| --- | --- |
| system/ | 身份、凭据、角色、授权和预定义类型目录 |
| databases/<数据库名>/ | 业务数据库的 catalog、数据与索引 |
| WAL 与检查点文件 | 已提交写入的恢复记录及刷盘进度 |
安装只初始化系统数据库,业务数据库由用户创建。表存在性以 catalog 为准,不能仅凭数据文件判断。
## 会话与并发
- 每条 TCP 连接拥有独立会话,当前数据库和事务互不影响。
- 读请求可并行,写请求由存储层协调。
- Web 与 CLI 共享服务端的权限和执行规则,不直接读写数据目录。
- Web 登录会话分别持有普通 API / SQL 连接与表格编辑连接,编辑事务按页面标识固定数据库。表格读取携带编辑标识时使用该事务快照;普通查询不进入编辑事务。
- 同一 Web Cookie 的请求串行执行。编辑提交以固定保存点重放本地编辑集,失败整体回退;成功回执按提交标识去重。SQL 控制台事务另按页面与标签标识限制连接访问。
## SQL 查询处理
查询依次经过解析、基于 schema 的语义检查、优化和执行。库名、表名、列名及别名在语义层解析;存储层使用精确的表名键。
| 能力 | 实现与边界 |
| --- | --- |
| 派生表 | 内层独立解析,必须有别名,不能引用外层列;输出列按别名暴露 |
| WITH / CTE | 解析时展开为派生表;不支持递归或物化,多次引用分别执行 |
| ANY / ALL | 按 OR / AND 的三值逻辑求值;空集的 ANY 为假、ALL 为真 |
| 子查询复用 | Filter 和计算列按外层绑定分组;JOIN 条件按首次遇到的绑定惰性复用 |
| 成本估算 | 使用行数、不同值数和数值列的 16 桶等宽直方图 |
| 索引选择 | 优化器判断适用条件与成本,存储层管理实际索引 |
派生表可根据 schema 和透明算子估计行数并参与安全的连接重排;缺统计或可能报错时保守处理。输出列投影可下推,但保留所有计算项的求值,避免隐藏错误。内存存储提供实时行数和封顶去重统计。JOIN 条件的子查询在首次遇到外层绑定时求值,缓存仅限当前连接;空行对不触发求值,普通条件仍逐行计算。尚无半连接或通用物化。统计信息只影响计划选择,优化不能改变查询结果语义。
完整 SQL 写法及限制见 [syntax.md](syntax.md)。
## 快照事务
| 阶段 | 行为 |
| --- | --- |
| BEGIN | 将当前数据库读为会话内存快照 |
| 事务内执行 | 读取快照与自身修改,看不到其他连接后续提交 |
| SAVEPOINT | 保存当前快照;回滚可退回指定保存点 |
| COMMIT | 比对基线与现值,确认无写写冲突后原子应用差分 |
| ROLLBACK | 丢弃会话快照与保存点 |
有整数 id 的表按行比对并提交差分;其他表按整表替换提交。发生冲突时返回 serialization_failure 并保留事务,由调用方回滚或重试。事务内不支持 DDL 或切换数据库。
## WAL 与崩溃恢复
1. 一组写操作与提交标记写入 WAL,每条记录带递增 LSN。
2. 数据和 catalog 刷盘后,检查点推进到已完成提交的 LSN。
3. 按检查点截断日志前缀,保留尚未确认刷盘的记录。
4. 重启时只重放检查点之后的已提交组,丢弃未提交组,并进行幂等检查。
建表、删表、建索引、删索引和数据修改均进入 WAL。LSN 跨重启续号,SystemStatus 的 last_lsn 表示检查点 LSN。
若系统目录损坏导致服务无法启动,使用离线 bootstrap。repair、recover 与 reset 的范围及操作步骤见 [bootstrap.md](bootstrap.md)。
## 页面与空间回收
- 删除行先将槽位标记为墓碑。
- 死空间达到页大小的四分之一时,随删除整理页内活行。
- 整理保留槽位编号,索引中的页号与槽号仍有效。
- compact 通常逐页整理;存在 DROP COLUMN 留下的隐藏列时整表重写。
不进行跨页填洞;没有 ORDER BY 时仍保留当前扫描顺序,但应用应使用显式排序获得稳定结果。
## 身份、授权与系统目录
系统表统一使用 __system_ 前缀,身份与凭据目录对普通语句隐藏。SUPERUSER 拥有最高权限,system_catalog_manager 提供受限目录管理能力;两者不通过角色成员关系继承。
| 机制 | 行为 |
| --- | --- |
| 角色继承 | 沿成员关系取得权限并集;拒绝自继承与环 |
| 对象授权 | 数据库 CONNECT / CREATE,表 SELECT / INSERT / UPDATE / DELETE;ALL 按作用域展开 |
| 转授权 | WITH GRANT OPTION 允许将持有的权限授予其他身份 |
| 撤销 | 授予者撤销自身来源;所有者或最高权限身份管理对象授权,级联清理无合法来源的下游 |
| 稳定对象 | catalog 分配数据库及表编号,授权与所有者绑定对象和身份编号,删除后不复用 |
| 所有者 | 建表者获得表所有权;启用角色可继承所有权,数据库所有权不隐含他人表的数据权限 |
对象权限目录 __system_object_acl 对普通语句和存储请求隐藏。权限快照按字符分块,在系统库写锁内比较版本并通过同一 WAL 组替换;服务启动恢复建表前持久化的创建者声明,旧名称授权只迁移一次到现存稳定对象。
类型目录说明见 [types.md](types.md),身份字段与权限边界见 [users-and-roles.md](users-and-roles.md)。
## 权限变更与会话失效
账号、属性和授权变化会标记受影响的在线连接。角色变化先展开成员及转授权来源关系,确定涉及的登录身份。共享读写门允许查询并行,并将授权及对象结构变更与查询排序;事务提交再次验证权限和稳定对象编号。
TCP 连接在下一次请求时返回 unauthorized 并关闭;空闲连接不被强行中断。权限逐条查询,避免继续使用旧授权。Web 会话还通过账号版本校验,口令或身份变更使旧凭据失效。
## 查询引擎扩展设计(尚未实现)
按 bound IR → 子查询物化 / 递归 CTE 推进;DOMAIN 约束见[类型文档](types.md#domain-约束设计尚未实现)。以下设计尚未实施,当前能力见前文;实施状态以 project-plan.txt 为准。
### bound IR
#### 列与作用域绑定
保留语法 AST 用于解析和诊断;语义检查产出独立 `BoundStatement`,规划、优化及执行逐步只接收它。列引用绑定为作用域编号、来源编号、列编号和类型,保留原始位置用于诊断;相关列明确标记所属外层作用域,不再通过字符串补限定名辨认身份。
#### 表达式类型与错误语义
表达式节点携带结果类型、nullable、已解析运算符/函数标识和显式转换。没有证据证明安全时 `may_error` 为 true;优化不得删除或提前求值可能报错的表达式。未定型 NULL 只参与推导,最后绑定到上下文类型;不允许流入存储 schema。DOMAIN 身份保留在类型节点中,比较可以用底层语义,赋值需经过领域校验。
#### schema 与对象版本
绑定仅使用 schema,不能扫描数据或打开索引。catalog 解析对象后提供 schema 与版本;执行前再次验证稳定对象编号和 schema 版本。统计独立附着,不纳入绑定身份。内存存储使用与磁盘相同的绑定规则。
#### 实施与验收
迁移顺序为查询表达式 → RelOp 输入 → 写语句和约束;过渡适配器单向转换且仅位于边界,不维护两套优化规则。验收用现有查询全集并补同名遮蔽、相关列、别名、NULL/DOMAIN 和错误时序回归;既有可观察顺序保持。
### 通用子查询物化
#### 惰性执行与缓存身份
引入查询内 `LazyMaterialize` 算子:首次实际需要时执行,保留结果行序与重复;非相关查询每语句一次,相关查询按 bound IR 外层列绑定值缓存。缓存键沿用规范值相等、NULL 与领域底层语义,并绑定子计划和执行视图;不能跨语句、会话或权限版本复用。
#### 求值语义
首版只物化已证明确定且无副作用的子计划;可能报错的表达式保持现有首次使用和短路路径,无法证明错误时序相同的改写禁用。空输入不触发子查询,标量多行仍在实际消费时报错。ANY/ALL、IN、EXISTS 各自保留三值及空集规则,不能用集合去重替代结果。
#### 预算与回退
预算同时限制单缓存、查询总缓存和相关键数量;超过预算时在执行该次子计划前选择不缓存的原路径,不能重跑已执行一半的计划。磁盘溢写作为后续阶段,需明确临时目录、类型编码、权限及取消清理。EXPLAIN 展示物化范围与估算,运行统计不泄露缓存行值。
#### 验收条件
验收比较启用/禁用的结果多重集、行序、首次错误、空输入、NULL、重复外层绑定、多次 CTE 引用、预算及取消;不确定函数不可复用。
### 递归 CTE
#### 首版语法范围
保留现有非递归 WITH 行为;新增 WITH RECURSIVE 走绑定作用域,不在解析期无限展开。首版仅单一自引用、锚点 UNION ALL 递归项;自引用只能出现在递归项 FROM,锚点不可引用自身。互递归、多个自引用、递归项聚合/排序/LIMIT、外连接及非单调子查询明确拒绝。UNION 去重递归留后续设计,不能冒用 UNION ALL。
#### 类型绑定与迭代
锚点和递归项输出列数相同;显式列名唯一,先由锚点确定类型,再校验递归项可赋值转换,不按运行数据扩宽。无法给锚点 NULL 定型时要求显式类型。执行用 worktable,递归项仅读取上一轮 delta,按轮次和每轮既有行序追加输出,保留 UNION ALL 重复。
#### 资源预算与终止
拟定默认深度 1000、累计行数 100000、内存 64 MiB,并受服务端上限限制;达到任一预算返回 resource_limit,不返回截断结果。深度定义为递归项执行轮数;delta 为空正常终止。外层 LIMIT 首版不作为终止证明,先完整执行递归再消费,避免隐藏循环/错误。取消释放所有 worktable。
#### 验收条件
验收覆盖零轮、多轮、重复路径、循环超限、NULL/类型冲突、空锚点、名称遮蔽、非法自引用和查询取消。EXPLAIN 明确递归与预算。
## 存储与事务扩展设计(尚未实现)
先引入稳定行编号和 MVCC,再实施跨页 compact 与并行写。前置条件是新页格式、版本化 WAL、离线升级工具及恢复验收。
新格式必须标版本,旧二进制拒绝打开。旧数据在保留备份的停服副本上升级、校验后发布,不混用新旧写入路径。每阶段先运行改动模块的规定测试入口,全量门禁由所有者运行;性能数据不能代替语义、恢复或权限验收。
### 行版本 MVCC
#### 身份与可见性
引入库内单调 row_id,独立于用户 `id`、主键及物理槽号;删除不复用。每版本包含 row_id、版本编号、创建/结束提交序号与前版本定位,未提交版本只在事务私有工作集内。读视图固定 snapshot_csn,选取创建序号不大于快照且结束序号大于快照的版本,再合并自身修改。提交序号与 WAL LSN 分离,映射随提交记录持久化。
每连接仍只允许一个事务,保持快照隔离和保存点语义;不声称可串行化,写偏斜仍可能发生。写集记录读取的基线版本,提交验证该版本仍为最新;同 row_id 更新/删除冲突返回 serialization_failure。唯一键冲突在当前已提交状态和本次写集联合检查,不能仅用开始快照验证。无业务 id 的表也按 row_id 提交,不再整表替换。
索引键定位 row_id,索引查询再按快照检查版本;更新键保留旧键候选直到所有活跃快照不再需要,不能只索引最新值。扫描先保持旧格式导入的行序,更新不改 row_id 的扫描位置,新插入按持久化序号排序;不得借 MVCC 无意改变现有无 ORDER BY 输出。
#### 提交、恢复与回收
首版仍串行提交:验证 → 分配 CSN → WAL 记录版本变化及提交 → fsync → 应用 → 统一发布可见 CSN。读者仅获取已完整应用的可见序号;持久化提交后应用失败继续采用 recovery_required 封闭请求,不能把已提交组当作失败撤销。不引入未提交脏页,因此首版保持 redo-only;若将来允许提前刷未提交页,必须另行设计 undo/状态恢复。
保存点记录私有写集边界与不可变根,回退不撤销其他事务版本。活跃快照登记器包括流式扫描和物化消费者;最老快照决定旧版本回收界线。回收同时处理 heap 和索引,读定位有 pin,取消/断线可靠注销。长事务受现有寿命及新增保留空间预算限制,不能为了回收静默推进其快照。
升级在停服副本中为旧行分配 row_id、初始版本和顺序,重建索引,验证行数/值/约束后切换;不得把重复业务 id 合并。新 WAL 与页格式显式版本化,截断仍遵循已刷盘检查点,不顺带改成页 LSN 检查点。
#### 验收条件
验收包含重复/缺失业务 id、旧快照与键更新、删除重插、保存点、唯一并发冲突、写偏斜边界、长读回收、断线及提交每个持久化边界的崩溃恢复;复测 BEGIN/COMMIT 的传输和比较成本。
### 跨页 compact
#### 定位与影子页
使用 row_id 到版本位置的存储内部定位映射,索引不保存可失效的页/槽定位。首版在表维护独占锁下进行,等待该表读 pin 释放;其他表仍可运行。以快照回收界线保留所有可能可见版本,按既有逻辑扫描顺序打包到影子页和定位映射,随后 WAL 记录并原子切换文件代次。
#### 恢复与旧版本保护
旧代次直到无读引用且新代次检查点完成才删除;提交前崩溃丢弃影子文件,提交后重放切换。映射缺失、重复 row_id/版本和校验失败明确报错,不返回缺行扫描。隐藏列一并按当前 schema 改写,旧快照需要的结构不得提前删除。跨页整理不改变对象编号、行编号、值或输出顺序。
#### 验收条件
验收验证前后全量扫描和索引点/范围查一致、快照旧版本、隐藏列、页损坏、空间不足、取消与文件切换崩溃;记录回收页数及 IO,不能只比较耗时。
### 并行写
#### 实施顺序与锁范围
先允许事务私有写集准备并行,继续保留短串行 WAL/CSN 发布;第二阶段按稳定表编号与 row_id 排序取得写锁、唯一键锁。DDL/维护使用库结构锁,跨表事务必须一次取得完整锁集,禁止边执行边任意升级。权限读门在执行和最终复查期间保持现有语义;目录及 ACL 修改仍单独排序。
#### 唯一键锁与取消
唯一键锁使用与值相等一致的规范编码,缺主键也使用 row_id 锁。等待有取消与超时,超时返回明确错误且保持/回退事务遵循现有错误分类;固定锁序避免内部锁环,不承诺支持任意用户锁或死锁自动重试。
#### 提交发布顺序
允许不同表/行的验证和页面准备并行,但同一页短 latch、catalog 高水位、索引节点和 WAL 写入仍保护。发布 CSN 必须按提交顺序形成连续已应用前缀,后提交不能跨过未完成组而对读者可见。group commit 为后续性能阶段,不能改变每组原子性和持久化失败语义。
#### 验收条件
验收覆盖同表不同行、同一行、唯一键冲突、跨表相反顺序、DDL/compact/撤权竞争、超时取消和 WAL 故障;用确定同步点断言并发结果,再单独测吞吐与等待时间。