修复错误,优化性能,实现类型系统,优化安装卸载过程
This commit is contained in:
1 parent
fd5f320360
commit
48eec484b7
104 files changed
+4127
-845
No files matched your search
+83
-44
@@ -1,72 +1,111 @@
|
||||
# 架构与组件
|
||||
|
||||
ChuSQL 采用客户端 / 服务端架构。数据库文件仅由 `chusql-server` 直接访问,其他组件通过网络接口与数据库服务通信。
|
||||
ChuSQL 采用客户端 / 服务端架构。正常运行时,只有数据库服务直接访问数据文件;离线初始化与恢复由 bootstrap 完成。
|
||||
|
||||
## 主要组件
|
||||
## 组件与访问路径
|
||||
|
||||
| 组件 | 作用 |
|
||||
| --- | --- |
|
||||
| `chusql-server` | 数据库服务,负责 SQL 执行、权限检查、事务与数据访问 |
|
||||
| `chusql-web` | Web 管理端,通过数据库服务访问数据 |
|
||||
| `csql` | 命令行客户端,通过数据库服务执行 SQL |
|
||||
| `chusql-core/engine` | SQL 解析、语义检查、优化与执行 |
|
||||
| `chusql-core/storage` | 页面、索引、日志与持久化存储 |
|
||||
|
||||
## 数据流
|
||||
|
||||
典型访问路径为:
|
||||
|
||||
```text
|
||||
~~~text
|
||||
Web / CLI / 其他客户端
|
||||
↓
|
||||
TCP
|
||||
↓
|
||||
chusql-server
|
||||
↓
|
||||
SQL 引擎与存储层
|
||||
↓
|
||||
磁盘
|
||||
```
|
||||
↓ TCP
|
||||
chusql-server
|
||||
↓
|
||||
SQL 引擎 → Rust 存储层 → 磁盘
|
||||
~~~
|
||||
|
||||
所有 SQL 和权限检查均由数据库服务统一处理。Web 和 CLI 不直接访问数据目录,因此多个客户端可以同时连接同一数据库服务。
|
||||
| 组件 | 职责 |
|
||||
| --- | --- |
|
||||
| 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` 指定的位置,主要包含:
|
||||
[storage] data_dir 是持久化数据的根目录,诊断日志由 log_files 单独指定。
|
||||
|
||||
| 目录 | 内容 |
|
||||
| 位置 | 内容 |
|
||||
| --- | --- |
|
||||
| `system/` | 系统数据库,包括账号和权限等系统信息 |
|
||||
| `databases/<数据库名>/` | 用户创建的数据库 |
|
||||
| system/ | 身份、凭据、角色、授权和预定义类型目录 |
|
||||
| databases/<数据库名>/ | 业务数据库的 catalog、数据与索引 |
|
||||
| WAL 与检查点文件 | 已提交写入的恢复记录及刷盘进度 |
|
||||
|
||||
安装完成后只有系统数据库,不自动创建业务数据库。业务数据库需通过 `CREATE DATABASE` 创建。
|
||||
安装只初始化系统数据库,业务数据库由用户创建。表存在性以 catalog 为准,不能仅凭数据文件判断。
|
||||
|
||||
## 数据库服务
|
||||
## 会话与并发
|
||||
|
||||
`chusql-server` 默认监听 `127.0.0.1:7777`,客户端建立连接后完成登录、数据库选择和 SQL 执行。
|
||||
- 每条 TCP 连接拥有独立会话,当前数据库和事务互不影响。
|
||||
- 读请求可并行,写请求由存储层协调。
|
||||
- Web 与 CLI 共享服务端的权限和执行规则,不直接读写数据目录。
|
||||
|
||||
每条连接拥有独立会话状态,例如当前数据库选择不会影响其他连接。不同连接可以同时执行语句:读请求之间可以并行,写请求之间由存储层排队。
|
||||
## SQL 查询处理
|
||||
|
||||
语句里的库前缀和表限定符由引擎在语义层解析:存储层只接受精确的表名键,因此 `FROM sales.orders` 与选定 `sales` 后的 `FROM orders` 指向同一张表。FROM 的来源是一棵树:单表、派生表(括号里的子查询,必须带别名)与连接共用同一个 `FromClause`,所以 `JOIN` 两侧都能是派生表。解析派生表时先把内层语句在空作用域里解析成一张“表”——内层不能引用外层列,输出列统一改成裸列名(内层 `u.id` 在外层用 `d.id` 引用),重名直接报错;执行侧对应一个 `Derived` 算子,把内层计划的结果按别名加前缀。派生表不参与连接顺序重排(叶子的行数未知),投影也不下推进去,但内层计划照常做谓词下推与索引改写。投影项的 `AS` 别名与 `SELECT` 的输出列名一起在语义层解析,派生表与最外层查询都按别名暴露列名。
|
||||
查询依次经过解析、基于 schema 的语义检查、优化和执行。库名、表名、列名及别名在语义层解析;存储层使用精确的表名键。
|
||||
|
||||
`WITH` 不新增语法树节点:解析器在读到一个 `WITH` 时就把每个 CTE 的名字与语句记进一张环境表,遇到 `FROM` 里出现的 CTE 名(不分大小写,只认前面已定义的)就换成 M11 的派生表节点,主语句与 CTE 内部都能再写 `WITH`。这样展开出来的语句对语义层、执行器与鉴权完全等价于手写派生表:CTE 用列清单改名走派生表的投影别名,来源树与权限覆盖不必另做一套。代价是 CTE 不做物化(引用两次就执行两次)、不支持递归,且前向引用会当成普通表名去查。
|
||||
| 能力 | 实现与边界 |
|
||||
| --- | --- |
|
||||
| 派生表 | 内层独立解析,必须有别名,不能引用外层列;输出列按别名暴露 |
|
||||
| WITH / CTE | 解析时展开为派生表;不支持递归或物化,多次引用分别执行 |
|
||||
| ANY / ALL | 按 OR / AND 的三值逻辑求值;空集的 ANY 为假、ALL 为真 |
|
||||
| 子查询复用 | Filter 和计算列按外层绑定分组;JOIN 条件按首次遇到的绑定惰性复用 |
|
||||
| 成本估算 | 使用行数、不同值数和数值列的 16 桶等宽直方图 |
|
||||
| 索引选择 | 优化器判断适用条件与成本,存储层管理实际索引 |
|
||||
|
||||
量词比较走另一个真节点 `QuantCmp`:解析时普通比较运算符先读最长符号,发现后面是 `ANY` / `ALL` 就让给量词解析,因此 `age > ANY (SELECT …)` 与 `age > 25` 共用同一层优先级。求值时先把子查询的行取成字面量,再摊成 `AND`(`ALL`)/ `OR`(`ANY`)链交给既有三值逻辑,于是空子查询 `ANY` 为假、`ALL` 为真,子查询里的 `NULL` 按三值规则传播。节点不带取反位(本方言没有前缀 `NOT`),取反写相反的运算符,如 `<> ALL` 与 `NOT IN` 同义。
|
||||
派生表可根据 schema 和透明算子估计行数并参与安全的连接重排;缺统计或可能报错时保守处理。输出列投影可下推,但保留所有计算项的求值,避免隐藏错误。内存存储提供实时行数和封顶去重统计。JOIN 条件的子查询在首次遇到外层绑定时求值,缓存仅限当前连接;空行对不触发求值,普通条件仍逐行计算。尚无半连接或通用物化。统计信息只影响计划选择,优化不能改变查询结果语义。
|
||||
|
||||
优化器的成本估算用存储维护的统计:行数、列级不同值数,以及数值列的列级等宽直方图。直方图是 16 个桶加区间下界 / 上界,只统计 `int` 与 `float` 列(NULL 与非数值不进直方图);存储侧随插入累计、随删除递减,值跑出区间就把旧桶按中点并进新区间,删到表空时清空。这些统计随 schema 下沉到引擎,`Cost.rangeRows` 有直方图就按桶与区间的交集估命中行数(端点按闭区间算),没有直方图退回原来的四分之一;`Cost.selectivityOf` 把这个比例交给连接顺序估算与索引改写的成本把关,因此一条覆盖大半张表的范围条件不会再被改写成索引点查。在线协议里每条列统计多出 `lo` / `hi` / `hist` 三个可选字段,缺省即「没有直方图」,老客户端与老字典都能照常读。
|
||||
完整 SQL 写法及限制见 [syntax.md](syntax.md)。
|
||||
|
||||
子查询按外层绑定分组求值:`Filter` 与计算列两个算子入口先看条件里有没有子查询,有就按子查询引用的外层列取值给扫描到的行分组,同一绑定只把子查询换一次(`IN` / `EXISTS` / 标量 / 量词四种位置共用),然后各行的条件与表达式都用换好的那份求值。非相关子查询没有外层列,因此整条语句只跑一次子查询;相关子查询按不同绑定各跑一次(外层 3 行但绑定只有 2 种就只跑 2 次)。没有子查询的行仍走原来的逐行求值,JOIN 条件与更深的表达式位置不做分组。
|
||||
## 快照事务
|
||||
|
||||
显式事务在会话层实现:`BEGIN` 把当前数据库整库读成内存快照,事务内的语句都在快照上执行;`SAVEPOINT` 把当前快照压进会话里的保存点栈,`ROLLBACK TO` 退回该点并保留它,`RELEASE` 丢掉该点与其后的保存点;`COMMIT` 先把快照、基线与库里现值按行 `id` 逐键比对,检测到写写冲突就报 `serialization_failure` 并保留事务;没有冲突才把差分用一条 `apply_transaction` 请求交给存储层应用;`ROLLBACK` 直接丢弃快照。因此隔离级别是快照读加原子提交:事务里看不到别的事务未提交或后来提交的改动,提交对外一次性可见,冲突由调用方回滚或重试。
|
||||
| 阶段 | 行为 |
|
||||
| --- | --- |
|
||||
| BEGIN | 将当前数据库读为会话内存快照 |
|
||||
| 事务内执行 | 读取快照与自身修改,看不到其他连接后续提交 |
|
||||
| SAVEPOINT | 保存当前快照;回滚可退回指定保存点 |
|
||||
| COMMIT | 比对基线与现值,确认无写写冲突后原子应用差分 |
|
||||
| ROLLBACK | 丢弃会话快照与保存点 |
|
||||
|
||||
WAL 负责崩溃恢复:每条记录带自增 LSN,一组写操作与一条提交标记一次落盘;每组提交在数据和字典都刷盘之后把 `wal.checkpoint` 推进到这条提交的 LSN,含义是「到此为止的改动都已落盘」,再按这个边界丢掉日志前缀——所以日志里通常只有当组在飞的记录。进程重启时只重放检查点之后的记录,按标记分组取已提交的组、丢弃没有标记的整组,重放前逐条做幂等检查作为第二道保险,结束后再把检查点推到最后一条提交。LSN 从检查点与文件尾里较大的那个续号,跨重启单调,`SystemStatus` 应答里的 `last_lsn` 报告该检查点 LSN。建表、删表、建索引、删索引与数据改动走同一条 WAL,恢复时一并重放。
|
||||
有整数 id 的表按行比对并提交差分;其他表按整表替换提交。发生冲突时返回 serialization_failure 并保留事务,由调用方回滚或重试。事务内不支持 DDL 或切换数据库。
|
||||
|
||||
堆页的空间回收分两级:删行只在槽位目录里把该槽的长度置零(墓碑),页内留下的空洞按四分之一阈值判定——死空间达到整页四分之一时随这次删除一起整理(同一次写页,不额外写 WAL),把活行重新码放到页尾并原地更新槽里的偏移。`compact` 请求在没有隐藏列的表上就是逐页做同一件事,只有带隐藏列(`DROP COLUMN` 留下的旧字节)时才整表重写。整理只改页内偏移、不动槽位号,所以索引里存的「页号加槽号」位置继续有效,不需要重建索引;跨页的空洞不做填充,行仍按插入顺序进页,没有 `ORDER BY` 时的可见顺序不变。
|
||||
## WAL 与崩溃恢复
|
||||
|
||||
系统数据库及部分管理操作仅允许管理员访问。系统表统一用 `__system_` 前缀:账号表 `__system_users` 对语句通道隐藏,角色、授权、带 option 的授权、成员四张表(`__system_roles` / `__system_grants` / `__system_grant_options` / `__system_members`)只在 `system` 库里可见,权限服务自身也通过 SQL 读写它们;存储启动时把旧名 `sys_roles` / `sys_grants` / `sys_members` 连同数据与索引文件一起迁移过来。角色支持互相继承:`GRANT 角色 A TO 角色 B` 记一条 B 属于 A 的成员关系,判定权限时沿成员关系算出向上闭包再取并集;写入时拒绝成环与自继承,删角色会清掉它两个方向的成员关系。带 `WITH GRANT OPTION` 的授权记在第四张表 `__system_grant_options` 里(同样的角色、权限、对象三列,另开一张表省掉给 `__system_grants` 加列带来的系统表结构迁移):普通身份只要手里那条授权带 grant option 就能把同一项权限转授给别的角色,`REVOKE` 仍然只允许管理员,收回授权时连它的 grant option 一起收掉,但不级联收回别人已经转授出去的授权。授权命令由权限服务在会话层之外直接执行,`GRANT` 命令会先按当前库把对象补全成 `库.表` 再落盘,因此判断转授权资格与实际写入用的是同一个对象名。
|
||||
1. 一组写操作与提交标记写入 WAL,每条记录带递增 LSN。
|
||||
2. 数据和 catalog 刷盘后,检查点推进到已完成提交的 LSN。
|
||||
3. 按检查点截断日志前缀,保留尚未确认刷盘的记录。
|
||||
4. 重启时只重放检查点之后的已提交组,丢弃未提交组,并进行幂等检查。
|
||||
|
||||
账号与权限改动会断开受影响账号的在用连接。TCP 服务维护一张在线连接表(自增编号对应一条连接条目:登录账号与「已被断开」标记),会话在执行完 `CREATE` / `ALTER` / `DROP USER` 以及 `CREATE` / `DROP ROLE`、`GRANT` / `REVOKE` 之后算出受影响账号——角色类命令先沿成员关系把角色展开成具体账号名(删角色必须在命令落地前算,落地后成员关系就没了)——再给这些账号的连接打上断开标记。连接线程读到请求后先看标记,命中就回 `unauthorized`(`session was dropped by an account change`)并收工,因此空闲连接在客户端下一次请求时结束,不强行从别的线程中断阻塞读取:Windows 上 GHC 经 `socketToHandle` 得到的句柄,`hGetLine` / `hIsEOF` 阻塞期间既收不到 `throwTo` 也不受 `timeout` 约束,标记法不依赖异步异常,全平台行为一致。权限判定本身不缓存、每条语句现查,所以这里补的是「已登录的长连接不能继续用旧身份」,与不相关账号的连接无关。Web 侧走令牌,令牌登录时记下账号版本,改口令或删账号会递增版本,旧令牌因此自动失效。
|
||||
建表、删表、建索引、删索引和数据修改均进入 WAL。LSN 跨重启续号,SystemStatus 的 last_lsn 表示检查点 LSN。
|
||||
|
||||
服务监听地址、端口和请求限制可在 `chusql.toml` 的 `[server]` 分区中配置。
|
||||
若系统目录损坏导致服务无法启动,使用离线 bootstrap。repair、recover 与 reset 的范围及操作步骤见 [bootstrap.md](bootstrap.md)。
|
||||
|
||||
## 页面与空间回收
|
||||
|
||||
- 删除行先将槽位标记为墓碑。
|
||||
- 死空间达到页大小的四分之一时,随删除整理页内活行。
|
||||
- 整理保留槽位编号,索引中的页号与槽号仍有效。
|
||||
- compact 通常逐页整理;存在 DROP COLUMN 留下的隐藏列时整表重写。
|
||||
|
||||
不进行跨页填洞;没有 ORDER BY 时仍保留当前扫描顺序,但应用应使用显式排序获得稳定结果。
|
||||
|
||||
## 身份、授权与系统目录
|
||||
|
||||
系统表统一使用 __system_ 前缀,身份与凭据目录对普通语句隐藏。SUPERUSER 拥有最高权限,system_catalog_manager 提供受限目录管理能力;两者不通过角色成员关系继承。
|
||||
|
||||
| 机制 | 行为 |
|
||||
| --- | --- |
|
||||
| 角色继承 | 沿成员关系取得权限并集;拒绝自继承与环 |
|
||||
| 表级授权 | 支持 SELECT、INSERT、UPDATE、DELETE 和 ALL |
|
||||
| 转授权 | WITH GRANT OPTION 允许将持有的权限授予其他身份 |
|
||||
| 撤销 | 管理员撤销权限及对应转授权能力;当前不级联撤销已转授权限 |
|
||||
| 对象名称 | 授权前补全为库.表,检查与持久化使用同一名称 |
|
||||
|
||||
类型目录说明见 [types.md](types.md),身份字段与权限边界见 [users-and-roles.md](users-and-roles.md)。
|
||||
|
||||
## 权限变更与会话失效
|
||||
|
||||
账号、属性和授权变化会标记受影响的在线连接。角色变化先展开成员关系,确定涉及的登录身份。
|
||||
|
||||
TCP 连接在下一次请求时返回 unauthorized 并关闭;空闲连接不被强行中断。权限逐条查询,避免继续使用旧授权。Web 会话还通过账号版本校验,口令或身份变更使旧凭据失效。
|
||||
Reference in new issue
Block a user