概述
ClickHouse 和 OLTP 数据库中更新数据之间的差异
在处理更新方面,ClickHouse 和 OLTP 数据库由于其底层设计理念和目标用例而存在显著差异。例如,PostgreSQL,一个面向行的、符合 ACID 规范的关系数据库,支持强大且事务性的更新和删除操作,通过多版本并发控制 (MVCC) 等机制确保数据一致性和完整性。这使得即使在高并发环境中也能进行安全可靠的修改。
相反,ClickHouse 是一个面向列的数据库,针对读取密集型分析和高吞吐量追加操作进行了优化。虽然它原生支持就地更新和删除,但必须谨慎使用它们以避免高 I/O。或者,可以重构表以将删除和更新转换为追加操作,在这些操作中,它们被异步和/或在读取时处理,从而反映了对高吞吐量数据摄取和高效查询性能的关注,而不是实时数据操作。
在 ClickHouse 中更新数据的方法
在 ClickHouse 中更新数据有几种方法,每种方法都有其自身的优势和性能特点。您应该根据您的数据模型和您打算更新的数据量选择合适的方法。
对于这两种操作,如果提交的 mutations 数量持续超过在某个时间间隔内在后台处理的 mutations 数量,则必须应用的未物化 mutations 队列将继续增长。这将导致 SELECT
查询性能最终下降。
总而言之,应谨慎发出更新操作,并应使用 system.mutations
表密切跟踪 mutations 队列。不要像在 OLTP 数据库中那样频繁发出更新。如果您需要频繁更新,请参阅 ReplacingMergeTree。
方法 | 语法 | 何时使用 |
---|---|---|
更新 mutation | ALTER TABLE [表] UPDATE | 当数据必须立即更新到磁盘时使用(例如,为了合规性)。对 SELECT 性能产生负面影响。 |
轻量级更新 | ALTER TABLE [表] UPDATE | 使用 SET apply_mutations_on_fly = 1; 启用。在更新少量数据时使用。行在所有后续 SELECT 查询中立即返回更新后的数据,但最初仅在内部标记为磁盘上已更新。 |
ReplacingMergeTree | ENGINE = ReplacingMergeTree | 在更新大量数据时使用。此表引擎针对合并时的数据去重进行了优化。 |
CollapsingMergeTree | ENGINE = CollapsingMergeTree(Sign) | 在频繁更新单个行时使用,或者在需要维护随时间变化的对象最新状态的场景中使用。例如,跟踪用户活动或文章统计信息。 |
以下是在 ClickHouse 中更新数据的不同方法的摘要
更新 Mutations
可以通过 ALTER TABLE … UPDATE
命令发出更新 mutations,例如
ALTER TABLE posts_temp
(UPDATE AnswerCount = AnswerCount + 1 WHERE AnswerCount = 0)
这些操作是极其 I/O 密集型的,会重写所有与 WHERE
表达式匹配的 parts。此过程没有原子性 - parts 会在准备好后立即替换为 mutated parts,并且在 mutation 期间开始执行的 SELECT
查询将看到来自已 mutated parts 的数据以及来自尚未 mutated parts 的数据。用户可以通过 systems.mutations 表跟踪进度状态。这些是 I/O 密集型操作,应谨慎使用,因为它们会影响集群 SELECT
性能。
阅读更多关于 更新 mutations 的信息。
轻量级更新
轻量级更新提供了一种更新行的方法,使得它们立即更新,并且后续的 SELECT
查询将自动返回更改后的值(这会产生开销并减慢查询速度)。这有效地解决了普通 mutations 的原子性限制。我们在下面展示一个例子
SET apply_mutations_on_fly = 1;
SELECT ViewCount
FROM posts
WHERE Id = 404346
┌─ViewCount─┐
│ 26762 │
└───────────┘
1 row in set. Elapsed: 0.115 sec. Processed 59.55 million rows, 238.25 MB (517.83 million rows/s., 2.07 GB/s.)
Peak memory usage: 113.65 MiB.
-increment count
ALTER TABLE posts
(UPDATE ViewCount = ViewCount + 1 WHERE Id = 404346)
SELECT ViewCount
FROM posts
WHERE Id = 404346
┌─ViewCount─┐
│ 26763 │
└───────────┘
1 row in set. Elapsed: 0.149 sec. Processed 59.55 million rows, 259.91 MB (399.99 million rows/s., 1.75 GB/s.)
请注意,对于轻量级更新,仍然使用 mutation 来更新数据;它只是不会立即物化并在 SELECT
查询期间应用。它仍将在后台作为异步进程应用,并产生与 mutation 相同的巨大开销,因此是一种应谨慎使用的 I/O 密集型操作。可以与此操作一起使用的表达式也受到限制(有关 详细信息,请参见此处)。
阅读更多关于 轻量级更新 的信息。
Collapsing Merge Tree
源于更新开销大但可以利用插入来执行更新的想法,CollapsingMergeTree
表引擎可以与 sign
列一起使用,作为一种告诉 ClickHouse 更新特定行的方式,方法是折叠(删除)一对 sign 为 1
和 -1
的行。如果为 sign
列插入 -1
,则将删除整行。如果为 sign
列插入 1
,ClickHouse 将保留该行。要更新的行根据创建表时在 ORDER BY ()
语句中使用的排序键进行标识。
CREATE TABLE UAct
(
UserID UInt64,
PageViews UInt8,
Duration UInt8,
Sign Int8 -- A special column used with the CollapsingMergeTree table engine
)
ENGINE = CollapsingMergeTree(Sign)
ORDER BY UserID
INSERT INTO UAct VALUES (4324182021466249494, 5, 146, 1)
INSERT INTO UAct VALUES (4324182021466249494, 5, 146, -1) -- sign = -1 signals to update the state of this row
INSERT INTO UAct VALUES (4324182021466249494, 6, 185, 1) -- the row is replaced with the new state
SELECT
UserID,
sum(PageViews * Sign) AS PageViews,
sum(Duration * Sign) AS Duration
FROM UAct
GROUP BY UserID
HAVING sum(Sign) > 0
┌──────────────UserID─┬─PageViews─┬─Duration─┐
│ 4324182021466249494 │ 6 │ 185 │
└─────────────────────┴───────────┴──────────┘
上述更新方法要求用户在客户端维护状态。虽然从 ClickHouse 的角度来看这是最有效的,但在大规模使用时可能很复杂。
我们建议阅读 CollapsingMergeTree
的文档,以获得更全面的概述。