数据库基础体系 · 第 132/139 篇。文章以各产品官方稳定版本的公开语义为准;示例会明确引擎、事务与部署边界。
分布式数据库中的时间与 ID:时钟、序列、Snowflake、UUID 和顺序
在单机程序中,“当前时间”“自增主键”和“插入先后”经常被直觉地联系在一起:
- 时间更早的记录,ID 更小;
- ID 更大的记录,后提交;
- 自增列没有间隙;
- 数据库返回的时间就是事务提交时间;
- UUID 虽然唯一,但天然无序。
在分布式系统中,这些关系通常都不成立。原因是“时间”“唯一性”和“顺序”是三个不同的问题:
- 时间:某台机器观察到的物理时刻是什么?
- 唯一性:两个参与者生成的值是否可能相同?
- 顺序:能否判断两个事件发生、提交、可见或被复制的先后?
理解分布式数据库中的 ID,首先要把这三个概念拆开。
一、先区分五种“顺序”
设事件 和 分别表示两个事务、写操作或消息。
1. 生成顺序
如果某个节点先生成 ID ,再生成 ID ,则称:
这通常只说明同一个生成器中的本地顺序。不同节点之间没有天然关系。
2. 时间顺序
如果物理时钟读数满足:
只能说明观察到的时间值如此。它不自动证明 先于 。机器时钟可能漂移、跳变,节点之间也可能存在时钟偏差。
3. 因果顺序
如果事件 影响了事件 ,例如:
- 的结果被 读取;
- 发送消息, 接收消息;
- 属于同一事务,且先于 执行;
则可以写作:
因果顺序通常比“时间戳大小”更可靠,但它只定义部分顺序。两个完全并发的事件之间可能不存在因果关系。
4. 提交顺序
数据库事务有自己的提交过程。事务先执行并不代表先提交:
事务 T1:开始 -> 执行写入 -> 长时间等待 -> 提交
事务 T2:开始 -> 执行写入 -> 很快提交
可能出现:
但:
ID 生成时间也可能早于提交时间。
5. 可见顺序
一个事务提交后,其他事务何时能读到它,取决于数据库的隔离级别、复制延迟、读路由和一致性协议。
因此:
这是本文后续所有讨论的基础。
二、物理时钟:时间戳不是全局真相
1. 墙上时钟与单调时钟
操作系统通常提供两类时间概念。
**墙上时钟(wall clock)**表示现实世界中的日期和时间,例如:
2025-03-08 10:15:20.123 UTC
它适合展示、审计和按时间范围查询,但可能发生回拨:
10:00:05
10:00:06
09:59:59 <-- NTP 校时或人工调整
10:00:00
**单调时钟(monotonic clock)**只用于测量持续时间。它不一定对应日历时间,但在进程运行期间不会因为校时而倒退,因此适合计算超时:
start = monotonic_now()
执行操作
elapsed = monotonic_now() - start
不能把单调时钟值直接写成业务创建时间,也不能把墙上时钟用于精确的超时判断。
2. 时钟偏差和漂移
在分布式系统中,节点 和节点 的本地时钟可能分别为:
A: 12:00:00.100
B: 12:00:00.080
即使 生成的事件实际更早,也可能观察到更大的时间戳。用时间戳排序,只能得到“按观察值排列的顺序”,不能自动得到真实发生顺序。
时钟偏差还会导致 Snowflake 一类算法遇到以下问题:
上一次时间:1000
本次时间: 999
如果算法直接使用时间戳拼接 ID,可能产生重复或逆序。因此生成器必须明确处理时钟回拨。
常见处理方式包括:
- 暂停生成,等待本地时钟追上;
- 拒绝请求并报警;
- 使用逻辑时间补偿回拨;
- 使用受控时间服务或数据库提供的时间;
- 将节点时间偏差控制在协议允许的范围内。
这些策略没有统一的正确答案。暂停会增加延迟,拒绝会影响可用性,逻辑补偿会使 ID 中的“时间部分”不再严格等于真实时间。
三、数据库中的时间语义
数据库函数返回的时间值,必须结合引擎、事务和语句边界理解。
1. PostgreSQL:事务时间、语句时间和实际时钟
以下示例针对 PostgreSQL:
BEGIN;
SELECT
current_timestamp AS tx_time_1,
statement_timestamp() AS stmt_time_1,
clock_timestamp() AS wall_time_1;
SELECT pg_sleep(2);
SELECT
current_timestamp AS tx_time_2,
statement_timestamp() AS stmt_time_2,
clock_timestamp() AS wall_time_2;
COMMIT;
在同一个事务中:
current_timestamp,以及 SQL 标准形式的CURRENT_TIMESTAMP,表示事务开始时间,因此两次查询通常相同;statement_timestamp()表示当前语句开始时间;clock_timestamp()返回函数执行时读取的实际时钟,前后调用可能不同。
预期现象类似:
tx_time_1 = tx_time_2
stmt_time_1 与 stmt_time_2 不同
wall_time_1 与 wall_time_2 约相差 2 秒
这意味着 PostgreSQL 中:
created_at DEFAULT current_timestamp
记录的是事务时间语义下的时间,而不是严格的提交时刻。
如果事务执行 10 分钟后才提交,created_at 仍然可能是事务开始时刻。若业务需要“提交后可见的时间”,不能仅靠这个列推断;应根据业务模型记录提交事件,或使用数据库内部的提交序列、变更日志等机制。
2. MySQL:语句内稳定的当前时间
以 MySQL 8.4 为例,NOW() 和 CURRENT_TIMESTAMP 在一条语句执行期间保持一致。这适合让一条语句写入统一的创建时间:
CREATE TABLE event_log (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
created_at TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
message VARCHAR(100) NOT NULL
);
INSERT INTO event_log (message)
VALUES ('a'), ('b');
SELECT id, created_at, message
FROM event_log
ORDER BY id;
同一条 INSERT 语句生成的多行,其默认时间通常相同。这个时间表示语句执行时数据库观察到的当前时间,不等价于严格的事务提交时间。
MySQL 还提供 SYSDATE(),其语义与 NOW() 不同:它可以在函数执行时读取实际时间,因此同一条语句内不同调用可能不同;具体行为还受服务器配置影响。需要一致时间时,不应随意把 SYSDATE() 和 NOW() 混用。
3. 时区、精度与比较
时间列还存在三个独立问题:
- 时区:存储的是 UTC、带时区时间,还是“无时区的本地日期时间”?
- 精度:秒、毫秒、微秒是否足够?
- 比较语义:按绝对时间比较,还是按用户所在时区展示?
时间戳精度更高,不代表顺序信息更强。例如两个并发事务都在同一微秒内生成时间,时间戳仍可能相同;时间戳不同,也不代表事务之间存在因果关系。
四、逻辑时钟:不依赖绝对时间表达顺序
1. Lamport 逻辑时钟
Lamport 时钟为每个节点维护一个整数 。
规则如下:
- 节点本地发生事件时:
- 节点发送消息时,将当前 一起发送;
- 节点收到携带时间 的消息时:
如果存在因果关系:
则一定有:
但反方向不成立。若 ,不能证明 ,因为两个并发事件也可能恰好得到不同的逻辑值。
例如:
节点 A:本地事件 a,L_A = 1
节点 B:本地事件 b,L_B = 1
此时 和 并发。若再采用节点编号作为并列时的排序依据,可以构造全序,但这个全序是人为打破平局,并不表示真实因果关系。
2. 向量时钟
向量时钟为每个节点维护一个向量。例如两个节点 A、B:
A 的事件: [1, 0]
B 的事件: [0, 1]
定义:
表示向量每个维度都不大于对方,并且至少一个维度严格小于对方。此时可以判断 可能因果先于 。
若:
[1, 0] 与 [0, 1]
互不小于对方,则表示并发。
向量时钟能表达更丰富的因果关系,但向量大小随参与者数量增长,因此不适合简单地作为所有数据库记录的 ID。
3. 混合逻辑时钟
混合逻辑时钟(HLC)通常把物理时间和逻辑计数结合起来:
(timestamp, logical_counter)
物理时间用于大致按现实时间排序,逻辑计数用于处理同一时刻或时钟回拨下的事件。
HLC 可以帮助复制协议、MVCC 或分布式事务维护因果边界,但它不是一个“所有数据库都保证的全局提交时间”。具体保证取决于数据库协议和部署方式。
五、数据库序列:唯一递增,不等于无间隙和提交顺序
1. PostgreSQL SEQUENCE 的核心语义
在 PostgreSQL 中,序列是数据库对象,不是普通表中的一列。示例:
CREATE SEQUENCE order_id_seq
START WITH 1000
INCREMENT BY 1;
SELECT nextval('order_id_seq'); -- 1000
SELECT nextval('order_id_seq'); -- 1001
序列的关键特点是:
nextval()在同一序列上提供并发安全的取值;- 返回值通常按调用顺序递增;
- 序列推进不随事务回滚;
CACHE可能让值提前分配到后端进程,因此故障时可能出现更大的间隙;- 序列值本身不表示某行已经提交或一定存在。
反例:
BEGIN;
SELECT nextval('order_id_seq'); -- 得到 1002
ROLLBACK;
SELECT nextval('order_id_seq'); -- 得到 1003
1002 不会因为事务回滚而回收。这是有意设计的:如果序列值也参与严格事务回滚,并发事务会为等待回滚而付出复杂的锁和性能代价。
2. CACHE 与故障间隙
假设序列配置:
CREATE SEQUENCE s CACHE 10;
数据库可能一次为某个后端预留一段值。进程异常退出时,尚未使用的预留值可能丢失。因此看到:
1000, 1001, 1010
不代表数据库产生了错误,也不代表中间记录被删除。
如果业务要求“发票号码连续且不能跳号”,普通序列不是合适工具。连续编号需要专门的、通常带事务锁竞争的编号流程,并且还要定义取消、作废和并发分配的法律或业务规则。
3. 序列与表插入的反例
CREATE TABLE t (
id bigint PRIMARY KEY DEFAULT nextval('order_id_seq'),
payload text NOT NULL
);
BEGIN;
INSERT INTO t(payload) VALUES ('first'); -- 分配 1004
-- 事务暂不提交
BEGIN; -- 另一个会话
INSERT INTO t(payload) VALUES ('second'); -- 分配 1005
COMMIT;
-- 第一个会话回滚
ROLLBACK;
最终表中可能只有 1005,而 1004 不存在。更重要的是,1004 先分配并不表示它先提交。
4. MySQL AUTO_INCREMENT
MySQL 的 AUTO_INCREMENT 也主要用于生成唯一键,而不是生成无间隙的业务编号。并发插入、事务回滚、服务器重启、批量插入以及存储引擎的分配策略都可能产生间隙。
例如:
CREATE TABLE orders (
id BIGINT NOT NULL AUTO_INCREMENT,
payload VARCHAR(100) NOT NULL,
PRIMARY KEY (id)
) ENGINE = InnoDB;
START TRANSACTION;
INSERT INTO orders(payload) VALUES ('will rollback');
ROLLBACK;
INSERT INTO orders(payload) VALUES ('committed');
SELECT * FROM orders;
第二次插入得到的 id 可能大于第一次已经分配过的值。具体的分配细节受 MySQL 版本、存储引擎和并发模式影响,但“回滚后不保证回收并重用”是应用不应依赖连续性的核心事实。
在复制、分片或多主部署中,每个节点独立分配自增值还可能发生冲突,因此通常需要不同的起始值、步长或外部 ID 生成器。这些方法可以避免碰撞,却不会自动提供全局严格顺序。
六、Snowflake:带时间结构的分布式 ID
1. Snowflake 不是唯一固定标准
“Snowflake ID”通常指一类 64 位左右的整数布局,最常见的思想是:
符号位 | 时间差值 | 节点标识 | 同毫秒序列号
经典实现常见为:
- 1 位符号位;
- 41 位毫秒时间差值;
- 10 位节点标识;
- 12 位同毫秒序列号。
但位数、起始时间、节点字段和回拨策略都可以改变。因此不能看到一个 64 位整数,就断言它一定是某个特定 Snowflake 实现。
2. 一个具体编码算例
假定:
epoch = 2020-01-01T00:00:00Z
当前时间差 delta = 5 毫秒
worker_id = 3
sequence = 7
采用:
timestamp_bits = 41
worker_bits = 10
sequence_bits = 12
则编码为:
代入:
其中:
delta决定大致时间顺序;worker_id区分不同生成节点;sequence区分同一节点、同一毫秒内的多个请求。
若同一节点在同一毫秒连续生成:
delta = 5, worker = 3, sequence = 0
delta = 5, worker = 3, sequence = 1
delta = 5, worker = 3, sequence = 2
生成的整数递增。
3. 生成器状态与并发流程
一个生成器至少维护:
last_timestamp
sequence
worker_id
生成过程可以写成:
now = current_millisecond()
if now < last_timestamp:
处理时钟回拨
if now == last_timestamp:
sequence = sequence + 1
if sequence 超出上限:
等待下一毫秒
else:
sequence = 0
last_timestamp = now
返回编码结果
并发实现必须保证这段状态更新是原子的。可以使用互斥锁、CAS 或单线程事件循环;否则两个请求可能读到相同的 last_timestamp 和 sequence,产生重复 ID。
4. 容量推导
若同一节点每毫秒的序列字段有 位,则一个毫秒最多生成:
个 ID。
经典 12 位序列字段最多为:
个 ID/毫秒,即理论上约 409.6 万个 ID/秒,但这是单节点、无等待、无锁竞争和实现正确时的位级上限,不是可直接承诺的业务吞吐量。
若某毫秒内请求超过上限,常见做法是等待下一毫秒。此时延迟会上升;若不等待而重置序列,就会产生重复 ID。
5. 时钟回拨是 Snowflake 的关键故障路径
假设生成器状态:
last_timestamp = 1000
下一次读取到:
now = 998
如果直接编码,新的 ID 可能小于上一个 ID,甚至在某些实现中与历史 ID 冲突。
可选策略:
暂停等待
等待本地时间 >= 1000
优点是容易保持时间字段单调;缺点是时钟回拨期间请求阻塞。
拒绝生成
抛出错误并报警。适用于不能接受错误 ID 的场景,但会降低可用性。
逻辑补偿
将有效时间设置为:
effective_timestamp = max(now, last_timestamp)
并通过额外序列区分回拨期间的请求。这样可以保持 ID 单调,但 ID 的时间字段不再代表真实墙上时间,且必须防止补偿窗口耗尽。
依赖节点分配和时钟治理
为每个节点分配稳定 worker_id,监控时钟偏差,并在部署环境中控制时间同步。这减少故障概率,但不能替代生成器内部的回拨处理。
6. Snowflake ID 保证什么,不保证什么
在节点标识不重复、状态更新正确、时钟策略满足约束的前提下,Snowflake 通常可以提供:
- 分布式唯一性;
- 近似按生成时间递增;
- 适合使用整数主键;
- 可从 ID 中提取大致生成时间。
它通常不能保证:
- 严格的全局真实时间顺序;
- 严格的事务提交顺序;
- 严格的跨节点生成先后;
- 时钟回拨、节点 ID 配置错误或状态丢失后的绝对安全;
- 业务数据已经提交或存在。
例如节点 A 在 12:00:00.001 生成 ID 后暂停,节点 B 在 12:00:00.002 生成 ID 并提交。A 的事务随后在 12:00:01 提交。此时 Snowflake ID 的顺序与提交顺序相反。
七、UUID:优先解决命名空间和唯一性
UUID 是 128 位标识符。它首先解决的是“不同生成者产生相同值的概率或可能性”,并不天然解决数据库索引顺序。
1. UUID v4:随机型
UUID v4 主要使用随机位。它的优点是:
- 不依赖中心序列;
- 不暴露时间和节点信息;
- 适合客户端离线生成。
缺点是随机写入 B-tree 索引时局部性较差。新值可能落到索引的任意位置,引起更多页面分裂和缓存不命中。这个问题是存储访问模式问题,不是 UUID 正确性问题。
2. UUID v1:时间与节点信息
UUID v1 包含时间字段、时钟序列和节点相关信息。它具有一定的时间结构,但会带来隐私和部署方面的考虑,例如可能暴露生成时间或节点信息。其字符串表现和二进制字段排列也不等同于数据库中的时间排序。
3. UUID v7:面向时间排序的 UUID
UUIDv7 使用 Unix 毫秒时间戳作为高位字段,并保留随机字段。其目标是比完全随机的 UUID 更适合按时间插入和索引。
但必须注意:
- 时间字段通常只有毫秒精度;
- 同一毫秒内需要依赖随机字段或实现提供的单调序列;
- 不同节点的生成顺序仍可能与提交顺序不同;
- 按字符串排序、按原始 16 字节排序、按数据库特定字节布局排序,结果可能不同;
- “大致有序”不等于“全局严格连续”。
UUIDv7 更准确的理解是:把时间局部性加入 UUID 的唯一性方案,而不是把 UUID 变成全局事务序列。
4. PostgreSQL 中的 UUID 主键
PostgreSQL 的 uuid 类型以 128 位值存储和比较:
CREATE TABLE api_request (
request_id uuid PRIMARY KEY,
payload text NOT NULL,
created_at timestamptz NOT NULL DEFAULT current_timestamp
);
INSERT INTO api_request(request_id, payload)
VALUES ('550e8400-e29b-41d4-a716-446655440000', 'hello');
SELECT request_id, created_at, payload
FROM api_request;
示例中的 UUID 是手工提供的。数据库的 uuid 类型负责存储、比较和索引,但“是否由客户端生成”“采用 v4 还是 v7”“如何保证跨服务一致”属于应用和部署设计。
不要把 UUID 主键的排序当作创建顺序:
SELECT *
FROM api_request
ORDER BY request_id;
这只是按 UUID 值排序,不是按创建时间、提交时间或事件因果排序。
5. MySQL 中的 UUID 存储
MySQL 可以使用 CHAR(36) 存储带连字符的文本 UUID,也可以使用 BINARY(16) 存储紧凑的二进制形式:
CREATE TABLE api_request (
request_id BINARY(16) PRIMARY KEY,
created_at TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
payload VARCHAR(100) NOT NULL
);
SET @u = UUID();
INSERT INTO api_request(request_id, payload)
VALUES (UUID_TO_BIN(@u), 'hello');
SELECT BIN_TO_UUID(request_id), created_at, payload
FROM api_request;
BINARY(16) 比 CHAR(36) 更紧凑,也避免字符串比较和字符集带来的额外成本。
MySQL 提供 UUID_TO_BIN(uuid, swap_flag) 和 BIN_TO_UUID()。swap_flag 的特殊重排主要针对 UUID v1 的时间相关字段,以改善索引中的时间局部性;不能把这个参数误认为适用于所有 UUID 版本,也不能由此推导出严格的全局顺序。
八、ID 顺序与数据库索引顺序
B-tree 索引适合范围扫描和局部插入。若连续插入的键大致递增,写入通常更集中在索引右侧;若键完全随机,写入会分散到多个页面。
因此:
- 单调递增整数通常有较好的索引局部性;
- Snowflake 通常提供近似递增的整数;
- UUID v4 通常较随机;
- UUID v7 通常比 v4 更有时间局部性;
- UUID 文本和二进制的排序布局必须实际验证。
但索引顺序并不是业务事件顺序。即使主键递增,也可能发生:
事务 T1:拿到 id=10,未提交
事务 T2:拿到 id=11,先提交
事务 T1:后提交
按主键查询得到的顺序是 10, 11,按提交顺序却是 11, 10。
如果业务需要事件顺序,应显式建模,例如:
CREATE TABLE account_event (
event_id bigint PRIMARY KEY,
account_id bigint NOT NULL,
event_type text NOT NULL,
created_at timestamptz NOT NULL,
payload jsonb NOT NULL
);
其中:
event_id负责唯一定位;created_at负责展示或时间范围过滤;- 若每个账户必须严格有序,还需要账户级序列、版本号或由单一协调者分配的
account_version; - 若需要判断跨节点因果关系,则应保存因果版本、日志偏移量或协议提供的提交位置。
不要让一个字段同时承担所有语义。
九、分布式数据库中的 ID 生成路径
一个常见的写入路径如下:
客户端
|
| 生成 UUID / 请求 ID
v
路由层
|
| 根据分片键选择节点
v
分片节点
|
| 分配本地序列或调用 Snowflake
v
复制协议 / WAL / Raft 日志
|
| 达成提交条件
v
客户端收到成功响应
这里至少存在四个时间点:
t1:客户端或服务生成 ID
t2:数据库执行 INSERT
t3:复制协议确认或本地事务提交
t4:客户端收到响应
通常只有部分关系成立:
但在重试、异步队列和跨服务调用中,客户端观察到的时间还会受到网络延迟影响。若客户端在超时后重试:
第一次请求:数据库已提交,但响应丢失
第二次请求:客户端再次发送
如果 ID 每次重试都重新生成,可能产生两条业务记录;如果使用同一个幂等键,数据库可以拒绝重复提交或返回原结果。
因此:
- 请求 ID适合追踪一次请求;
- 幂等键适合识别同一业务操作的重试;
- 主键适合唯一定位一行;
- 事件序列适合表达同一实体的变化顺序。
它们可以是同一个值,但不应因为方便就默认它们语义相同。
十、常见误解与诊断方法
误解一:ID 越大,记录越新
只有在 ID 生成器、时钟、提交模型和查询节点都满足额外约束时,这个结论才可能近似成立。分片、并发事务、回滚和复制都会破坏它。
诊断时应同时记录:
id
应用生成时间
数据库写入时间
事务提交或日志位置
节点标识
请求 ID
分别排序比较,而不是只看主键。
误解二:时间戳相同就表示同时发生
相同精度的时间戳只能说明它们在该精度下相同。它们可能来自不同事务、不同节点,甚至相隔数百微秒但被截断为同一毫秒。
需要排序时,应增加唯一的并列键:
ORDER BY created_at, event_id
但这仍然是确定性排序,不等于恢复了真实因果顺序。
误解三:序列应该回滚
序列的主要目标是并发分配唯一值,而不是提供无间隙账本编号。回滚不回收序列值,正是为了避免事务与序列分配之间形成严重锁竞争。
误解四:UUID 天然无序,所以不能做主键
UUID 可以做主键,但要评估:
- 数据量和索引大小;
- 生成位置;
- UUID 版本;
- 二进制还是文本存储;
- 插入局部性;
- 是否需要按时间范围扫描。
UUID v4 在分布式生成和隐私方面有优势;Snowflake 在整数索引和大致时间排序方面有优势;UUIDv7 在标准化的 128 位格式与时间局部性之间折中。它们不是简单的优劣关系。
误解五:数据库时间就是提交时间
PostgreSQL 的 current_timestamp 是事务开始时间语义;MySQL 的 NOW() 在语句内稳定,但都不应被直接当作严格提交时间。若提交顺序是业务要求,必须使用数据库提供的提交序列、复制日志位置或显式版本协议。
十一、如何选择
可以按实际语义选择,而不是按“看起来先进”选择。
选择数据库序列或自增键
适合:
- 单一数据库或单一分片内生成;
- 主要需求是高效唯一键;
- 不要求跨分片全局连续;
- 不把主键解释为提交时间。
选择 Snowflake 类 ID
适合:
- 多节点独立生成整数 ID;
- 希望 ID 大致按时间递增;
- 需要较紧凑的索引键;
- 能够管理 worker ID、时钟同步和回拨故障。
必须明确节点标识的生命周期。节点 ID 重复是直接导致 ID 冲突的高风险错误。
选择 UUID v4
适合:
- 客户端或边缘节点离线生成;
- 不希望暴露时间或节点信息;
- 更看重命名空间隔离和随机性;
- 可以接受随机索引写入的代价。
选择 UUID v7 或其他时间有序 UUID
适合:
- 需要 128 位分布式 ID;
- 希望改善随机 UUID 的索引局部性;
- 需要在不同语言和服务之间使用统一格式;
- 接受它只提供近似时间顺序,而不是提交顺序。
只使用时间戳
通常不适合作为分布式唯一主键。相同精度内可能并发碰撞,不同节点之间还存在时钟偏差。时间戳更适合作为查询、审计和展示字段,而不是唯一性机制。
十二、最终的语义边界
一个可靠的分布式数据模型,通常会把不同职责分开:
主键 / ID -> 唯一定位
时间戳 -> 记录观察到的时间
序列 / 版本号 -> 表达局部或协议定义的顺序
提交位置 -> 表达复制或事务协议中的先后
幂等键 -> 识别同一次业务请求
因果元数据 -> 表达跨节点依赖关系
如果要求“严格顺序”,还必须回答三个问题:
-
顺序覆盖什么范围?
单个账户、单个分片、一个数据库,还是整个集群? -
顺序依据是什么?
生成、接收、执行、提交、复制确认,还是对外可见? -
冲突如何处理?
两个并发事件是否允许任意排序,还是必须由一个协调者裁决?
只有在这些问题明确后,才能判断应该使用序列、Snowflake、UUID、逻辑时钟、事务版本,还是复制协议提供的日志位置。
ID 能提供唯一性,时钟能提供时间观测,序列能提供某个生成器内的分配顺序;它们都不能单独替代分布式数据库的一致性协议。
系列导航与关联阅读
- 系列入口:数据库完整学习路线:从关系模型、事务索引到分布式与向量检索
- 上一篇:数据库共识基础:Raft、Paxos、Leader、日志复制和成员变更
- 下一篇:数据库代理与网关:连接复用、读写路由、分片、审计和故障边界
- 延伸:分布式数据库一致性模型:线性一致、顺序一致、因果和最终一致
- 延伸:数据库复制、分片与高可用:一致性、路由、故障转移和扩容
官方资料
本文依据数据库官方文档重新梳理;正文、示例与生产检查清单由 WR BLOG 编写。

评论
0 条讨论