分布式 SQL:何时该使用 Spanner、CockroachDB 和 YugabyteDB
了解分布式 SQL 何时值得承担成本,Spanner、CockroachDB 与 YugabyteDB 如何比较,以及如何安全规划多区域工作负载。

分布式 SQL 是什么
分布式 SQL 是一种关系型数据库架构,它将数据和事务处理分散到多台机器上,同时向应用呈现一个逻辑 SQL 数据库。它保留表、联接、索引、约束和 ACID 事务,并增加自动分区、复制和故障恢复能力。
当一个系统具备以下特征时,通常可归入这一类:
- 关系型模式和 SQL 查询接口
- 可跨数据库节点水平扩展
- 跨分区的事务一致性
- 自动复制和故障切换
- 作为一个逻辑数据库协同运行
这个定义很重要,因为仅给 PostgreSQL 或 MySQL 添加只读副本,并不会让它变成分布式 SQL。主库加副本仍会把写入送到一台主服务器。由应用管理的分片能分散写入,但应用必须决定记录存在哪里,以及跨分片操作如何执行。分布式 SQL 将很大一部分责任交给数据库。
介于传统 RDBMS 与 NoSQL 之间
分布式 SQL 结合了传统 RDBMS 的关系型编程模型,以及分布式数据存储的横向扩展设计。只要主实例能处理写入负载,并且区域故障时不需要在别处持续写入,传统 PostgreSQL 和 MySQL 部署就能很好地工作。只读副本、缓存、连接池和更好的索引,足以将这一模式延续很多年。
许多 NoSQL 数据库通过限制联接、事务或一致性保证,换取更容易的分布式能力。对于大型事件流、可随时丢弃的缓存,以及很少参与多行事务的记录,这些取舍依然合理。关系型集群需要承担更多协调工作,因为应用希望在数据分散到多个节点后,约束和事务仍然有效。
实际区别在于谁承担复杂性。手动分片时,应用团队需要实现路由、再平衡数据、协调模式变更,并处理涉及多个分片的操作。使用分布式 SQL 时,数据库提供这些机制,但工程师仍要为网络化系统设计模式和查询。
它要解决的问题
分布式 SQL 面向这样的应用:其可用性、地理部署或写入增长已经超出单主架构的能力。常见例子包括全球 SaaS 服务、不能超卖的预订系统,以及在节点故障后仍须保持约束的金融账本。
它可以免去应用层分片,减少对单个写入地点的依赖,也能将数据放在用户附近或获批准的司法辖区内。但这些收益有代价:更多副本、更多网络流量、更多协调,以及单台服务器不存在的故障模式。
当工作负载能轻松放入一个区域时,传统托管关系型数据库仍是更好的默认选择。只有当自定义分片、区域故障切换或地理数据控制会变成一套庞大的独立工程系统时,分布式 SQL 才值得付出成本。
分布式 SQL 的底层工作方式
分布式 SQL 通过把数据划分为带副本的分区,并借助共识和分布式事务协议协调变更来工作。数据库将大部分机制隐藏在 SQL 之后,但其行为仍会影响延迟、吞吐量、模式设计和事故响应。
分区决定记录存放位置
集群会将逻辑表拆成更小的单元,这些单元可在节点间独立移动。Spanner 常把它们称为 splits,CockroachDB 使用 ranges,YugabyteDB 使用 tablets。每个单元覆盖表或索引键空间的一部分。
分区边界可能基于范围、哈希或明确的地理规则。按客户标识排序的范围便于扫描相关记录,但单调递增的标识符可能把新写入集中到一个分区。哈希分布能更均匀地分散写入,但会让有序扫描或租户放置更难。许多生产模式会将租户标识与另一个值组合,让相关数据便于访问,同时避免所有写入集中在一个位置。
二级索引也需要独立的分布式存储。因此,一次对单行的写入可能更新基表,以及位于不同分区的多个索引条目。单服务器上成本很低的索引,在集群中可能增加共识工作和网络流量。
复制与共识保护每个分区
每个分区通常有多个副本,共识组决定哪些变更序列会被接受。CockroachDB 和 YugabyteDB 使用基于 Raft 的复制。Spanner 使用基于 Paxos 的复制,并配合其时间基础设施。
一个 leader 或 leaseholder 负责协调副本组的写入。系统会将变更记录到足以构成法定多数的副本上,才视为已提交。若某个节点消失,只要法定多数仍可用,幸存成员就能选出或指定新的协调者。
法定多数是一项数学要求,不承诺每次故障都无害。三副本组通常可容忍一个副本不可用。若失去两个成员,剩下的副本无法安全接受写入,因为它无法证明其他多数派没有在别处继续推进。跨故障域放置副本与副本数量同样重要。
分布式事务协调多个分区
只涉及一个分区的事务通常可用较少协调完成。涉及多个分区的事务需要一个共同的提交决定,使所有参与者要么应用写入,要么全部中止。
具体协议因产品而异,但通常包括读取或锁定相关版本、验证并发变更、复制意图或暂存记录,以及最终确认提交。长事务会扩大冲突窗口。大批量操作可能涉及许多共识组,即使每条语句看起来简单,也会造成延迟尖峰。
这就是为什么要按网络特性设计事务。若数据库支持,可将相关行放在兼容的分区前缀下。让事务保持简短,不要在事务开启时等待外部服务,也不要在未测量影响的情况下,把数千条无关记录放进一个原子单元。
时间与顺序需要明确机制
分布式节点没有完全同步的物理时钟,因此每种产品都需要安排事务顺序的方式。Spanner 使用 TrueTime 不确定性边界和提交等待来提供外部一致性。其他系统可结合物理时钟、逻辑组件、依赖跟踪和事务协议。
时钟协调会影响可串行化执行、follower reads 和快照等操作。应用应使用数据库事务时间戳,而不是假定不同应用服务器生成的时间戳能建立可靠的全局顺序。
本地性决定网络路径
本地性配置决定副本位于何处,以及哪个区域协调一条记录的写入。只要合适的副本靠近调用方,读取可以很快。强顺序写入仍必须到达满足法定多数所需的副本,因此延迟取决于所选拓扑。
好的放置方案应遵循工作负载,而不是公司的架构图。如果某个欧盟租户的大多数写入来自欧洲,将其写入协调者放在欧洲,就能避免每次事务开始时的洲际往返。一个由所有区域共同更新的全局记录,例如计数器,不可能对每位写入者都在本地,并可能成为争用点。
何时应选择分布式 SQL
当地域韧性、水平写入能力或跨分区正确性的重要程度,足以证明持续协调的成本合理时,分布式 SQL 是正确选择。大型公司不一定需要它,小型产品若承诺严格的区域可用性,也可能需要它。
值得评估的条件
当以下多项条件同时存在时,应认真评估:
- 服务必须在可用区或区域中断时继续运行
- 写入需求接近单个主数据库的实际极限
- 手动分片会消耗大量应用工程时间
- 事务必须跨节点或地点保持正确
- 记录需要可强制执行的地理放置
这些条件应有数据支撑。定义所需的恢复时间目标、恢复点目标、事务延迟、峰值写入速率和故障域。模糊的“全球扩展”需求不足以选择架构。
仅有各地用户还不构成决定性理由。以内容为主的应用可以将 Web 服务器和缓存部署在用户附近,同时保留一个数据库区域。如果能接受稍旧的结果,只读副本也能支持各区域的浏览。真正更有必要的是:多个地点的用户必须针对相关数据进行低延迟写入。
适合更简单数据库的条件
当流量适中、写入来自一个区域、恢复可通过计划内的数据库提升完成时,传统关系型服务通常更合适。它提供成熟工具、广泛的扩展兼容性、熟悉的调试方式和更低的基础设施账单。
严格的延迟要求也可能更适合单区域主库。本地持久化写入可比跨远距离区域的法定多数写入快得多。以分析为主的系统通常应把运营事务与长时间扫描分开,而不是期待同一集群同时擅长两者。
团队能力也很重要。托管服务减少了硬件、打补丁和控制平面操作,但不会消除模式争用、事务重试、查询规划、容量管理或应用侧事故处理。如果团队没有时间测试故障行为,采用分布式数据库可能反而增加风险。
基于替代方案的决策门槛
最有力的理由出现在替代方案已经很复杂时。若工程师正准备构建租户路由、分片映射、跨分片事务规则、区域提升流程和独立迁移工具,那么提供这些功能的数据库值得深入评估。
如果替代方案只是一个带只读副本和经过测试备份的托管 PostgreSQL 实例,迁移需要明确证据。先对现有系统做基准测试。CPU 饱和的根因可能是低效查询、糟糕的连接管理、过多索引或缺少缓存,而不是需要水平写入。
一致性、可用性和延迟
分布式 SQL 通常会在故障期间拒绝无法到达所需法定多数的操作,以保持事务一致性。这能保护已提交状态,但也意味着网络分区时,部分请求可能失败或等待。
CAP 描述故障行为
当集群各部分之间的通信中断时,CAP 定理开始适用。对于受影响的数据,系统无法同时保证线性一致性和每个孤立侧都成功响应。偏向一致性的数据库会允许拥有法定多数的一侧继续运行,并在其他地方拒绝不安全写入。
CAP 并不解释正常运行时的延迟。即使每条链路都正常,副本仍须通信。更广泛的工程决策包括网络分区时会发生什么,以及应用在健康运行时接受多少协调开销。
应用必须明确处理不可用结果。超时、可重试的事务错误和暂时失去写入区域都是正常可能性。对余额或预订而言,在两个孤立区域都返回成功会更糟,因为后续协调可能没有有效的自动答案。
强读取与有意读取旧数据不同
强读取会观察到符合所要求顺序保证的数据库状态。有些产品还提供 follower reads 或有界陈旧读取,用较低新鲜度换取更低延迟,并减少写入协调者的工作。
选择应取决于读取的字段。商品说明通常可容忍副本稍旧。刚修改的密码、当前账户余额或剩余库存,应使用合适的强一致或会话一致路径。应用不应为了速度将每次读取都标为可陈旧,然后又在服务代码中重建正确性。
应使用实际的驱动和路由层测试读己之写行为。更新后,下一次请求可能到达不同的应用服务器或数据库端点。为保证用户看到已接受的变更,可能需要会话令牌、事务边界或强读取设置。
隔离级别控制并发结果
事务隔离决定并发事务可能产生哪些异常。可串行化隔离的目标是让已完成的事务看起来像一个接一个执行,即使数据库实际并发运行它们。
当并发操作无法安全排序时,可串行化执行可能中止其中一个参与者。中止是在防止错误结果,不是数据库损坏。应用需要围绕整个事务进行有限次数的重试,包括所有影响其写入的读取。
数据库之外的重试必须具备幂等性。若代码在事务确定提交前就发送邮件或调用支付服务,重试可能重复副作用。应在数据库事务中记录 Outbox 事件,提交后由独立工作程序交付外部操作。
距离给写入延迟设定下限
跨区域事务不可能快于其协议所需的消息传递。法定多数成员之间 80 毫秒的往返时间,在计算查询执行、索引维护、应用工作和排队之前,就已贡献了真实延迟。
昂贵的模式通常是一次用户操作中有多个顺序事务。若结账执行订单插入、库存预留、支付状态更新和审计写入,形成四次阻塞提交,网络成本会不断累积。将具有同一原子结果的数据库变更合并,可减少不必要的往返;外部支付调用应保持在开放事务之外。
应测量分位数延迟,而非平均值。领导权变更、争用、存储停滞和重试会体现在尾部延迟中。一个设计即使满足中位数目标,却在日常再平衡时错过 p99,也会造成用户可见的失败。
Spanner、CockroachDB 和 YugabyteDB 对比
Spanner、CockroachDB 和 YugabyteDB 解决相似的分布式问题,但在部署模式、兼容性、事务实现和运维假设上不同。选择时应测试应用行为,而不是只根据共同的 SQL 标签决定。
| 方面 | Google Spanner | CockroachDB | YugabyteDB |
|---|---|---|---|
| 主要 SQL 接口 | GoogleSQL 或 PostgreSQL 方言 | 通过 PostgreSQL 线协议提供兼容 PostgreSQL 的 SQL | 用于兼容 PostgreSQL SQL 的 YSQL,以及用于 Cassandra 风格访问的 YCQL |
| 复制基础 | 使用基于 TrueTime 排序的 Paxos 组 | 基于 ranges 的 Raft 复制 | 基于 tablets 的 Raft 复制 |
| 常见交付方式 | 托管 Google Cloud 数据库 | 托管云服务或自主管理部署 | 托管云服务或自主管理部署 |
| 可移植性顾虑 | 方言和平台特定行为 | PostgreSQL 功能、扩展和语义存在差异 | YSQL 与 PostgreSQL 之间的版本和功能差异 |
| 典型评估场景 | 需要全球事务性放置的 Google Cloud 系统 | 希望以 PostgreSQL 为导向进行开发,同时获得分布式运行能力的团队 | 希望使用面向 PostgreSQL 的访问方式,或希望在 SQL 与 Cassandra 风格 API 间选择的团队 |
Spanner 适合托管 Google Cloud 策略
Spanner 适合愿意使用托管 Google Cloud 数据库,并围绕其方言、拓扑和运行模型设计的组织。TrueTime 支持外部一致事务,也就是说,在文档规定的语义范围内,已提交事务遵循真实时间顺序。
其 PostgreSQL 方言可以减少 SQL 语法差异,但方言不等于完全兼容 PostgreSQL。扩展、管理函数、系统目录、数据类型、驱动和 ORM 假设仍需验证。团队应盘点每一项数据库依赖,再判断现有应用是否可移植。
当目标系统已依赖 Google Cloud 的身份、网络、可观测性和区域控制时,Spanner 值得重点关注。托管模式免去了数据库节点管理,但模式设计、查询调优、配额、成本管理和应用恢复仍是客户责任。
CockroachDB 适合面向 PostgreSQL 的分布式应用
CockroachDB 适合希望以 PostgreSQL 风格访问应用,同时将事务数据分布在多个 ranges 上的团队。它默认使用可串行化隔离,因此应用必须正确重试因争用或排序冲突而被拒绝的事务。
应在迁移、驱动和 ORM 层测试兼容性。PostgreSQL 扩展和特殊行为可能缺失或不同。依赖单节点执行计划的查询,在表和索引被划分到多个 ranges 后也可能表现不同。
range 的移动和自动再平衡简化了容量变更,但糟糕的主键选择仍可能产生热点 range。多区域抽象有助于表达表的本地性,但开发者仍须决定哪些记录是区域性的,哪些是全局的,以及写入应在哪里协调。
YugabyteDB 适合 YSQL 与混合 API 需求
YugabyteDB 适合重视兼容 PostgreSQL 的关系型接口,并可能从其独立的兼容 Cassandra API 中受益的应用。YSQL 提供关系表和分布式事务,YCQL 遵循不同的数据模型,不应被视作访问所有 YSQL 操作的另一条路径。
其存储层通过 tablets 分布数据。表设计、tablet 拆分、索引放置和事务范围都会影响工作如何在集群中扩散。PostgreSQL 应用仍需针对扩展、函数、工具和规划器行为进行兼容性测试。
不同的部署方式可以适应需要控制放置位置的基础设施策略。自主管理时,这种控制会将运维责任转给客户:升级、修复流程、容量、可观测性、证书、备份和故障测试都需要明确负责人。
有效的产品测试依赖应用证据
有效的比较应在每种可行产品上运行同一套有代表性的工作负载。测试模式创建、迁移、ORM 生成的 SQL、事务重试、备份恢复、故障切换、扩展事件和最高频查询。
不要只比较峰值每秒事务数。记录 p50、p95 和 p99 延迟,冲突与重试率,区域间传输字节数,存储放大倍数,恢复时间,以及模拟事故期间的运维投入。最佳选择是以可接受的成本和运维负担,满足正确性与恢复目标的那个。
面向区域用户的全球 SaaS
当租户需要区域数据放置和事务访问,又不希望为每个地理区域维护独立数据库栈时,全球 SaaS 应用可从分布式 SQL 中受益。当租户在模式中明确体现,且大部分事务停留在一个租户内时,设计效果最好。
租户本地性应遵循合同与流量
租户标识可驱动放置策略,使欧洲记录留在获批准的欧洲位置,而另一客户的记录留在合同约定的国家或区域。这使系统保留一套逻辑模式,同时执行不同的物理策略。
放置规则必须覆盖基表以外的内容。索引条目、变更流、临时数据、备份和导出记录都可能包含受监管信息。若策略固定了行,却将全局二级索引发送到其他地方,可能违反预期边界。
租户隔离也会影响性能。大型租户可能压垮共享分区,或占满一个节点。可能需要在该租户内哈希或再分区,但仍应保留租户范围事务的高效访问能力。
区域读取需要明确的新鲜度策略
只读为主的仪表盘可在可接受轻微延迟时使用附近副本。账户变更、授权决定和事务后确认页面则需要更强的行为。应按新鲜度要求分类查询路径,而不是采用一个全局设置。
写入放置应追随每个租户的常规写入方。若某客户员工主要在新加坡工作,却在另一洲协调其写入,就会造成可避免的延迟。租户迁移流程应在不丢失写入、不违反驻留要求、也不让应用缓存继续指向旧位置的前提下更新放置。
全球应用代码必须容忍变化
维护期间 leader 会移动、节点会重启、路由会改变。驱动需要合理的超时、重试策略、连接续期和事务重启逻辑。重试应采用抖动并设置上限,避免过载集群立即收到同步涌来的重复请求。
监控应按区域和租户类别区分用户延迟。全球平均值可能掩盖某个远距离客户群多付出了数次网络往返。将 API spans 与数据库语句关联的跟踪标识,有助于发现本地性错误。
金融流程与账本
当数据库约束和事务能够在故障和并发请求中强制执行账本不变量时,金融流程会受益。分布式能力本身不会自动产生正确的会计结果,因此模式必须编码不可违反的规则。
账本应保留可审计的分录序列
面向追加的账本会将每次资金流动记录为分录,而不是反复替换一个没有历史的余额值。每笔记账都应有稳定的事务标识、账户、金额、币种、业务时间戳和创建元数据。应在提交前检查复式记账规则,确保记账单元中的借方和贷方平衡。
缓存余额能加快读取,但它必须与分录在同一事务中变更,或明确被视为派生数据。对账任务应比较派生总额与源分录,并报告差异,而不是悄悄改写历史。
并不需要让每个账户都拥有全局顺序。影响一个账户或一对转账账户的事务需要一致顺序,而无关账户可并发进行。围绕这条边界设计,比依赖一个全局序列或结算行更能减少争用。
幂等性让重试安全
支付 API、队列和 webhook 会在超时后重试,因此每个业务操作都需要稳定的幂等键。在正确范围内强制唯一性,例如每个商户或账户,然后在一个数据库事务中创建支付记录和账本分录。
CREATE TABLE payment_attempts (
account_id UUID NOT NULL,
idempotency_key TEXT NOT NULL,
provider_reference TEXT,
status TEXT NOT NULL,
created_at TIMESTAMPTZ NOT NULL,
PRIMARY KEY (account_id, idempotency_key)
);
如果两个工作程序提交同一操作,唯一约束决定哪个插入成功。失败的一方应读取已有记录并返回已确定的结果。它不应只因数据库事务被重试,就再创建一次第三方扣款。
外部调用需要事务边界
除非数据库和无关的支付服务商都参与专门的协调协议,否则数据库无法与其原子提交,而大多数公开 API 并不支持这种协议。应将网络调用放在数据库事务之外,并用 pending、authorized、captured、failed 和 reversed 等明确状态建模流程。
事务性 Outbox 可以向下游工作程序发布已提交的变更。消费者应按事件标识去重,因为消息可能不止一次送达。这样可提供可恢复的处理,而不假称每个服务都能共享一个单一事务。
热点账户需要针对工作负载的设计
发薪、市场结算和大型商户可能将写入集中到一个账户上。增加数据库节点无法将一行存在冲突的数据拆开。可选方案包括不可变分录分区、按期间累计、为单个账户排队记账,或精心定义的子账户层级。
要测试真实的倾斜分布。均匀的合成流量可能让集群看似准备充分,但生产中的一个商户就可能反复触发可串行化冲突。正确性优先,但数据模型应在会计规则允许之处释放安全并发。
库存、预订和预约
当多个用户可认领同一稀缺物品时,库存和预订系统需要一个权威的分配事务。快速的可用性读取有助于浏览,但只有提交路径能决定谁获得最后一个单位。
条件写入防止超卖
只有剩余库存足够时,条件更新才能预留库存。受影响行数会告知应用分配是否成功。
UPDATE inventory
SET available = available - 1
WHERE sku = $1
AND available > 0;
该语句应与预订记录处在同一事务中。先读取可用量、再递减会产生竞争,除非隔离级别和谓词处理保护了这个决定。数据库约束还应拒绝负数量,作为另一层保护。
对于指定座位,对演出和座位标识施加唯一约束可确保只有一个预订获胜。酒店库存通常按房间晚数或库存池日期建模,使重叠住宿不能占用相同容量。正确的争用单元来自业务规则。
预留将分配与支付分开
临时预留会在支付或用户确认进行期间保留库存。保存其过期时间和状态,然后通过条件事务将其转换为已确认预订。过期工作程序只能释放仍处于活动状态的预留,因为确认与过期可能竞争。
仅凭物理时间延迟不足以保证释放。工作程序可能停止、队列可能滞后、区域可能故障。计算可售库存的查询应一致地考虑过期状态,修复任务则回收遗漏的预留。
预留时长是产品和容量决策。十分钟预留对结账可能合理,但在高峰期,它可能锁住稀缺库存中很大的一部分。设置前应测量放弃率和支付完成时间。
极端争用不会线性扩展
数千名买家竞争同一行数据,无法通过增加副本实现并行。每次成功递减都必须与其他递减排好顺序。准入控制、队列、库存桶或预先分配的区域配额,可在发售时保护数据库。
区域配额能减少协调,但会改变语义。如果欧洲有未使用的单位,而另一个区域售罄,系统需要安全转移配额的方法,或接受暂时失衡。只有当业务能定义如何协调区域容量时,才应使用这一模式。
高可用与灾难恢复
当副本放置、冗余容量和应用行为符合明确服务目标时,分布式 SQL 可在选定的基础设施故障中维持服务。仅有复制并不能保证这种结果。
SLO 应明确故障域
可用性目标必须对应一个工作负载和故障场景。应定义服务要承受一个节点、一个可用区还是整个区域的故障。说明事件中的可接受错误率和延迟,而不仅是恢复后的状态。
同一栋建筑内的三副本集群,与分布在独立可用区的三副本具有不同风险。多区域拓扑能防护更大范围的事件,但会引入更长的法定多数路径,并要求剩余容量足以吸收某个地点消失后的流量。
恢复时间目标定义服务必须多快恢复。恢复点目标定义可丢失多少已提交数据。同步法定多数复制可为覆盖范围内的故障支持零已提交数据丢失目标,但前提是所需副本和应用路径都按设计运行。
故障切换会产生可见的应用事件
领导权变更可能中断进行中的事务、关闭连接并增加延迟。应用必须区分可重试的数据库结果和永久性业务错误。失败的事务应作为整体重启,而不是只重放最后一条语句。
故障后连接池可能保留失效端点。健康检查、DNS 行为、负载均衡器、证书校验和驱动的拓扑发现,都应纳入测试计划。数据库可能是健康的,而应用仍找不到它。
故障后的容量需要明确计算。若三个区域平时运行在接近 70% 利用率,失去一个区域后就没有足够空间承担它的工作份额。预留余量需要成本,但没有故障切换容量的拓扑无法达成其宣称的目标。
演练验证设计
故障演练应禁用一个节点、隔离一个可用区、中断区域连通性,并移除一个应用端点。测量错误持续时间、事务重试率、延迟分位数、队列增长和操作员响应。
在重要的拓扑、驱动或模式变更后都应进行这些演练。去年流量下验证过的流程,在数据量翻倍或某个租户成为主导后可能失效。应自动化演练中安全的部分,让证据不依赖每年一次的手工事件。
复制不是备份
副本会忠实复制误删、有缺陷的迁移和有害的应用写入。备份和时间点恢复能防止复制无法识别的逻辑损坏。
恢复演练应构建独立的干净环境,验证校验和或应用不变量,并测量总恢复时间。还应包括加密密钥、访问策略、模式版本和依赖配置。一个存在却无法在目标时间内恢复的备份,不是合格的恢复系统。
数据驻留与合规驱动的架构
分布式 SQL 能将租户或记录组放在获批准的区域,但合规取决于每个副本、访问路径和运维流程。数据库本地性只是更大治理体系中的一项控制措施。
驻留规则需要精确定义
“数据必须留在某国”的要求,可能指存储、处理、支持访问、备份、加密密钥,或所有这些内容。不同理解会产生不同拓扑。法律顾问和审计人员应把法规和合同转化为可测试的技术控制。
团队需要盘点受监管字段和派生数据。日志、跟踪、搜索索引、分析导出、支持附件和消息队列,可能包含与主表相同的个人信息。限制数据库位置却向全球导出原始载荷,不符合预期政策。
数据最小化能简化设计。如果全球服务只需要账户标识和汇总状态,就应将敏感细节留在批准区域,并在其他地方仅暴露允许的最小表示。
放置策略必须涵盖生命周期操作
策略应说明实时副本、临时副本、备份、快照、变更记录和恢复环境可存在于何处。再平衡和维护也必须遵守同样边界。紧急流程不应为了方便而把受监管数据复制到未获批准的区域。
访问控制需要地理和组织范围限制。服务身份只能获得所需的表和操作权限。人工生产访问应被记录,在可行时设定时限并接受审查。绑定区域的加密密钥可增加控制,但密钥可用性和灾难恢复也需单独设计。
租户迁移需要有文档化流程。合同变更、客户迁移或企业重组可能要求在司法辖区间移动记录。流程应明确旧副本何时消失、备份如何到期,以及什么证据能证明完成。
全球报表可能需要派生数据集
如果全球仪表盘跨区域扫描原始客户数据,就可能与严格放置要求冲突。区域处理可在本地计算获批准的聚合结果,再将非敏感结果发布到中央报表存储。
聚合规则应防止重建受限记录。即使移除了直接标识符,小群组、自由文本字段和详细维度也可能泄露个人信息。因此分析治理应纳入架构评审,而非留到后续报表项目。
运营与分析工作负载通常值得使用不同系统。事务数据库保护当前产品状态,而按区域范围划分的管道为报表生成受治理的数据集。这种分离能让长时间分析扫描远离对延迟敏感的事务。
成本与性能规划
分布式 SQL 的成本高于基础单区域数据库,因为它维护冗余容量并跨网络协调工作。但当它替代昂贵的分片工作,或避免超过运营溢价的损失时,投资仍可能合理。
计算和存储包含复制开销
一个逻辑容量为 2 TB、具有三个完整副本的数据集,在计算二级索引、临时压缩空间、备份和元数据之前,就接近 6 TB 的复制数据。实际计费和压缩因产品而异,估算应基于实测物理存储,而不是只看逻辑表大小。
计算资源必须覆盖正常工作、共识处理、再平衡、备份活动和故障余量。当一个分区成为热点时,节点并不是可互换的吞吐量单位。只有工作负载能分散时,增加容量才有帮助。
索引会增加写入工作和存储。应根据查询价值、更新频率和地理放置审查每个二级索引。分布式集群中未使用的索引会浪费磁盘,并让每次受影响写入更昂贵。
网络费用可能十分可观
复制会在副本地点之间发送写入。跨区域查询、变更订阅、备份和应用流量还会增加传输。多个区域的活跃流量可能产生单区域基准测试完全无法暴露的账单。
估算每笔事务的字节数、复制因子、写入速率、索引放大和传输方向,然后在代表性负载运行中使用提供商计费数据测试。只看请求数会遗漏大载荷和后台移动。
本地性错误会同时提高成本和延迟。由于端点选择或租户放置,部署在一个区域的服务可能反复查询另一区域的协调者。分布式跟踪和按区域拆分的成本可揭示这种模式。
用户旅程会暴露累积延迟
应建模完整用户操作,而不是孤立语句。对于结账,统计每次顺序数据库提交、强读取、外部 API 调用和队列交接。将实测的区域往返时间和查询执行分位数应用到关键路径。
假设一次旅程包含两次顺序法定多数写入,每次增加 90 毫秒网络协调,仅此就在应用处理前贡献约 180 毫秒。合并共享同一原子决定的变更可减少一次提交,并行执行独立读取则可能缩短路径。
负载测试应包含真实的争用和载荷大小。使用随机标识符的基准可完美分散数据,但生产写入可能集中到少数热门租户。应纳入 leader 变更和再平衡,使尾部延迟反映日常集群运行。
用真实替代方案比较总体拥有成本
相关比较不是分布式 SQL 与一个没有运维成本的虚构数据库相比,而是与明确替代方案相比:托管 PostgreSQL、副本、分片服务、区域恢复、应用路由,以及维护它们所需的工程师。
应计入迁移工作、培训、可观测性、事故响应、支持计划和退出成本。托管运行可减少基础设施劳动力,自主管理则可能满足控制要求,但需要更深的人员配置。
一个简单财务模型可以比较年度平台溢价、预期停机损失、延迟的工程工作、合规风险和受区域延迟影响的收入。对不确定输入使用范围,并找出哪些假设会改变决定。如果结论依赖一个不现实的巨大停机估算,更简单的系统可能仍更适合。
模式与应用设计模式
当访问路径能分散独立工作,同时让相关事务保持接近时,分布式 SQL 模式会表现良好。原样迁移单节点模式可以保留正确性,却可能产生糟糕延迟或严重争用。
主键会影响分布
单调递增主键可能把新行导向一个 range 的末端。随机标识符能分散插入,但完全随机的分布可能让租户扫描或区域放置代价高昂。复合键常能平衡这些目标:以租户或桶标识开头,并在组内保留可排序的值。
应根据事务边界选择前缀。如果几乎所有操作都限定在租户范围内,按租户分组能减少分布式工作。非常大的租户可能需要在自己的命名空间内设置桶,以便多个分区并发接受写入。
表增长后修改主键可能需要大规模数据重写。迁移前要用真实倾斜情况测试候选布局。检查分区热度、事务扇出、索引本地性和扫描行为,而不是只评估总吞吐量。
争用需要在扩容前重新设计
全局计数器、单例配置行或一个商户余额,都会让原本独立的请求串行化。更多节点无法消除每笔事务都更新同一值的逻辑要求。
若可接受临时聚合,就用分区计数器替换精确全局计数器。通过版本化配置避免高频更新同一行。对于资金状态,应保留会计不变量,并在仅追加分录或独立子账户中寻找并发性,而不是削弱正确性。
长时间的读改写事务会加剧冲突。只读取最小必要集合,避免在事务内等待用户操作,并尽快提交。若业务工作需要数分钟,应将其表示为跨多个短事务的状态机。
重试行为属于应用契约
驱动可能重试单条语句,也可能将可重试错误暴露给应用代码。必须了解哪一层负责重放完整事务。部分重放可能使用过期决定,或遗漏先前读取。
重试循环应有最大尝试次数、随机退避和监测。记录冲突类型、受影响操作、尝试次数和最终结果。无限重试会把争用变成隐藏延迟,并可能压垮集群。
业务请求需要稳定标识,以便安全检查不确定的客户端响应。若数据库已提交但响应丢失,客户端应查询已确定的操作,而不是提交一个语义上全新的操作。
模式变更需要生产规模演练
分布式模式变更可能很快更新元数据,而回填和索引创建继续在后台运行。这些任务消耗存储、网络和 CPU,也可能与实时写入相互影响。
应采用扩展再收缩式迁移。先添加兼容字段或表,部署能同时兼容两种形态的代码,分批受控回填,切换读取,验证后再移除旧形态。回滚计划应考虑新版本已写入的数据。
使用接近生产的数据量和区域拓扑测试大型迁移。在小型预发布集群上很快完成的变更,生产中可能需要数小时,并与客户流量竞争。开始前应监控进度、暂停控制、磁盘余量和重试行为。
采用清单与概念验证
有用的概念验证会用一个代表性工作负载,对照明确的正确性、延迟、韧性和成本目标进行测试。通用基准无法判断某个具体模式和应用是否表现良好。
选择有真实约束的工作负载
选择一个流程,例如预订稀缺商品、记一笔账本转账,或在指定区域开通租户。复用其接近生产的模式、查询、事务边界、载荷大小和流量倾斜。
测试前定义成功标准:
- 并发和重试下的正确结果
- 按区域划分的 p50、p95 和 p99 延迟
- 有故障余量的持续峰值吞吐量
- 节点和区域故障中的恢复行为
- 已测量的计算、存储和网络成本
安全余量应来自预期增长和故障容量,而不是任意倍数。若失去一个区域属于范围,测试中剩余地点必须能处理被重定向的负载。
构建真实的应用表面
API 和小型用户界面能暴露数据库专用工具可能遗漏的事务顺序、驱动行为和用户感知延迟。Koder.ai 可通过对话创建 React 界面、Go 后端和 PostgreSQL 基线。它的规划模式可在生成前帮助定义工作流,源代码导出则让工程师能为候选数据库调整数据层。
应将生成的应用作为测试脚手架,而不是数据库兼容性的证明。运行迁移、检查生成的 SQL、配置官方驱动,并有意识地实现事务重试。Koder.ai 的快照和回滚可保护应用迭代,但不能替代数据库备份或恢复演练。
Koder.ai 还支持部署和托管,可将测试应用实例放在靠近数据库区域的位置。这样便能测量完整请求路径,而非从一个地点发出所有基准请求。除非环境具备生产记录所需的控制措施,否则测试数据应保持合成数据。
演练正常运行与故障
测试应覆盖稳定流量、突发流量、热点分区、长时间查询、模式变更、备份工作和节点替换。随后在获批准的测试环境中中断连通性,并移除一个故障域。
捕获事务中止、重试次数、不可用响应、leader 移动、队列深度、磁盘使用和区域传输。记录操作员必须执行的操作。若自动恢复需要未记录的手动步骤,就还不能投入生产。
将备份恢复到独立环境,并验证应用不变量。对于库存,确认分配未超过库存。对于账本,重新计算余额并验证分录平衡。对于 SaaS 租户,确认放置与访问策略在恢复后仍然存在。
迁移前验证兼容性
盘点数据库扩展、存储过程、触发器、数据类型、隔离假设、ORM 功能、报表查询、备份工具和管理脚本。将每项归类为兼容、可替换或阻塞项。
在完整规模副本或生成数据集上运行有代表性的迁移。测量回填时长、变更数据捕获延迟、双运行成本和切换时间。若迁移使用双写,需定义如何检测差异,以及每个阶段哪个系统是权威来源。
影子读取可在不改变生产状态的情况下比较结果。应考虑时间差异和有意的陈旧查询,避免将预期变化误判为损坏。事务数据中任何无法解释的差异,都必须在切换前解决。
审查生产就绪性
生产审查应为数据库运行、应用重试、安全、驻留策略、成本和事故响应指定负责人。还应包括仪表盘、告警、运行手册、容量阈值、恢复证据和回滚决策点。
最终决定仍可能是继续使用 PostgreSQL 或 MySQL。只要概念验证产生了可靠证据,即使证据显示分布式方案的成本超过当前需求所能证明的价值,它也是成功的。当需求确实支持采用时,应逐步迁移,衡量每个阶段,并在新系统于真实负载下证明自身之前,保留经过测试的回退路径。
常见问题
用简单的话说,什么是“分布式 SQL”数据库?
分布式 SQL 数据库提供关系型 SQL 接口,包括表、联接、约束和事务,但以跨多台机器的集群方式运行,通常跨越多个区域,同时对应用表现为一个逻辑数据库。
它希望同时具备:
- 熟悉的 SQL/ACID 行为
- 水平扩展能力,可增加节点
- 无需手动分片的高可用和故障容忍能力
分布式 SQL 与传统 PostgreSQL/MySQL 架构有何不同?
单节点或主从复制的 RDBMS 对于单区域 OLTP 往往更简单、成本更低、速度更快。
当替代方案需要以下能力时,分布式 SQL 会更有吸引力:
- 由应用管理分片
- 复杂的多区域故障切换
- 跨可用区或区域的强一致性要求
- 用一套运营模型满足数据驻留需求
为什么分布式 SQL 系统要使用 Raft 或 Paxos 这样的共识协议?
大多数系统依赖两个核心概念:
- 复制:每个数据分片或分区存储在多个节点上。
- 共识,例如 Raft 或 Paxos:副本对写入顺序达成一致,提交通常需要获得多数派确认。
这让系统即使节点故障也能保持强一致性,但会增加网络协调开销。
数据如何在节点和区域之间分区与放置?
它们会把表拆分成更小的块,常称为分区或分片,也可能使用厂商术语,如 ranges、tablets 或 splits。每个分区:
- 有自己的副本组
- 可以放在指定节点或区域
- 能在集群再平衡时移动
通常可通过策略影响放置位置,让热点数据和主要写入方保持接近,减少跨网络往返。
为什么分布式 SQL 中的事务会更慢,尤其是跨区域时?
分布式事务通常会涉及多个分区,它们可能位于不同节点或不同区域。一次安全提交可能需要:
- 在参与者之间加锁或验证
- 获得复制确认,也就是法定多数确认
- 协调一致的提交决定
这些额外的网络往返是写入延迟上升的主要原因,尤其是共识跨越多个区域时。
哪些最明显的迹象说明我确实需要分布式 SQL?
出现以下两项或更多情况时,可以考虑分布式 SQL:
- 用户分布在多个区域,并且需要一致的数据
- 需要跨可用区或区域自动故障切换,对 RTO/RPO 要求严格
- 纵向扩展已无法满足写入需求
- 核心事务需要强一致性,如资金、库存或预订
- 合规要求按地理位置放置数据
如果工作负载在一个区域内配合副本和缓存就能满足,传统 RDBMS 往往仍是更好的默认选择。
强一致性能带来什么,又要付出什么代价?
强一致性意味着事务一旦提交,读取就不会看到旧数据。
从产品角度看,它有助于避免:
- 重复扣款或错误余额
- 超卖最后一件商品
- 两名用户预订同一个座位
代价是网络分区期间,强一致系统可能会阻塞或失败部分操作,而不是接受彼此分叉的数据事实。
如何在分布式 SQL 中安全处理重试,也就是保证幂等性?
依靠数据库约束加事务:
- 为每次请求或尝试保存
idempotency_key或类似字段 - 添加唯一约束,例如
(account_id, idempotency_key) - 在一个事务内写入业务记录和所有账本或 Outbox 行
这样重试会变成无操作,而不是产生重复数据。这对支付、开通服务和后台任务重处理至关重要。
该如何在 Spanner、CockroachDB 和 YugabyteDB 之间选择?
可按以下方式初步区分:
- Spanner:通常以 GCP 托管方式使用,拥有成熟的多区域强一致设计,SQL 方言选择会影响可移植性。
- CockroachDB:提供类似 PostgreSQL 的体验和线协议,可托管或自建,但并非 100% 兼容 PostgreSQL。
- YugabyteDB:提供兼容 PostgreSQL 的 SQL API,也可选用 Cassandra 风格 API,可托管或自建。
选择前应测试实际的 ORM、迁移和所依赖的 PostgreSQL 扩展,不要假设可以直接替换。
在确定采用分布式 SQL 前,一个好的概念验证计划是什么?
从一个关键流程开始做聚焦 PoC,例如结账、预订或账本记账。验证:
- 正确性,没有重复预订或更新丢失
- 关键查询的 p50/p95 延迟,包括跨区域目标
- 故障行为,包括节点丢失、可用区丢失,以及需要时的区域丢失
- 基础运维能力,包括监控、备份和恢复演练
如需梳理费用和套餐,可查看价格页面。相关实现说明可浏览博客。