第 11 篇在单库事务上谈隔离与锁。上了集群之后,读者最容易混的三件事是:写安全靠什么、读扩展副本会不会立刻看见提交、以及「因果一致」是否等于更强的隔离级别。本篇只钉内核语义边界——不写种子、备份、跨机房安装步骤。
本文是「图数据库内核」系列第 12 篇(共 16 篇)。→ 系列目录
篇目 核心内容 第 11 篇 · 事务与锁 单机 read-committed 与锁表 第 12 篇 · HA 边界 拓扑角色、复制、书签、路由 第 13 篇 · TinkerPop / JanusGraph 外置存储上的图层
版本锚定:Operations Manual(2026.07 / current)Introduction: Neo4j clustering architecture、Leadership, routing, and load balancing;Driver 手册 Bookmarks(因果链用法)。产品文档称 clustering;4.x 口中的 Causal Cluster / Core / Read Replica 在 5.x 叙事里拆成 server 池 + 每库 primary/secondary 拓扑。本篇沿用手册现行术语,并保留「因果一致」作为一致性模型名。Enterprise Edition。不粘贴本机
SHOW SERVERS/ 路由表输出。
一、先分清:服务器 ≠ 数据库角色
手册 Overview:集群是一组服务器跑多个数据库;服务器提供算力与存储,数据库各自有独立拓扑,拓扑由 primary 与 secondary(读扩展)组成。
关键句:
primary/secondary是某数据库副本的角色,不是服务器永久身份。- 服务器可用
modeConstraint(PRIMARY/SECONDARY/NONE)限制自己能挂哪种角色;NONE时同一台机可对库 A 当 primary、对库 B 当 secondary。 - 一个库也可以只挂在一台服务器上——此时该分配仍是 primary(single primary),只是没有写容错。
flowchart LR
subgraph servers ["Server pool"]
S1["Server 1"]
S2["Server 2"]
S3["Server 3"]
S4["Server 4"]
end
subgraph dbA ["Database A topology"]
P1["primary"]
P2["primary"]
P3["primary"]
Sec["secondary"]
end
S1 --- P1
S2 --- P2
S3 --- P3
S4 --- Sec
运维动作(ENABLE SERVER、ALTER DATABASE … TOPOLOGY)改变的是分配与约束;本系列关心的是:一次
COMMIT 在哪类副本上变成「可安全读」。
二、Primary:写路径、Raft 与多数
2.1 谁能写
- 只有 primary 有资格成为该库的 writer。
- 任意时刻,该库的多个 primary 中自动选出一个 writer;writer 可随时间变化。
- Writer 同步把写推到其他 primary,并在收到足够成员确认前不完成提交(手册:does not allow a commit to be completed until it receives confirmation that the data has been written to enough members)。
Leadership, routing, and load balancing:一致性与安全靠 Raft;Leader 给底层日志定序,Follower 复制 Leader 状态。对 Neo4j 而言,写由当前该库的 Leader(即 writer)排序。每个数据库是逻辑上独立的 Raft 组——一台服务器可以对库 X 当 Leader、对库 Y 当 Follower。
2.2 容错心智:\(M = 2F + 1\)
手册用 \(M = 2F + 1\):要容忍 \(F\) 个 primary 故障,需要 \(M\) 个 primary。常见落点:
| 拓扑 | 写可用性直觉 | 备注 |
|---|---|---|
| 1 primary | 无写容错;盘坏则耐久性也丢 | 延迟最低档 |
| 3 primaries | 多数部署:可丢 1 个仍写 | 手册默认推荐档 |
| 5 primaries | 可丢 2 个仍写 | 写需联系更多成员,延迟上升 |
| 上限 11 | 手册允许但不推荐堆满 | 写延迟与协调成本 |
要区分三档「丢失」难度(手册从易到难):写可用性 → 读可用性 → 耐久性。Primary 挂了太多时库会变成 read-only(不能再处理写)。
与第 11 篇的交叠:锁与隔离仍发生在执行该事务的成员上;集群多数确认的是提交日志的安全复制,不是把全图锁表广播成全局 Serializable。
三、Secondary:异步追赶与只读扩展
Secondary 是从 primary 经 transaction log shipping 异步复制的库副本:周期性向上游成员拉取新事务。职责是放大只读查询——手册称其像图数据的 cache,可跑任意只读查询与过程。
边界事实:
- 丢 secondary 不影响库可用性与 primary 容错;只减吞吐。
- 不保证立刻可见最新提交:按自己的节奏 pull,可能暂时落后;「最近提交的事务未必马上在 secondary 上可见」。
- Secondary 提供某种耐久性缓冲(已提交数据可在别处仍有拷贝),但不能单独当作写安全的多数——写安全在 primary Raft 侧。
读扩展的默认产品叙事:路由表把读导向 reader,让 writer 专注写(见第五节)。这对第 05 / 09 篇意味着:同一条 Expand 计划在 secondary 上跑时,吃的是该副本本地的 page cache 与 store——拓扑角色改变的是「数据新鲜度与路由」,不是邻接布局算法本身。
四、因果一致:书签如何改写「提交可见」
4.1 模型
手册 Causal consistency:因果一致保证因果相关操作在系统各实例上以相同顺序被看到;对客户端即 read-your-writes——不论连到哪台实例,都能至少读到自己写过的内容。例子:创建账号的写,在同一用户随后登录读时必须已在。
实现把手是 bookmark:
- 事务执行后,客户端可拿到 bookmark(表示库的某一状态);
- 后续事务带上该 bookmark;
- 集群只让已处理过该书签事务所对应状态的服务器执行下一事务——形成 causal chain。
其余路由由集群与 Driver 的 topology 协作完成:写进 primary,读可落到 secondary。
4.2 与第 11 篇隔离的差一层
| 轴 | 第 11 篇(单事务语义) | 本篇(跨成员可见性) |
|---|---|---|
| 默认隔离 | read-committed;遍历可被并发改 | 不因上集群而自动升为 Serializable |
| 自己刚提交的写 | 同连接/同成员上通常立刻可见 | 跨会话、跨 secondary 时靠 bookmark 保证 |
| Lost update / 索引 scan 异常 | 仍存在 | 仍存在;复制不消除 |
| 死锁 | 写路径上的瞬时错误 | 仍在处理写的 primary/writer 上发生 |
Driver 手册补充(用法级):同一 session
内 bookmark 多由驱动维护,因果链默认成立;跨 session / 与
execute_query 混用时才显式管
bookmark。只有需要跨事务因果时才该付等待成本——bookmark
等待会拖延迟。
工程判断:把「RC 下路径中段被改写」和「secondary 尚未追赶到 bookmark」当成两类 bug。前者靠锁或重试;后者靠因果链或接受最终可见。
sequenceDiagram
participant App
participant Writer as Primary writer
participant Sec as Secondary
App->>Writer: WRITE txn
Writer-->>App: commit + bookmark B
App->>Sec: READ with bookmark B
Note over Sec: wait until caught up to B
Sec-->>App: read-your-writes
五、路由:客户端表、服务端转发、读写分流
5.1 Client-side routing
Driver 向集群要 routing table(也可用
dbms.routing.getRoutingTable()):按库列出
writers / readers /
routers。通常一个 writer;默认配置下
writer 不在 readers
列表里,以便写节点专注写负载。Driver 用
neo4j:// 做路由;bolt://
直连指定主机、不做路由。
5.2 Server-side routing
写落到非 Leader 成员时,若开启
dbms.routing.enabled
等配置,可由服务器内部把查询转到能写的成员。这是对「连错角色」的补救,不是第二种隔离模型。
5.3 与内核代价的关系
- 写延迟下限受 primary 多数确认与跨成员网络约束——不是单机 WAL 刷盘故事的简单外推。
- 读延迟下限仍是第 05 / 09 / 10 篇:page cache、Expand、基数;secondary 再多也救不了爆炸路径。
- 选举频繁(手册:环境抖动、过载)会扰动写路径——排障时与「Cypher 计划变差」分开看。
六、刻意不展开的边界
| 话题 | 本篇立场 |
|---|---|
| 安装、seed、备份恢复、跨 DC 配方 | 运维手册;不写步骤 |
| Aura 拓扑与定价 | 产品边界;不写 |
| Composite database(5.x 取代 4.x Fabric 代理叙事) | 多图联邦 / 分片访问入口;不存储数据;不替代单库 HA;跨 constituent 的锁与全局隔离不在本系列内核主线 |
多库 system 库的 primary/secondary |
有独立配置轴;只记「也有拓扑,但用服务器配置而非
CREATE DATABASE 拓扑」 |
| 图数据水平分片算法 | Composite / 应用分片;非 store format 主题 |
系列 PLAN 已写明:复制集 / Fabric(现 Composite)分布式查询全书不做——本篇只够回答「单图提交可见如何跨副本」。
七、争论与开放问题
- 多数同步 primary vs 异步 secondary:A 侧用 Raft 多数换写安全与可预测耐久;B 侧用 secondary 换读 QPS,接受滞后。因果书签是两者之间的客户端契约,不是把 secondary 提升成同步投票者。
- 「集群 = 更强一致」误解:产品卖的是容错 + 读扩展 + 可选因果链;默认隔离仍是第 11 篇的 RC。把 HA 当 Serializable 替代品会误诊 lost update。
- 开放问题:bookmark 等待与 secondary 追赶延迟在幂律热点写下的可观测性;多库各自 Raft 组时客户端会话跨库的因果组合;Composite 查询下「部分图已见写、部分未见」的应用层协议——手册给了机制零件,生产惯例仍分散。
谱系:因果一致词汇接分布式一致性模型谱系(Lamport 因果序以降);工程落地把手是 bookmark + 路由表,而不是应用自己轮询「是否复制完」。Raft 定序侧与站内 TiKV RaftStore 同属「日志定序再应用」家族,但 Neo4j 的 secondary catch-up 与 TiFlash learner 叙事不要一一对号——角色与产品边界不同。
八、来源与实验台账(本篇)
| 结论 | 等级 | 来源 |
|---|---|---|
| 四特性:可扩展、容错、可运维、因果一致;server/db 解耦;primary/secondary 定义 | A | Ops Manual Clustering introduction(2026.07 / current) |
| Writer 同步多数确认;secondary 异步 log shipping 与滞后;容错公式与推荐个数 | A | 同上 |
Raft Leader/Follower、每库独立 Raft 组、client/server
routing、neo4j:// vs bolt:// |
A | Leadership, routing, and load balancing |
| Session 内外 bookmark 行为与性能提示 | A | Neo4j Driver 手册 Bookmarks |
| Composite 取代 4.x Fabric、不存数据 | A | Ops Manual Composite databases concepts |
| 本机集群拓扑 / 路由表实测 | 未跑 | 不贴伪造输出 |
九、小结
- 角色在库副本上,不在服务器皮肤上;写安全看 primary 多数,读扩展看 secondary。
- Secondary 异步追赶:默认可滞后;bookmark 因果链才是跨成员 read-your-writes 的产品契约。
- 集群不升级隔离级别:第 11 篇的 RC / 锁 / 死锁故事仍在写路径成员上成立。
- 下一篇:离开原生 store,看 TinkerPop / JanusGraph 把图层架在外置存储上时,邻接与事务边界如何改写。
→ 系列目录 · 上一篇:事务与锁 · 下一篇:TinkerPop / JanusGraph
同主题继续阅读
把当前热点继续串成多页阅读,而不是停在单篇消费。
【图数据库内核】图引擎全景:属性图作为一等公民的存储与遍历
定位属性图相对行存/LSM/向量/GraphRAG 的生态位;钉住邻接代价、Neo4j store format(record→block)、page cache、索引与 Expand、Cypher 计划五条坐标系,并以 Angles & Gutierrez 谱系与原生图争论收束系列路线。
【图数据库内核】邻接的代价模型:边表 JOIN、CSR 与原生指针为何不是同一件事
把同一逻辑图落成边表+索引、CSR、原生关系链与 Neo4j block 内联四条路径,用统一代价语言比较一次 hop 与 k 跳扩张;钉住幂律超节点与局部性,为后续 record/block 布局篇垫底座。
【图数据库内核】Neo4j record 系布局:节点、关系链、属性链与 dense group
以 Neo4j 5.26 record-storage-engine 源码钉住 standard/aligned 固定记录:15B 节点、34B 关系、41B 属性、25B relationship group;讲清 sparse 链、dense 阈值与一次 hop 的指针路径。
【图数据库内核】Neo4j Block format:128 B 主块、动态溢出与 dense B+ 树
对照第 03 篇 record 指针链,钉住 Enterprise 推荐的 block 布局:block.x1.db 128 B 主块、node/relationship 动态 store、big_values、relationship dense 多根 B+ 树与实体上限;重写一次 hop 的读路径。