S55

分布式数据库

点击正文段落选定收藏或划线位置;未选定时使用屏幕内第一段。

课内目录

1 分布式数据库要回答什么

运维平台把主机巡检结果写进数据库:记录包含租户、主机、采集时间和检查状态。最初,数据库由 1 个进程提供服务,数据文件放在本机文件系统,值班人员通过 SQL 查找失败检查。随着主机增多,磁盘容量、写入速率和查询并发逐渐成为限制。增加数据库节点看起来很自然,但应用随即遇到新问题:某条记录在哪里,查询要访问多少节点,节点失联后还能不能写,已经返回成功的数据能否在故障后读到?

分布式数据库要把这些问题组织成可使用的接口。应用通常仍然提交 SQL,系统却可能让多个进程通过网络共同完成工作。客户端连接的地址可能属于接入层;保存数据的进程可能位于其他主机;负责协调查询的进程还要等待远端结果。能够连接接入地址,只证明连接路径上的部分组件可用,不能证明所有数据分片都健康。

先分清分片和副本。分片是把记录分到不同的数据集合,例如按租户编号计算归属;副本是为某个分片保存额外的数据拷贝。分片主要帮助分担容量和处理量,副本主要服务于可用性与恢复,也可能参与读取。增加副本不等于增加独立的数据分片,增加分片也不等于每次查询都会变快。每次查询访问的分片数量,常常比集群总节点数更能解释延迟。

再区分正确性与可用性。正确性包含约束是否成立、提交结果是否明确、读取是否符合约定;可用性关心请求在期限内能否得到可用回答。网络异常时,系统需要按照自己的规则接受、拒绝或等待请求。入门阶段不必背诵所有一致性术语,但必须能追问:这里的读取可能旧多久,提交成功代表什么,某个副本不可达时服务有什么表现?这些问题要用具体产品的明确承诺回答。

本课沿用主机巡检平台,重点观察操作系统和网络能提供的证据。进程 CPU 高、数据目录所在磁盘等待长、到某个节点的连接反复超时,都可能影响 SQL。SQL 层的慢查询又可能来自扫描过多分片、等待锁或结果合并。两类证据需要按时间和请求标识对齐,不能把机器指标正常当成数据库正确,也不能看到慢 SQL 就直接增加机器。

接入、协调、分片与副本的关系
接入、协调、分片与副本的关系

学习结束时,应能解释 1 次查询为何跨机器,识别计算存储分离中仍然存在的本地状态,用执行计划与主机观测缩小故障范围,并把分片键、事务范围、超时和恢复验收写进方案。复制协议的内部选举与提交算法留给后续学习;本课只使用它们对外暴露的行为边界。

2 计算存储分离与本地态

计算存储分离,是让执行 SQL 的计算资源和持久保存数据的存储资源能够相对独立地调整。计算节点解析请求、执行算子、合并结果,存储服务负责相应的数据读写。实际系统的边界并不完全相同:有的把部分过滤推到存储侧,有的由计算侧读取数据块再处理。评估架构时,应确认哪些工作发生在哪个进程中,避免只根据架构图上的名称推断性能。

分离带来的直接收益是资源调节更灵活。查询 CPU 不足时,可以增加计算资源;数据增长时,可以扩展持久存储。但 1 次远程读取仍然要走网络。客户端到接入节点、计算节点到存储节点、参与节点之间的通信,可能具有不同的延迟和故障范围。网络连接数、返回字节量、重试和超时会进入查询成本,不能把远程数据访问理解成本地文件读取的简单替换。

计算节点也不是完全没有状态。它可能持有连接会话、尚未提交的事务、查询中间结果、数据缓存、预编译信息和临时文件。持久数据留在存储服务,并不意味着删除计算进程后所有工作都能无感延续。连接可能中断,未完成的排序可能重做,新节点的缓存需要重新建立。扩容后的短时间内,更多冷缓存请求甚至可能增加存储压力。

在 Linux 主机上,ps -eo pid,comm,%cpu,%mem 可帮助定位资源占用较高的进程,df -h /var/lib/db 可检查示例数据路径所在文件系统的容量。路径必须替换成实际部署路径;容器里的路径和宿主机挂载也可能不同。若环境安装了相应工具,iostat -xz 1 可以观察块设备负载与等待,ss -tin 可以查看 TCP 连接的状态信息。这些命令提供局部线索,不直接给出“数据库慢”的最终结论。

临时文件尤其容易被忽略。跨分片排序需要合并数据,内存不足时可能落到本地临时目录。持久数据所在的远端存储容量充足,本机临时盘仍可能耗尽。排查时要同时确认数据库配置中的临时目录、该目录所在挂载点的剩余容量,以及数据库对溢写的统计。不要用删除正在使用的数据库文件来腾空间;应先限制负载,再按系统支持的方法清理和调整。

持久数据与计算节点本地状态
持久数据与计算节点本地状态

理解本地态还能帮助设计重启验收。除了观察节点重新加入,还应检查客户端能否重新连接、失败事务是否得到处理、缓存恢复期间的延迟是否可接受。所谓弹性扩展,既包含资源创建,也包含路由接入、缓存建立和负载重新分配。只有业务请求恢复到约定水平,扩展动作才算完成。

3 全局索引与分布式事务边界

假设巡检表按 tenant_id 分片。查询带有租户条件时,系统可能定位到较少的分片;值班人员如果只知道 host_id,则未必知道应该访问哪里。局部索引只整理各自分片内的数据,查询仍可能把请求发给所有候选分片。全局索引为跨分片的检索提供额外定位信息,但它本身也要存储、更新和恢复,不是免费增加的快捷入口。

例如,全局索引把主机编号映射到记录所在位置。按主机编号查询时,执行过程可能先访问索引,再访问数据。新增巡检记录时,系统则需要让数据和相关索引按照约定保持一致。如果索引包含足够的返回列,有时能够减少后续读取;如果索引缺少所需字段,就仍然需要回到数据所在位置。应通过执行计划确认访问路径,不能仅凭“已经建了索引”判断查询成本。

索引还影响写入。每增加需要维护的索引,写请求通常要更新更多结构,消耗存储、网络与处理时间。索引键是否集中也很重要:连续时间或少数高频状态值可能让写入聚集到有限位置。不是所有索引设计都会产生同样的热点,但每项设计都应回答实际值分布如何、谁负责维护,以及故障恢复时如何保证可见结果符合系统承诺。

事务讨论的重点是操作范围。更新同一分片中的多条记录,与更新不同分片中的记录,协调成本可能不同。例如平台调整主机归属,同时写入变更审计;如果这 2 类记录位于不同分片,就要确认数据库是否支持它们在同一事务中提交。即使支持,也要了解隔离级别、超时、冲突与连接中断后的处理方式。支持 SQL 事务不代表所有部署和所有语句都有相同边界。

客户端最容易误判的是提交时超时。超时表示客户端没有及时拿到明确结果,不能单凭它断定事务失败。服务端可能已经完成提交,只是回复没有到达;也可能尚未提交或已回滚。再次执行包含副作用的操作前,应依据稳定的操作编号查询结果,或使用数据库明确支持的幂等方案。只依赖重试次数,会把结果未知变成重复写入的风险。

全局索引的读取路径与写入维护
全局索引的读取路径与写入维护

入门选型可以先列出必须共同成功的修改,再标注它们各自的分片位置。能在数据模型中自然收敛到同一分片的操作,通常更容易控制协调范围;确实需要跨分片原子性的操作,应保留明确的事务需求,并通过故障实验验证。不要为了降低成本而悄悄删掉正确性要求,也不要把所有操作都包进巨大事务来回避建模。

4 动手:追踪一次跨分片查询

以下实验使用隔离的教学库,不在生产库造数或修改参数。约定提供 PostgreSQL 协议兼容入口,命令行工具为 psql,数据库支持查看分布式执行计划;兼容协议不意味着所有 PostgreSQL 扩展和监控视图均可用。示例表为 checks(tenant_id, host_id, checked_at, status),按 tenant_id 分片,已准备多个租户的巡检记录。连接信息由实验环境提供,口令不直接写在命令历史中。

先查询某个租户最近失败的检查,再查询所有租户最近失败的检查。保持时间范围、返回字段与排序方式接近,避免把不同业务量误当成分片机制的差异。下面的时间条件使用固定区间,方便在同一批数据上比较。数据库如不支持示例中的计划语法,应使用它提供的等价入口。

EXPLAIN
SELECT tenant_id, host_id, checked_at
FROM checks
WHERE tenant_id = 42 AND status = 'failed'
  AND checked_at >= TIMESTAMP '2026-10-01 00:00:00'
  AND checked_at <  TIMESTAMP '2026-10-02 00:00:00'
ORDER BY checked_at DESC
LIMIT 20;

EXPLAIN
SELECT tenant_id, host_id, checked_at
FROM checks
WHERE status = 'failed'
  AND checked_at >= TIMESTAMP '2026-10-01 00:00:00'
  AND checked_at <  TIMESTAMP '2026-10-02 00:00:00'
ORDER BY checked_at DESC
LIMIT 20;

阅读计划时,寻找实际系统标出的分片裁剪、远程扫描、数据交换和合并排序等操作,名称可能不同。记录候选分片数、预计扫描行数、过滤位置和结果合并位置。LIMIT 20 限制最终返回数量,并不能保证整个执行只检查 20 条记录。系统可能下推局部限制,但协调节点仍需要等待多个参与者,才能确定全局结果。

普通 EXPLAIN 通常给出计划而不执行查询;带实际执行统计的形式会真正运行语句。本实验只对上述只读查询按环境文档采集实际统计,并配置合适的语句超时。不要把任何带 ANALYZE 字样的写操作都当作安全预览。运行期间记录开始时间、会话或请求标识、实际耗时与参与节点,后面才能对应到主机和数据库日志。

跨分片查询从定位到合并的路径
跨分片查询从定位到合并的路径

同时在有权限的实验节点观察进程与连接,例如 ss -tn 查看已建立的 TCP 连接,pidstat -p 1234 1 观察指定数据库进程的资源变化;进程号应替换为实际值,工具也需要预先安装。不要期待连接数量等于分片数:连接池、复用和代理都会改变外观。数据库执行统计负责说明逻辑参与者,操作系统指标负责说明这些参与者当时的资源状态。

最后形成对照记录:带租户条件的查询访问多少分片,去掉条件后增加多少扫描和交换,最慢参与者是谁,协调端是否发生临时文件溢写。若两种查询计划相同,继续查看数据分布和优化器选择;不要为了符合预期而改写观察结果。实验的产物是可解释的证据链,而不只是 2 个耗时数字。

5 扩展与复杂查询取舍

扩展至少要拆成容量、写入、读取和复杂计算几个目标。增加节点可能改善总容量,也可能让多个租户的独立查询并行执行;但某个只落在单个分片的热点写入,不一定因此变快。新增节点后如果数据尚未迁移,原来的热点还在原处。即使发生迁移,也需要考虑迁移期间的数据传输、额外读写和业务延迟。

跨分片查询还有扇出成本。参与者越多,发出的请求通常越多,协调端需要处理的结果也可能越多。假设每个分片在某个期限内完成的概率都是 0.99,并暂时假设彼此独立,需要全部完成的查询访问 16 个分片时,全部按期完成的概率约为 0.99 的 16 次方。真实系统的延迟往往相关,因此这个简化算式只用于说明:并行并不消除等待较慢参与者的问题。

连接与聚合更能体现数据位置的重要性。若主机表与巡检表按相同租户规则放置,按租户连接可能减少交换;若按不同规则分布,系统可能搬运其中部分数据,或者重新分配中间结果。小表广播有时合适,大表广播则可能放大内存和网络消耗。判断依据应包含过滤后数据量与交换字节量,不能只看原始表的行数。

全局排序、去重和聚合也可能把工作集中到协调节点。COUNT(*) 返回 1 行,却可能需要多个分片参与扫描或汇总;少量返回行不代表少量内部工作。部分聚合可以在分片内先计算,再汇总,但是否采用、收益多大,仍取决于查询、数据和实现。对入门使用者,更可靠的习惯是看计划中的数据移动,再对照实际统计。

滑块用一个简化模型展示参与分片数变化时的等待与交换,不预测真实吞吐。现实中,索引、缓存、并发、分片大小和执行策略都会改变结果。模型帮助提出测量问题:增加资源究竟缩短了哪个阶段,代价发生在哪里?不能把动画里的线性关系直接写进容量承诺。

容量扩展与查询代价的不同路径
容量扩展与查询代价的不同路径

决定扩展前,至少准备有代表性的请求组合:租户内点查、租户内范围查询、跨租户汇总和写入。分别测量稳定负载与突发负载下的吞吐、延迟分位数、错误比例和资源占用。还要观察扩展后的恢复阶段,而不只比较资源稳定之后的最好结果。对偶发的大范围报表,可以评估错峰、预汇总或独立资源池,但每种做法都要说明数据时效和运维成本。

6 常见坑:把单机 SQL 习惯直接搬迁

常见误区是认为 SQL 文本一样,代价就差不多。在单机上尚可接受的宽范围扫描,分布式执行时可能变成多节点扫描和集中合并。SELECT * 会把不需要的列带上网络,深分页可能反复处理大量被丢弃的记录。改进应从实际访问模式出发:只取必要列,给出稳定且可用的筛选条件,对连续翻页评估基于排序键的续查,同时保证排序含有明确的并列处理规则。

另一个误区是把分片键当成普通建表参数。按租户分片有利于租户内访问,却可能让大租户集中占用某个分片;按时间分片便于部分时间范围操作,却可能让当前时间窗口承接集中写入。没有脱离负载分布的万能键。选择时需要看访问条件、租户大小差异、增长方向与迁移成本,并确认系统实际采用的映射规则,而不只看字段名字。

连接池也容易被照搬。某个单机服务开 100 条连接尚可接受,多个应用实例各自开 100 条连接后,数据库接入层可能积累大量会话。每个会话又可能发出跨分片请求,使远端并发进一步增加。应同时限制应用侧在途请求与连接数量,观察数据库排队和会话资源。连接数很少也不代表负载轻,长时间运行的复杂查询可能持续占用大量资源。

超时设置如果互不配合,会制造额外工作。客户端在 3 秒退出,服务端查询仍可能继续运行;应用立即重试后,旧查询和新查询可能重叠。要了解客户端取消是否传播、数据库语句超时如何生效,以及连接回收后事务如何处理。重试需要上限、退避和可识别的请求编号;写入更要先处理结果未知的问题。提高超时只能改变等待期限,不能消除资源瓶颈。

还有人把“副本能读”理解成“任何时候都能读到最新值”。如果系统允许读取稍旧的副本,刚修改完状态又从另一个入口读取,就可能暂时看到旧结果。方案需要为关键操作明确读取路径或一致性要求,并验证实际配置。对于诊断日志,要区分业务数据的时间、采集时间和客户端显示时间,避免把时钟或采集延迟误判成数据库丢写。

最后,不要把故障处理变成连续重启。先记录错误类别与时间范围:连接被拒绝、连接超时、查询超时、锁等待、磁盘空间不足,各自指向的下一步不同。journalctl -u db-service --since '10 min ago' 只适用于使用 systemd 且服务名已替换的环境;容器部署应查看对应日志入口。日志可能包含敏感字段,采集时应限制范围和访问权限,保留请求标识与错误码即可。

7 案例:热点租户打满共享池

某巡检平台每天集中上传采集结果。租户 42 新增大量主机后,在固定时段批量补传数据,同时运行历史失败检查汇总。多个租户共享计算资源池。值班人员首先看到的是全平台查询延迟上升,应用增加重试后连接排队继续增长。此时仅凭“受影响租户很多”,无法判断问题来自共享资源、多个分片,还是公共网络入口。

先固定观察窗口和证据。记录请求延迟分位数、超时比例与在途查询,按租户和查询指纹汇总数据库统计;查询指纹指把同类语句归在一起的标识,不应把完整敏感参数随意记录。随后将共享池的 CPU、内存、临时文件与到存储节点的网络流量放到相同时间线上。若使用监控采集系统,还要注明采样间隔,不能把不同窗口的数据直接相减。

观察发现,租户 42 的汇总查询参与分片较多,扫描量和中间结果较大;协调进程 CPU 持续升高,临时盘写入增加,其他租户的简单查询主要耗时在进入执行前的队列里。存储节点并非全部繁忙,也没有足够证据证明公共入口故障。这组证据把重点指向共享计算池被大查询挤占,而不是笼统的“整个分布式数据库变慢”。

从用户延迟对齐到共享资源和查询证据
从用户延迟对齐到共享资源和查询证据

先采取可回退的缓解:暂停或限速该租户的批量报表,限制同类查询并发,对客户端重试设置上限。取消查询需要使用数据库支持的接口,并确认目标会话,避免误杀关键写入。实施后继续观察队列长度、超时比例与完成吞吐;若队列下降但失败请求骤增,就不能只用延迟下降宣布恢复。

长期改进需要对应原因。为报表设置独立资源池或配额,可以缩小相互影响范围;优化时间过滤、必要索引或预汇总,可以减少每次工作的量。若写入本身集中在该租户的单个分片,则还需评估该租户的数据组织,单独增加共享计算资源未必有效。限流、查询优化、分片调整解决的是不同问题,不能把它们当成可互换的按钮。

热点与连接、存储故障的排查分支
热点与连接、存储故障的排查分支

验收要覆盖热点租户和其他租户。记录同一请求组合下各租户的成功吞吐、延迟、排队与错误,并测试批量任务恢复后是否再次挤占。还应检查积压数据能否在约定时间内补齐,限流是否导致采集端无限堆积。故障报告最终要写清触发负载、共享位置、证据、缓解效果和剩余边界,避免只留下“扩容后恢复”这种难以复用的结论。

8 把分布式数据库选型写进方案

选型应从负载与约束开始。列出主机巡检平台最重要的查询,说明租户内访问和跨租户访问各占多少,平均与高峰写入量是多少,记录保留多久,增长主要来自租户数量还是少数大租户。数据量、记录大小和分布偏斜需要分开记录。只提供总行数,无法判断分片是否均衡,也无法估计全局索引与复杂查询的代价。

接下来描述数据位置与事务边界。方案应给出分片键候选、典型 SQL 能否携带该键、全局检索走什么索引、哪些操作必须原子提交。对于跨分片操作,列出隔离与超时要求;对于允许稍旧读取的报表,给出可接受的时效范围。把“需要强一致”转成可测试的行为,例如成功更新后的关键确认应在规定读取路径看到该状态。

资源方案需要同时覆盖计算、持久存储和本地临时空间。写明计算节点重启时会话如何恢复、缓存预热期间如何控制并发、数据迁移期间如何观测负载。网络预算至少关心数据交换量、连接与超时,不能只写网卡带宽。共享部署还要给出租户隔离方法和例外处理流程,让大租户突发时的影响范围可解释。

评测采用同一批数据和固定请求组合,明确预热方式、持续时间、并发、返回字段与错误计算口径。分别观察正常运行、代表性热点和允许的节点故障;故障注入仅在隔离环境按预案执行。结果同时报告成功吞吐、延迟分位数、错误比例、扫描与交换量,以及恢复耗时。平均延迟不足以描述少数请求长时间等待的问题。

方案还要写明连接中断后的责任。应用负责保存稳定的操作编号,数据库提供可查的提交结果或明确的事务接口,运维负责观测和恢复流程。对于备份,说明备份包含什么、如何恢复、恢复后的数据如何校验;副本与备份服务于不同故障场景,不能因已有副本就省略恢复演练。误操作传播到副本时,副本本身未必能提供需要的历史状态。

最后给出退出条件:若代表性查询无法满足延迟、热点隔离失效,或恢复结果无法校验,就回到数据模型、资源配置或候选系统重新评估。保留常规关系数据库加适当容量规划的比较基线,只有当分布式能力确实解决了已量化的问题,才承担相应复杂度。入门方案不需要解释内部复制算法,但必须说明操作人员看什么、何时介入、如何判断恢复完成。

9 从分布式数据库走向消息系统(S56)

巡检平台扩展到多节点后,还会遇到到达速率与处理速率不一致的问题。采集进程可能在网络恢复后同时补传,数据库却只能以有限速率完成写入。直接让所有采集进程反复请求数据库,会把等待分散到连接池、应用内存和数据库队列。数据已经分布存储,并不意味着突发负载自动得到平滑处理。

消息系统提供另一类接口:生产者先提交消息,消费者按能力读取并处理。这里需要关注的是工作如何排队、如何确认处理进度、失败后如何再次处理。它与数据库可以配合,但持久状态、查询接口和消息流转承担的责任不同。把请求放进队列之后,处理并未立即完成,用户可见状态和运维指标也要随之调整。

例如采集结果先进入待处理队列,消费者写入巡检表。数据库短时变慢时,队列长度可能增长;消费者恢复后,再逐步消化积压。这个缓冲有容量、保留时间和最大允许延迟。若输入长期超过处理能力,积压仍会持续扩大。容量规划必须把到达速率、完成速率和积压年龄放在一起看,不能把队列视为无限存储。

数据库成功写入与消息处理确认之间还存在失败窗口。消费者写完数据库后、确认消息前崩溃,恢复时可能再次处理同一条消息;先确认再写数据库,则可能在崩溃后失去这次处理机会。这里先记住需要稳定的事件编号和可重复执行的写入设计,具体确认机制与交付语义留到 S56。不要把加入消息系统直接等同于自动避免重复或丢失。

操作系统证据仍然有用。消费者是进程,也有 CPU、内存、文件和连接;网络中断可能让它暂时无法确认;本地磁盘不足可能影响相关组件持久化。排查时应把数据库提交状态、消费者处理记录和消息进度按事件编号关联,不能只凭某个进程存活就认为处理链路健康。同样,积压下降也可能来自消息过期,需要结合成功写入数量判断。

从本课带到下课的核心问题是:工作在哪里等待,哪一步算完成,失败之后由谁重试,以及怎样证明结果符合约定。分布式数据库帮助组织跨机器的数据访问与事务;消息系统继续讨论跨进程的工作交接。理解这些接口边界后,再学习内部机制才有明确的问题背景。

放进方案里

巡检平台采用按租户组织数据的分布式数据库,主要服务租户内查询与采集写入,跨租户报表使用独立配额。评测数据覆盖普通租户与热点租户,记录规模、记录大小、时间分布和请求组合。分片键选择必须通过典型 SQL 验证;全局索引需说明检索收益、写入维护成本与一致性承诺。涉及主机归属和审计的共同修改,明确事务范围、隔离要求及结果未知时的查询办法。

计算池配置连接与在途请求上限,为临时文件预留空间。扩容验收包含接入路由、缓存建立和迁移阶段,持续观测成功吞吐、延迟分位数、错误比例、排队、扫描量及网络交换量。热点租户触发配额后,其他租户仍需满足约定指标;批量任务恢复后验证积压消化时间,防止压力转移到采集进程。

应用为可重试写入保留稳定操作编号。客户端超时后先核实提交状态,按照幂等规则决定是否重试,并使用有上限的退避策略。服务端语句期限与客户端等待期限需要配合,取消与连接回收行为应经过实验确认。监控使用请求标识关联查询、参与节点和主机指标,日志避免记录完整敏感参数。

上线前在隔离环境演练计算节点重启、代表性网络异常和备份恢复,保存触发条件、错误表现及恢复证据。恢复完成以关键查询、提交校验和积压处理均达到验收要求为准。若引入消息缓冲,补充队列容量、保留时间、最大积压年龄和重复处理策略。所有性能与恢复目标填写为本项目实测可达的数值,由负责人员确认,不以节点数量替代服务承诺。

10 小结:下一步消息系统(S56)

分布式数据库把 SQL 背后的工作分配给多个进程与节点。理解它,先看数据在哪里、查询访问哪些参与者、事务在哪个范围成立,再看网络、共享资源与本地状态怎样影响结果。索引、扩容和副本各有作用,也各有维护成本与适用边界。

复习时可以口头回答:为什么 LIMIT 20 仍可能跨分片扫描?计算存储分离后,临时盘满为什么还能让查询失败?提交超时后为什么需要核实结果?热点租户使其他租户变慢时,需要什么证据才能证明共享池受挤占?如果只能说出“增加节点”,应回到执行计划与观测路径重新检查。

下一课 S56 将讨论消息系统:从进程之间的工作交接出发,认识积压、确认与重复处理,并继续用可观测的运行行为判断系统是否真正完成工作。

同阶段相关课

AI 答疑

默认关闭。启用后,只发送本课标题、最近阅读的屏幕内正文摘录(最多 6000 字)与本次问题。

配置自己的服务