跳转到主要内容

Postgres 与 ClickHouse:对应概念与差异

对于来自 OLTP 系统、习惯了 ACID 事务的用户,需要注意的是,ClickHouse 为了换取性能,有意不完全提供这些特性。只要理解其语义,ClickHouse 仍可提供很高的持久性保障和写入吞吐量。下面将重点介绍一些关键概念;如果你是从 Postgres 转向 ClickHouse,建议先熟悉这些概念。

分片与副本

当存储和/或计算资源成为性能瓶颈时,分片和复制是突破单个 Postgres 实例扩展上限的两种策略。Postgres 中的分片是指将大型数据库拆分为分布在多个节点上的更小、更易管理的部分。不过,Postgres 原生并不支持分片。相反,可以借助 Citus 等扩展来实现分片,使 Postgres 成为可水平扩展的分布式数据库。通过将负载分摊到多台机器上,这种方式使 Postgres 能够处理更高的事务速率和更大的数据集。分片可以按行或基于 schema 进行,以灵活适配事务型或分析型等不同工作负载类型。由于需要跨多台机器进行协调并保证一致性,分片会在数据管理和查询执行方面引入相当大的复杂性。 与分片不同,副本是额外的 Postgres 实例,包含主节点中的全部或部分数据。使用副本有多种原因,包括提升读取性能以及用于 HA (高可用) 场景。物理复制是 Postgres 的原生特性,它会将整个数据库或其中的大部分内容复制到另一台服务器,包括所有数据库、表和索引。这一过程涉及通过 TCP/IP 将 WAL 分段从主节点流式传输到副本。相比之下,逻辑复制是更高层级的抽象,它基于 INSERTUPDATEDELETE 操作流式传输变更。虽然物理复制在某些情况下也能达到类似效果,但逻辑复制在针对特定表和操作、进行数据转换以及支持不同 Postgres 版本方面提供了更高的灵活性。 相比之下,ClickHouse 的分片和副本是与数据分布和冗余相关的两个关键概念。ClickHouse 副本可以类比为 Postgres 副本,不过其复制是最终一致的,并且没有主节点这一概念。与 Postgres 不同,ClickHouse 原生支持分片。 分片是表数据的一部分。你始终至少有一个分片。如果单台服务器的容量不足,可以将数据分片到多台服务器上以分摊负载,并在执行查询时让所有分片并行工作。你可以在不同服务器上手动为某个表创建分片,并将数据直接插入其中。或者,也可以使用分布式表,并通过分片键定义数据应路由到哪个分片。分片键可以是随机的,也可以是某个哈希函数的输出。重要的是,一个分片可以包含多个副本。 副本是数据的一个拷贝。ClickHouse 始终至少保留一份数据副本,因此副本的最小数量为 1。为数据增加第二个副本可以提供容错能力,并可能增加用于处理更多查询的计算资源 (还可以使用 并行副本 将单个查询的计算资源分布到多个副本上,从而降低延迟) 。副本通过 ReplicatedMergeTree 表引擎 实现,它使 ClickHouse 能够在不同服务器之间保持多份数据副本同步。复制是物理层面的:节点之间传输的只有压缩后的 parts,而不是查询。 总之,副本是数据的拷贝,用于提供冗余和可靠性 (以及潜在的分布式处理能力) ;而分片则是数据的一个子集,用于实现分布式处理和负载均衡。
ClickHouse Cloud 使用存储在 S3 中的单份数据,并配备多个计算副本。每个副本节点都可以访问这些数据,并且各自都拥有本地 SSD 缓存。这仅依赖通过 ClickHouse Keeper 进行的元数据复制。

最终一致性

ClickHouse 使用 ClickHouse Keeper (一个用 C++ 实现的 ZooKeeper,也可以直接使用 ZooKeeper) 来管理其内部复制机制,主要负责元数据存储并确保最终一致性。Keeper 会在分布式环境中为每次插入分配唯一的顺序编号,这对于在各项操作之间保持顺序和一致性至关重要。该机制还负责处理合并和变更等后台操作,既能将这些任务分发出去,又能保证它们在所有副本上按相同顺序执行。除了元数据之外,Keeper 还充当复制体系的综合控制中心,包括跟踪已存储数据分区片段的校验和,并作为副本之间的分布式通知系统。 ClickHouse 中的复制过程 (1) 始于数据被插入任意一个副本时。这些处于原始插入形式的数据会连同其校验和一起被 (2) 写入磁盘。写入完成后,该副本会 (3) 尝试在 Keeper 中注册这个新的数据分区片段,具体做法是分配唯一的块编号并记录该新分区片段的详细信息。其他副本在 (4) 检测到复制日志中的新条目后,会 (5) 通过内部 HTTP 协议下载相应的数据分区片段,并根据 ZooKeeper 中列出的校验和进行校验。该方法确保即使各副本处理速度不同或存在潜在延迟,最终也都能持有一致且最新的数据。此外,系统还能够并发处理多项操作,从而优化数据管理流程,并提升系统的可扩展性以及应对硬件差异的稳健性。 请注意,ClickHouse Cloud 使用一种针对云环境优化的复制机制,以适配其存储与计算分离的架构。由于数据存储在共享对象存储中,所有计算节点都可以自动访问这些数据,而无需在节点之间物理复制数据。相反,Keeper 仅用于在计算节点之间共享元数据 (即哪些数据存在于对象存储中的什么位置) 。 与 ClickHouse 相比,PostgreSQL 采用了不同的复制策略,主要使用流式复制,也就是主节点-副本模型:数据会持续从主节点流向一个或多个副本节点。这种复制方式可确保接近实时的一致性,并且既可以是同步的,也可以是异步的,使管理员能够在可用性与一致性之间进行权衡。与 ClickHouse 不同,PostgreSQL 依赖 WAL (预写日志) 、逻辑复制 和 decoding,在节点之间传输数据对象和变更。PostgreSQL 的这种方式更直接,但在高度分布式环境中,可能无法像 ClickHouse 那样通过复杂的 Keeper 协调分布式操作并实现最终一致性,从而提供同等水平的可扩展性和容错能力。

对用户的影响

在 ClickHouse 中,之所以可能出现脏读——即数据写入一个副本后,又从另一个副本读到尚未完成复制的数据——是因为其采用了由 Keeper 管理的最终一致性复制模型。该模型更侧重分布式系统中的性能和可扩展性,允许各副本独立运行并进行异步同步。因此,新插入的数据未必会立即在所有副本上可见,具体取决于复制延迟以及变更在系统内传播所需的时间。 相比之下,PostgreSQL 的流式复制模型通常可通过同步复制选项避免脏读:主节点会在提交事务前,至少等待一个副本确认已收到数据。这意味着事务一旦提交,就可以保证数据已在另一副本中可用。即使主节点发生故障,副本也能确保查询看到已提交的数据,从而提供更严格的一致性保障。

建议

刚开始使用 ClickHouse 的用户应当了解这些差异,因为在复制环境中,这些差异会体现得更加明显。通常,对于涉及数十亿甚至数万亿数据点的分析场景,最终一致性已经足够——因为指标往往较为稳定,或者在新数据持续高速写入的情况下,估算结果也完全可以接受。 如果确实有需要,也有几种方法可以提高读取一致性。但这两种做法都会带来更高的复杂度或额外开销——从而降低查询性能,并使 ClickHouse 更难扩展。我们建议仅在绝对必要时才采用这些方法。

一致性路由

为克服最终一致性的一些局限性,你可以确保将客户端路由到同一副本。在多个用户查询 ClickHouse,且需要各次请求结果保持确定性的场景下,这一点非常有用。虽然随着新数据写入,结果可能会发生变化,但通过查询同一副本,可以确保视图一致。 这可以通过多种方式实现,具体取决于你的架构,以及你使用的是 ClickHouse OSS 还是 ClickHouse Cloud。

ClickHouse Cloud

ClickHouse Cloud 使用一份存储在 S3 中的数据副本,并配有多个计算副本。每个副本节点都可以访问这份数据,并带有本地 SSD 缓存。因此,为了确保结果一致,用户只需确保请求始终被路由到同一个节点。 与 ClickHouse Cloud 服务各节点之间的通信通过代理进行。HTTP 和原生协议连接在保持打开期间会被路由到同一个节点。对于大多数客户端发起的 HTTP 1.1 连接,这取决于 Keep-Alive 窗口。大多数客户端 (例如 Node.js) 都可以对此进行配置。这还需要服务端配置,且服务端的值会高于客户端;在 ClickHouse Cloud 中,该值设置为 10 秒。 为了确保跨连接的一致路由,例如在使用连接池或连接过期时,你可以确保始终使用同一个连接 (这对原生协议来说更容易) ,或者申请启用粘性端点。这样会为集群中的每个节点提供一组端点,从而使客户端能够确保查询被确定性地路由。
联系支持团队以获取粘性端点的访问权限。

ClickHouse OSS

在 OSS 中,要实现这种行为,取决于你的分片和副本拓扑,以及查询时是否使用了分布式表 当你只有一个分片和多个副本时 (这种情况很常见,因为 ClickHouse 更适合纵向扩展) ,用户会在客户端层选择节点,并直接查询某个副本,同时确保该选择是确定性的。 虽然在不使用分布式表的情况下,也可以采用包含多个分片和副本的拓扑,但这类高级部署通常都有自己的路由基础设施。因此,我们假定只要部署包含多个分片,就会使用分布式表 (分布式表也可用于单分片部署,但通常没有必要) 。 在这种情况下,你应确保基于某个属性进行一致的节点路由,例如 session_iduser_id。应在查询中设置 prefer_localhost_replica=0load_balancing=in_order。这样可以确保优先选择各分片的本地副本;否则,在错误数相同的情况下,会优先按照配置中列出的顺序选择副本——如果错误数更高,则会随机选择并执行故障转移。也可以使用 load_balancing=nearest_hostname 作为这种确定性分片选择的替代方案。
创建分布式表时,你需要指定一个 cluster。这个在 config.xml 中定义的 cluster 会列出各个分片 (及其副本) ,从而让用户可以控制每个节点使用它们的顺序。利用这一点,你可以确保选择具有确定性。

顺序一致性

在极少数情况下,你可能需要顺序一致性。 数据库中的顺序一致性是指,数据库上的操作看起来会按某种顺序依次执行,并且这一顺序对所有与数据库交互的进程都保持一致。这意味着,每个操作看起来都会在调用到完成之间的某个瞬间立即生效,而且所有进程观察到的操作顺序都是唯一且一致的。 从用户角度来看,这通常表现为:需要将数据写入 ClickHouse,并在读取数据时保证返回最新插入的行。 这可以通过多种方式实现 (按优先顺序排列) :
  1. 在同一节点上读/写 - 如果你使用 native protocol,或者使用 session 通过 HTTP 执行写入/读取,那么你应当连接到同一个副本:在这种情况下,你直接从写入所在的节点读取,因此读取结果始终是一致的。
  2. 手动同步副本 - 如果你写入一个副本,却从另一个副本读取,可以在读取前执行 SYSTEM SYNC REPLICA LIGHTWEIGHT
  3. 启用顺序一致性 - 通过查询设置 select_sequential_consistency = 1。在 OSS 中,还必须指定设置 insert_quorum = 'auto'

有关如何启用这些设置的更多详细信息,请参见此处
使用顺序一致性会给 ClickHouse Keeper 带来更高负载。结果可能是 插入和读取速度变慢。作为 ClickHouse Cloud 中主要 table engine 的 SharedMergeTree,顺序一致性带来的额外开销更低,扩展性也更好。在 OSS 中,你应谨慎使用这种方式,并衡量 Keeper 负载。

事务 (ACID) 支持

从 PostgreSQL 迁移而来的用户,可能已经习惯了它对 ACID (原子性、一致性、隔离性、持久性) 特性的强大支持,这使其成为事务型数据库的可靠选择。PostgreSQL 中的原子性确保每个事务都被视为一个单一单元,要么完全成功,要么完全回滚,从而避免部分更新。一致性则通过强制执行约束、触发器和规则来保持,以确保所有数据库事务都会使数据处于有效状态。PostgreSQL 支持从 Read Committed 到 Serializable 的隔离级别,因此可以对并发事务所做更改的可见性进行精细控制。最后,持久性通过预写日志 (WAL) 实现,确保事务一旦提交,即使发生系统故障也不会丢失。 这些特性在作为单一事实来源的 OLTP 数据库中很常见。 尽管功能强大,但这也带来了固有的限制,使扩展到 PB 级规模变得颇具挑战。ClickHouse 在这些特性上做出取舍,以便在大规模场景下提供快速的分析查询,同时保持高写入吞吐量。 ClickHouse 仅在有限配置下提供 ACID 特性——最简单的情况是使用具有单个分区的非复制 MergeTree 表引擎实例。你不应期望在这些场景之外也具备这些特性,并应确保这些特性不是你的硬性要求。

压缩

ClickHouse 的列式存储意味着,相比 Postgres,其压缩效果通常会好得多。下图展示了比较两个数据库中所有 Stack Overflow 表存储需求的结果:
Query (Postgres)
SELECT
    schemaname,
    tablename,
    pg_total_relation_size(schemaname || '.' || tablename) AS total_size_bytes,
    pg_total_relation_size(schemaname || '.' || tablename) / (1024 * 1024 * 1024) AS total_size_gb
FROM
    pg_tables s
WHERE
    schemaname = 'public';
Query (ClickHouse)
SELECT
        `table`,
        formatReadableSize(sum(data_compressed_bytes)) AS compressed_size
FROM system.parts
WHERE (database = 'stackoverflow') AND active
GROUP BY `table`
Response
┌─table───────┬─compressed_size─┐
│ posts       │ 25.17 GiB       │
│ users       │ 846.57 MiB      │
│ badges      │ 513.13 MiB      │
│ comments    │ 7.11 GiB        │
│ votes       │ 1.28 GiB        │
│ posthistory │ 40.44 GiB       │
│ postlinks   │ 79.22 MiB       │
└─────────────┴─────────────────┘
有关优化和评估压缩的更多信息,请参见此处

数据类型映射

下表展示了 Postgres 对应的 ClickHouse 数据类型。
Postgres 数据类型ClickHouse 类型
DATEDate
TIMESTAMPDateTime
REALFloat32
DOUBLEFloat64
DECIMAL, NUMERICDecimal
SMALLINTInt16
INTEGERInt32
BIGINTInt64
SERIALUInt32
BIGSERIALUInt64
TEXT, CHAR, BPCHARString
INTEGERNullable(Int32)
ARRAYArray
FLOAT4Float32
BOOLEANBool
VARCHARString
BITString
BIT VARYINGString
BYTEAString
NUMERICDecimal
GEOGRAPHYPoint, Ring, Polygon, MultiPolygon
GEOMETRYPoint, Ring, Polygon, MultiPolygon
INETIPv4, IPv6
MACADDRString
CIDRString
HSTOREMap(K, V), Map(K,Variant)
UUIDUUID
ARRAY<T>ARRAY(T)
JSONString, Variant, Nested, Tuple
JSONBString
最后修改于 2026年6月10日