0 序
- 从上大学到今天,从事后端开发、大数据、AI系统开发工作,这些年来,也不同程度地使用数十款数据库。本篇尝试总结、提炼这些数据库系统的共性设计。
1 数据库系统的设计原理研究
存储引擎 / 底层的数据结构 (必读)
行存储
- 行存储:
- 数据结构=
B-Tree或变体 : 将数据按照【顺序存储】在树节点 中,每一个节点上边都有一定的key和pointer。而Key代表这顺序,pointer则是指向子节点的路牌。这样就可以快速定位到目标数据,减少磁盘IO。但是每一个解决方案都像是硬币,都是两面的。数据不是一成不变的,在不断的数据操作时,节点会分裂,一变2之后,从【内存】flush到【硬盘】的时候就很有可以变得不连续 。并且在大量更改的时候,除了会造成写放大 ,同样也会因为【磁盘】循环写入产生不连续。
- URL : 数据结构/MYSQL/HashMap 二叉搜索树(BST) VS 平衡二叉排序树(AVL) VS B树(平衡多路搜索树) VS B+树 VS 红黑树(平衡二叉B树) - 博客园/千千寰宇
- B-树的代表:(文件系统为主)
- NTFS 文件系统
- MongoDB(WiredTiger)数据库
- B+树的代表:(传统关系型数据库为主,偏 "读写均衡 / 事务")
- MySQL (InnoDB引擎) / PostgreSQL (Heap + B-Tree + WAL) / Oracle / SQL server / 达梦数据库 ((B + 树)页式索引,页式存储(数据页 / 索引页,共享表空间))
- SQLLite(B + 树,页式存储)
列存储
- 列存储:
- 数据结构=
LSM或变体(Log-Structured Merge Tree, 结构化的日志合并树)核心的解决方案: 通过【顺序写】替代【随机写】,最小化的控制碎片的产生,提升数据密度。
- URL : LSM-Tree :结构化的日志合并树------NewSQL数据库的基石 - 博客园/千千寰宇
- 核心组件: 在 LSM-tree 有3个部分: Memtable (Memory Mutable Table = 内存可变表) / SSTable (Sorted String Table = 有序字符串表) / WAL(Write-Ahead Log = 预写日志)
- 代表数据库:
- 经典 LSM 代表: LevelDB / RocksDB(都是 LSM 的鼻祖实现,是众多数据库的底层存储) / LiteDB (嵌入式数据库,其有LSM-Tree的倾向)
- 数据湖仓: Apache Paimon
- OLAP/数据仓库: Clickhouse / Apache Doris(基于LSM-Tree的变体设计) /
- HATP : OceanBase(自研 LSM 引擎;HTAP型数据库:同时高效处理OLTP(在线事务处理)和OLAP(在线分析处理)负载 => Shared Nothing 多副本架构来支持 OLTP + 轻量 OLAP 场景)
- 时序数据库/TSDB: InfluxDB (Time-Structured Merge tree,LSM 的变体) / OpenGemini (同 TSM 变体) / TimescaleDB
- NoSQL: Apache HBase / BigTable
- Cassandra / Dynamo(闭源)
- 数据结构=其他
- 达梦(HTAP-列存储模式): 其列存储走的是自家 HFS 机制,不是 LSM
- 达梦的列存储表(HUGE 表)建立在 HFS(Huge File System) 存储机制上,针对海量数据分析设计。
- 整个【存储体系】仍是经典的页 → 簇 → 段 → 表空间结构:行存储用 B-Tree(实际是 B+ 树变体)页式管理,列存储(HUGE 表)用 HFS 的段 / 区方式组织。
- DuckDB:列式存储(PAX 格式)
其他
- 其他
- Redis:哈希表(主)、跳表(zset)
Q: RAG技术中的向量数据库(Mivlus / Qdrant 等)、搜索引擎数据库(ES等)的底层核心数据结构?
- 向量数据库 和搜索引擎数据库的核心数据结构,其实都围绕"如何快速找相似/找相关"展开,但机制不同。
下面按类别拆解。
一、向量数据库(Milvus / Qdrant)
- 核心是近似最近邻(ANN)索引,因为精确最近邻在【高维空间】(几百到上千维)下【无法靠遍历完成】,必须牺牲【精度】换【速度】。
1. 主导索引结构(三大家族)
| 索引家族 | 底层数据结构 | 原理 | 代表/默认 |
|---|---|---|---|
| 图索引 HNSW | 多层"可导航小世界"图 + 邻接表(存节点id与边) | 分层构图:顶层稀疏长跳,底层稠密精细;查询从顶层贪心下降,多层跳转 | Qdrant 默认;Milvus 支持 |
| 聚类分桶 IVF | 倒排分桶列表(聚类中心 → 桶内向量id) | K-means 把向量分到 N 个桶,查询只进最近的几个桶做线性扫描 | Milvus 经典方案 |
| 量化压缩 PQ | 乘积量化码本(codebook) + 残差桶 | 把向量切段、每段聚类成码字,用码本 id 压缩内存,查询时查表近似距离 | IVF-PQ、HNSW-PQ |
- 大数据量且内存受限时,Milvus 用 DiskANN / Vamana 图,把图放在磁盘(page 粒度),依赖显式重排序(rerank)保证召回。
- 稀疏向量(如 SPLADE/BGE-M3 稀疏端)则回到倒排索引:term → 文档列表,本质上和搜索引擎同构。
2. 辅助存储结构
| 组件 | 结构 | 作用 |
|---|---|---|
| 标量过滤字段 | B+树 / 哈希索引 | 支撑 filter 里的范围/等值条件,先过滤再 ANN 或联合过滤 |
| 写入与日志 | LSM 思想 / WAL(日志)+ 列式 binlog | Milvus 按 segment 组织,写路径走消息队列 + 落盘,后台合并 |
| ID → 向量定位 | 段(segment)/ 分片(shard)内 id 映射表 | 定位向量物理存储位置 |
| 距离计算 | SIMD 优化的浮点数组(连续内存) | 向量本体即大数组,聚拢排列保证 cache 命中 |
图(HNSW)负责跳、桶(IVF)负责缩小范围、码本(PQ)负责省内存,倒排+B+树负责过滤。
二、搜索引擎数据库(Elasticsearch / Lucene)
核心是倒排索引(Inverted Index),本质是"词 → 文档"的映射,为全文检索而生。
1. 倒排索引的三个层次
| 层次 | 底层数据结构 | 说明 |
|---|---|---|
| 词项词典 | FST(有限状态转换器) | Lucene 用 FST 存词典,内存紧凑、支持前缀查询,是词典查词的标配 |
| 倒排列表 | 跳表(Skip List) + FOR/Delta 增量编码 | 文档 id 列表有序,跳表加速求交/求并;delta 压缩省空间 |
| 文档集合运算 | Roaring Bitmap(压缩位图) | 过滤、交集、并集用位图做,常数级且省内存 |
2. 其他关键结构
| 组件 | 底层数据结构 | 作用 |
|---|---|---|
| 数值/地理字段 | BKD 树(块式 K-D 树) | 支撑数值范围查询、排序、地理距离,替代早期朴素的区间结构 |
| 段管理 | LSM 树 / 不可变 Segment + 分层合并 | 写入先进内存 buffer 再刷成小段,后台 tiered merge 成大段 |
| 正排列存 | DocValues(列式存储) | 独立于倒排的列存,供排序、聚合、脚本取值,不重复加载原文 |
| 词项信息 | 词频、位置、payload 数组 | 支撑 TF-IDF/BM25 打分与短语查询 |
小结:词靠 FST 查词典,文档集合靠跳表+位图做交并,数值靠 BKD 树,落盘靠 LSM 段,聚合靠列存。
三、两者对比速览
| 维度 | 向量数据库 | 搜索引擎 |
|---|---|---|
| 核心索引 | ANN 图/桶/量化(HNSW·IVF·PQ) | 倒排索引(FST+跳表+位图) |
| 定位目标 | 向量相似度 | 词项/数值匹配 |
| 数值字段 | B+树过滤 | BKD 树 |
| 写路径 | WAL + LSM 段 | LSM 不可变段 + 合并 |
| 聚合支持 | 弱(多靠标量过滤) | 强(DocValues 列存) |
关键差异 :搜索引擎的倒排是"精确命中";向量库的 ANN 是"近似相似"。所以现代系统常把两者叠加------用稀疏向量/标量过滤走精确,用稠密向量走近似,正是 Milvus 混合检索(hybrid search)和 ES 的 knn + bool 组合的思路。
Q: 知识图谱数据库(TypeDB、GraphDB、Neo4j、NebulaGraph)的底层核心数据结构?
知识图谱/图数据库的底层核心,和前面两类完全不同:向量库解决"相似",搜索引擎解决"匹配",图库解决**"关联遍历"** 。它的灵魂是邻接表(index-free adjacency)------每条边直接指向两端的点,遍历关系靠指针跳转而非全局查表。但各家存储落地差异很大,尤其"原生图"和"RDF/逻辑推理"是两条路线。
一、各家底层核心数据结构
| 数据库 | 类型 | 底层核心数据结构 | 关键机制 |
|---|---|---|---|
| Neo4j | 原生属性图 | 定长记录存储(fixed-size record store)+ 双向链表关系链 | 每个节点记录固定大小,id×记录大小=文件偏移,O(1) 定位;每个节点维护"首条关系"指针,关系用双向链表串起进出边 |
| NebulaGraph | 分布式属性图 | RocksDB(LSM 树)+ 自定义 KV 编码 | 存储层每个分区一个 RocksDB;点/边编码成排序 KV,边按"起点vid"前缀聚集保证邻接查询局部性;按顶点 hash 分片分布到多节点 |
| TypeDB | 逻辑推理图 | RocksDB(LSM)+ 类型化超图编码 | 底层仍是 KV/LSM,但数据按类型系统组织(实体/关系/属性作为类型实例),之上叠 Datalog 式规则推理引擎 |
| GraphDB (Ontotext) | RDF 三元组 | 六重索引(Sextuple Indexing)+ 字典编码 | 对 S-P-O 三个位置全排列建 6 个索引(SPO/SOP/PSO/POS/OSP/OPS),任意查询模式都有对应索引命中 |
二、两条路线的本质差异
路线 A:原生属性图(Neo4j / NebulaGraph)
核心是"点+边",用邻接链表 或邻接前缀把关系"焊死"在数据结构里:
Neo4j: 节点记录 ──首关系指针──▶ 关系记录(双向链表)
▲ │
└───┘ (prev/next + 两端节点指针)
Nebula: key = (startVid + edgeType + rank + endVid) → 前缀即邻接
路线 B:RDF/语义网(GraphDB)
核心是三元组 (主语, 谓语, 宾语) ,不区分"点/边",一切都是三元组。为满足任意 SPARQL 查询模式,直接对三个位置全排列建索引(六重索引),并用字典编码把每个 RDF 项压成长整型 id,三元组就变成三个整数的数组。
三、共同技术底座
| 组件 | 使用结构 | 用途 |
|---|---|---|
| 属性/标量索引 | B+ 树 | 按属性值(name、age)定位起点,Neo4j 属性索引即是 |
| 持久化层 | LSM 树(RocksDB) | Nebula、TypeDB 直接以 RocksDB 为存储;高写入、顺序读友好 |
| 全文检索 | Lucene | Neo4j 全文、GraphDB 对字面量做全文,复用搜索引擎技术 |
| 遍历引擎 | 内存 BFS/DFS + 剪枝 | 邻接结构之上做跳数受限的图遍历 |
四、与向量库/搜索引擎对齐看
| 维度 | 向量库 | 搜索引擎 | 图数据库 |
|---|---|---|---|
| 核心索引 | ANN(图/桶/量化) | 倒排(FST+跳表+位图) | 邻接表/邻接编码 + 属性 B+树 |
| 定位目标 | 相似度 | 词项匹配 | 关系遍历 |
| 关键查询 | top-K 近邻 | 命中+打分 | 多跳路径/子图匹配 |
| 典型短板 | 弱关联推理 | 弱多跳 | 高扇出深度遍历退化 |
总结 :图库的"原生性"体现在边以指针/编码形式内联在存储结构里(Neo4j 指针链表、Nebula 前缀编码),而不是像关系库那样靠 join 现算;TypeDB 把"图"抽象成类型化超图并叠加推理,GraphDB 则把"图"还原成三元组加六重索引------这是两类思路,别混为一谈。
- 扩展问题:NebulaGraph 的 KV key 编码规则 (边如何按起点前缀组织)或 GraphDB 六重索引为什么能覆盖所有查询模式?
多副本与容灾 | 高可用架构
- Raft 协议(多副本的容灾/高可用架构的底层算法)
Q: 多款数据库(OpenGemini/达梦等)都自称基于Raft协议(多副本的容灾/高可用架构),该协议的定位是什么?用什么方法解决了什么问题?(必读)
-
定位 :RAFT 在达梦 DM9 里不是"数据库本身",而是给数据库"当保镖" ------一个管多副本数据一致性的共识算法,让高可用有了可信的底层机制。
-
它解决的痛点是:把数据复制到多台机器上之后,机器反而会"吵架"。
数据库要扛故障就得存多份副本,可副本一多就冒出三个新问题:
① 哪份数据算"权威的"?
② 主节点挂掉时,谁说了算、服务不能断?
③ 各副本会不会各写各的、数据"分叉"?RAFT 就是为这三问定规矩的。
- 它的方法就四个字:少数服从多数。
- 选主:所有副本先投票,选出一个 Leader(主节点)当"唯一话事人",只允许它接收写入并给每条日志编号排顺序,其他副本老老实实跟着复制;
- 日志复制:Leader 把写入广播给多数派(如 3 副本至少 2 个)确认后才算提交------所以只要多数节点活着,数据就不丢;
- 自动恢复:Leader 挂了,其余节点重新投票,记录最全的优先当选新主,服务秒级接管、数据不分裂。
-
对【达梦 DM9】的意义 :它把这一套机制内嵌进多副本架构 ,实现了官方说的"节点自动选主、自动故障处理、自动故障恢复"的金融级高可用。
-
特别提醒 :RAFT 只是"共识层",保证的是高可用与一致性协调 ,不代表分布式性能或兼容性 ------很多库"自称基于 RAFT"说明这是个成熟通用底座 ,真正拉开差距的是各自在存储、执行、优化器上的本事。选型时与其看"用没用 RAFT",不如看它在哪个环节用、故障切换实测多快。
Q: Raft 协议规范的权威 URL?其核心组件与架构设计?(必读)
1、Raft 协议规范权威 URL
- 官方站点(The Raft Consensus Algorithm): https://raft.github.io
- 原论文(Extended Version,Ongaro & Ousterhout,即规范文本本身)
- https://raft.github.io/raft.pdf (斯坦福大学)
这两处是 Raft 的"第一手权威",其余大多是解读或实现,均以此为准。
2、Raft 核心组件与架构设计
先给角色状态机(三种角色如何切换):
stateDiagram-v2 direction LR \* --> Follower : 节点启动即为Follower Follower --> Candidate : 选举超时&未收到Leader心跳 Candidate --> Leader : 获得过半数(多数派)选票 Candidate --> Follower : 发现更高任期 / 他节点当选 Leader --> Follower : 发现更高任期(Leader自降级) Leader --> Leader : 周期性广播心跳AppendEntries
再看核心组件与节点间通信架构:
flowchart TB subgraph LeaderLeader 节点 LS"持久化状态\
currentTerm 任期 / votedFor 投给谁 / log 日志" LI"易失状态\
commitIndex 已提交位点 / lastApplied 已应用位点" LLOG"日志模块\
有序条目 entry(term,index,command)" LSM"状态机 StateMachine\
(上层的数据库/存储引擎)" LS --- LI --- LLOG --- LSM end subgraph F1Follower 节点1 FS1"持久化状态 + 日志 + 状态机(同构副本)" end subgraph F2Follower 节点2 FS2"持久化状态 + 日志 + 状态机(同构副本)" end LLOG -- "① AppendEntries RPC<br/>(心跳+日志复制+确认)" --> FS1 LLOG -- "① AppendEntries RPC" --> FS2 F1 -- "② RequestVote RPC<br/>(选举期请求投票)" --> LS F2 -- "② RequestVote RPC" --> LS FS1 -- "日志/状态反馈" --> LSM FS2 -- "日志/状态反馈" --> LSM
总结 :Raft 把所有复杂度 收敛成"一个 Leader + 两个 RPC "------AppendEntries(日常复制日志、兼当心跳)和 RequestVote(选举时拉票),再加"多数派确认"这一条铁律。
任意时刻只有一个 Leader 说了算,日志按任期 (term)单调推进,提交即落盘多数节点,从而同时解决选举、日志复制、安全与故障恢复四件事。
事务管理
Q: 数据库的 MVCC机制(事务的多版本并发控制) 与 Raft 协议(多副本一致性协议) 的区别与联系?(必读)
-
数据库的事务:基于 MVCC 多版本并发控制,保证隔离与一致性
-
数据库的多副本、容灾:基于 RAFT 协议 的全新多副本架构:节点自动选主、自动故障处理、自动故障恢复。
MVCC 管"一个节点内,读写互不打架";
Raft 管"多个节点间,数据不分裂"。
一个管【并发】,一个【管共识】,层次不同、互为补充。
| MVCC | Raft | |
|---|---|---|
| 层面 | 单节点 事务并发层(内存/存储) | 跨节点 分布式复制/一致性层 |
| 解决的问题 | 读写并发互不阻塞 + 事务隔离(读快照不锁写) | 多副本达成一致 + 选举主节点 + 故障恢复 |
| 核心手段 | 给数据存多个版本 ,按版本号/快照判定可见性 | 给日志打 term(任期)+index(序号),按多数派确认排序提交 |
| 目标 | 并发性能 + 隔离性 | 高可用 + 不丢数据 + 不分裂 |
联系(本质相通)
- 都在"给数据打版本、按序裁决":MVCC 用版本号裁决"谁能看到哪个版本",Raft 用 term+index 裁决"日志哪条先落盘"。
- 是互补的两层,不是二选一 :现代高可用数据库通常是"MVCC 负责单节点本地并发,Raft 负责跨节点副本共识"的组合------例如分布式数据库每个分片内部用 MVCC 处理并发事务,分片间的副本同步交给 Raft。
小结 :MVCC 让一个库 能同时服务很多人还不乱;Raft 让坏掉几台机器数据还是那份数据。一个对"并发"负责,一个对"可靠"负责。