成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

在 ClickHouse 中處理更新和刪除以及 Upserts

大數據 數據分析
根據你的目標和性能要求,ClickHouse 有多種更新和刪除數據的方法。本文的其余部分將描述每種方法及其優缺點,以及解決一些常見挑戰的輕量級刪除的一些最新進展。我們推薦最佳實踐,并強調在考慮一種方法時需要考慮的一些重要事項。

作為世界上最快的實時分析數據庫,ClickHouse 的許多工作負載都涉及大量的數據,這些數據只寫一次,不經常修改(例如,物聯網設備產生的遙測事件或電子商務網站產生的客戶點擊)。雖然這些通常是不可變的,但在分析期間提供上下文的其他關鍵數據集(例如,基于設備或客戶 ID 的信息查找表)可能需要修改。

根據你的目標和性能要求,ClickHouse 有多種更新和刪除數據的方法。本文的其余部分將描述每種方法及其優缺點,以及解決一些常見挑戰的輕量級刪除的一些最新進展。我們推薦最佳實踐,并強調在考慮一種方法時需要考慮的一些重要事項。

在繼續之前,確定更新是否是解決問題的最佳方法。例如,對于不經常更改的數據,對數據進行版本控制可能是更好的選擇。在存儲效率和查詢性能方面,ClickHouse 是排名第一的分析數據庫,所以在許多情況下,僅僅保存多個版本的數據而不是更新可能會更好。

輕量級刪除

輕量級刪除代表首選和最有效的方式從 ClickHouse 刪除數據。通過 DELETE FROM 表語法,用戶可以指定一個條件來刪除特定的行,如下所示:

  • https://clickhouse.com/docs/en/guides/developer/lightweght-delete/#lightweight-delete-internals
DELETE FROM table WHERE col1 = 'Hi' AND col2 = 2

默認情況下,該操作是異步的,除非 mutations_sync 設置為 1(見下文)。執行刪除時,ClickHouse 會為每一行保存一個掩碼,在 _row_exists 列中表示是否被刪除。隨后的查詢依次排除這些已刪除的行,如下所示。

圖片圖片

在內部,ClickHouse 將數據分成幾個部分,每個部分包含列數據文件和索引。常規的合并周期負責組合(合并)和重寫這些部分。這確保了文件的數量不會隨著插入更多數據而繼續增長,從而保持查詢的速度。這些合并考慮輕量級刪除,不包括那些在新形成的部分中標記為要刪除的行。

圖片圖片

它于 22.8 發布,在撰寫本文時仍處于試驗階段,輕量級刪除將在下一個版本中成為生產就緒。在此之前,使用輕量級刪除需要設置allow_experimental_lightweight_delete=true。

用戶應該意識到,依靠正常的后臺合并周期,行最終只會從磁盤中刪除。雖然從搜索結果中排除,但這些行將駐留在磁盤上,直到它們的部分被合并。這種情況發生所需的時間是不確定的。這有幾個含義:

  • 節省的空間不會像通過突變發出刪除那樣直接——見下文。如果節省空間非常重要,例如磁盤空間不足,請考慮使用突變。
  • 由于不能保證刪除,有遵從性需求的用戶可能希望使用突變來確保刪除數據。

輕量級刪除操作的成本取決于 WHERE 子句中匹配行的數量和當前數據部分的數量。當匹配少量行時,此操作將是最有效的。用戶還應該意識到,輕量級刪除在寬部分(列數據文件單獨存儲)和緊湊部分(所有列數據使用單個文件)上表現最好。前者允許將掩碼 _row_exists 存儲為一個單獨的文件,從而允許它獨立于其他列進行寫入。通常,緊湊的零件將在插入后成形。一旦部分超過一定的大小(例如,由于合并),就使用寬格式。對于大多數工作負載,這不應該是一個問題。

  • https://clickhouse.com/docs/en/engines/table-engines/mergetree-family/mergetree/#mergetree-data-storage

最后,請注意,輕量級刪除使用與我們下面描述的相同的突變隊列和后臺線程。關于內部實現的更多細節,我們推薦使用這里的文檔。

  • https://clickhouse.com/docs/en/guides/developer/lightweght-delete/#lightweight-delete-internals

突變

使用突變更新數據

更新 ClickHouse 表中的數據最簡單的方法是使用 ALTER…UPDATE 語句。

  • https://clickhouse.com/docs/en/sql-reference/statements/alter/update
ALTER TABLE table
    UPDATE col1 = 'Hi' WHERE col2 = 2

該查詢將使用給定的過濾器更新表 table 上的 col1。

與一些數據庫不同,ClickHouse 的 ALTER UPDATE 語句默認情況下是異步的。這意味著更新發生在后臺,您不會立即對表產生影響。這個更新表的過程稱為突變。

  • https://clickhouse.com/docs/en/sql-reference/statements/alter/#mutations

圖片圖片

這里需要注意的是,更新數據是一個繁重的查詢,因為 ClickHouse 必須做大量的工作來優化存儲和處理。突變操作強制重寫包含要刪除的行的所有數據部分,在形成新部分時排除目標行。這可能會導致相當大的 I/O 和集群開銷,因此要謹慎使用它,或者考慮下面討論的替代方案。

使用突變刪除數據

與更新一樣,刪除也可以通過突變進行,并提供輕量級刪除的另一種選擇。在大多數情況下,由于重寫所有列的突變成本,輕量級刪除更適合于數據刪除。更具體地說,與輕量級刪除不同,所有列都被重寫,而不僅僅是一個 _row_exists 掩碼列。

然而,考慮到輕量級刪除的 “最終從磁盤刪除數據” 屬性,用戶可能更喜歡這種基于突變的方法來實現有保證的磁盤空間節省。此外,當用戶需要保證從磁盤中刪除數據時,例如由于遵從性原因,這種方法是合適的。

ALTER TABLE table
    DELETE WHERE col2 = 3

在此查詢中,刪除 col2 值為 3 的所有行。與其他修改類似,默認情況下,刪除也是異步的。可以使用上述相同的 mutations_sync 設置使其同步。

檢查突變進展

由于突變是異步運行的,因此可以通過 system.mutations 表進行監測。這允許用戶需要檢查他們在表上的特定突變的進度。

SELECT
    command,
    is_done
FROM system.mutations
WHERE table = 'tablename'

┌─command───────────────────────────────┬─is_done─┐
│ UPDATE col1 = 'Hi' WHERE col2 = 2     │       1 │
│ UPDATE col1 = 'All hi' WHERE col2 > 0 │       0 │
└───────────────────────────────────────┴─────────┘

如果 is_done 的值對于特定的突變是 0,那么它仍然在執行。對每個表部分執行突變,突變后的部分立即可用:

圖片圖片

同步更新

對于需要同步更新的用戶,mutations_sync 參數可以設置為 1(或者 2,如果我們也想等到所有副本也被更新):

  • https://clickhouse.com/docs/en/operations/settings/settings/#mutations_sync
SET mutations_sync = 1

現在我們的更新查詢將等待突變完成:

ALTER TABLE table
    UPDATE col1 = 'bye' WHERE col2 > 0

0 rows in set. Elapsed: 1.182 sec.

注意,當 ClickHouse 等待后臺突變完成時,這個查詢花了 1 秒鐘的時間。注意,此參數也適用于輕量級刪除。

更新整個表

在某些情況下,用戶需要更新整個列的值。最初,用戶可能會嘗試使用不帶 WHERE 子句的 ALTER TABLE 查詢來實現這一點。然而,這是失敗的,如下所示:

ALTER TABLE table UPDATE col1 = 'bye';

Syntax error: failed at position 38 (end of query):
ALTER TABLE table UPDATE col1 = 'bye';

ClickHouse 不會讓你更新整個表,因為更新是繁重的。強迫 ClickHouse 接受此操作的一種方法是使用始終為真過濾器:

ALTER TABLE table
    UPDATE col1 = 'bye' WHERE true

然而,更優的方法是創建一個新列,將新值作為默認值,然后在新舊列之間切換。例如:

ALTER TABLE table ADD COLUMN col1_new String DEFAULT 'global hi';

ALTER TABLE table
    RENAME COLUMN col1 TO col1_old,
    RENAME COLUMN col1_new TO col1,
    DROP COLUMN col1_old;

我們使用 col1_new 列的默認值來指定要使用的更新值。這是安全且高效得多的,因為我們跳過了這里的重突變操作。

使用 JOIN 進行更新和刪除

有時,我們需要根據關系刪除或更新行; 因此,我們必須 join 表。在 ClickHouse 中,使用 Join 表引擎和 joinGet 函數可以最好地實現這一點。假設我們有兩個表-一個與所有的頁面瀏覽量和另一個其他所有的登錄跟蹤:

CREATE TABLE pageviews
(
    `user_id` UInt64,
    `time` DateTime,
    `session_id` UInt64
)
ENGINE = MergeTree
ORDER BY time;

CREATE TABLE logins
(
    `user_id` UInt64,
    `time` DateTime
)
ENGINE = MergeTree
ORDER BY time;

這兩個表之間的區別在于,logins 表每個會話只存儲一個事件。假設在某個時間點,我們決定將 session_id 列添加到 logins 表中:

ALTER TABLE logins
    ADD COLUMN `session_id` UInt64

我們現在需要使用 user_id 和 time 上的 JOIN,用 pageviews 表中的相應值更新 logins.session_id 列:

SELECT *
FROM logins AS l
JOIN pageviews AS p ON (p.user_id = l.user_id) AND (p.time = l.time)

┌─user_id─┬────────────────time─┬─p.user_id─┬──────────────p.time─┬─session_id─┐
│       2 │ 2023-01-09 12:23:16 │         2 │ 2023-01-09 12:23:16 │ 2752888102 │
│       1 │ 2023-01-09 13:23:16 │         1 │ 2023-01-09 13:23:16 │ 4135462640 │
└─────────┴─────────────────────┴───────────┴─────────────────────┴────────────┘

首先,我們需要創建并填充一個特殊的 Join 表:

CREATE TABLE pageviews_join
ENGINE = Join(ANY, LEFT, user_id, time) AS
SELECT *
FROM pageviews

該表將允許我們在執行更新查詢時使用 joinGet 函數來基于 JOIN 獲取值:

ALTER TABLE logins
    UPDATE session_id = joinGet('pageviews_join', 'session_id', user_id, time) WHERE session_id = 0

我們可以看到,logins 表被相應的更新了 JOIN:

SELECT * FROM logins

┌─user_id─┬────────────────time─┬─session_id─┐
│       2 │ 2023-01-09 12:23:16 │ 2752888102 │
│       1 │ 2023-01-09 13:23:16 │ 4135462640 │
└─────────┴─────────────────────┴────────────┘

因為我們已經通過添加 session_id 列更改了 logins 表,所以我們可以在更改完成后 DROP pageviews_join 表(刪除之前請檢查 system.mutations 表以確定):

DROP TABLE pageviews_join

同樣的方法也可以用于通過輕量級或基于突變的刪除來刪除數據。

高效刪除大塊數據塊

如果我們必須刪除大的數據塊,用戶可以對表進行分區,以便根據需要刪除分區。這是一個輕量級操作。假設我們有以下表:

CREATE TABLE hits
(
    `project` String,
    `url` String,
    `time` DateTime,
    `hits` UInt32
)
ENGINE = MergeTree
PARTITION BY project
ORDER BY (project, path, time)

通過 project 列對該表進行分區,我們可以通過刪除整個分區來刪除具有特定 project 值的行。讓我們刪除所有帶有 project = c 的內容:

ALTER TABLE hits
    DROP PARTITION 'c'

這里,c 是我們想要刪除的 project 列值:

圖片圖片

可用分區的列表可以在 system.parts 表中找到:

SELECT partition
FROM system.parts
WHERE table = 'hits'

┌─partition─┐
│ c         │
│ a         │
│ b         │
└───────────┘

我們還可以使用 DETACH 和 ATTACH 語句在表之間移動分區(例如,如果我們想將數據移動到 trash 表而不是刪除它)。

在 DDL 中設置分區時,要注意按具有高基數的列或表達式進行分區的常見缺陷。這可能導致創建許多部件,從而導致性能問題。

定期刪除舊數據

對于時間序列數據,我們可能希望定期刪除過時的數據。ClickHouse 對于這個確切的用例具有 TTL 特性。這需要配置一個表,并指定我們希望刪除哪些數據以及何時刪除。假設我們想從 hits 表中刪除超過一個月的數據:

  • https://docs.google.com/document/d/1CAmuBror9fGLZwFCiBUH5rt89LKq-ylA1Lpa6-FrJns/edit
  • https://clickhouse.com/docs/en/engines/table-engines/mergetree-family/mergetree#table_engine-mergetree-ttl
ALTER TABLE hits
    MODIFY TTL time + INTERVAL 1 MONTH

在這里,我們要求 ClickHouse 刪除當前時間的時間列值超過一個月的所有行。還可以在列上設置 TTL,以便在一段時間后將其值重置為默認值。通過按日期進行分區,四舍五入到適當的時間單位,例如,days,可以使該操作更有效。在執行 TTL 規則時,ClickHouse 將以最有效的方式自動刪除數據。同樣,表不應該按照高基數(例如毫秒粒度)的時間列進行分區,以避免高部分計數。通常按天或月劃分對于大多數 TTL 操作來說就足夠了。

  • https://clickhouse.com/docs/en/engines/table-engines/mergetree-family/mergetree#mergetree-column-ttl

使用 CollapsingMergeTree 刪除和更新

如果我們必須頻繁地更新單個行,我們可以使用 CollapsingMergeTree 引擎來有效地管理數據更新。

  • https://clickhouse.com/docs/en/engines/table-engines/mergetree-family/collapsingmergetree/

假設我們有一個包含文章統計信息的表,用于跟蹤每篇文章的閱讀深度。我們希望用一行顯示每個用戶閱讀每篇文章的深度。這里的挑戰在于,我們必須在用戶閱讀文章時更新實際的閱讀進度。讓我們為數據創建一個表:

CREATE TABLE article_reads
(
    `user_id` UInt32,
    `article_id` UInt32,
    `read_to` UInt8,
    `read_start` DateTime,
    `read_end` DateTime,
    `sign` Int8
)
ENGINE = CollapsingMergeTree(sign)
ORDER BY (read_start, article_id, user_id)

特殊的 sign 列用于 CollapsingMergeTree,作為告訴 ClickHouse 我們想要更新特定行的一種方式。如果我們在 sign 列中插入 -1,整行就會被刪除。如果我們插入一行 sign = 1,ClickHouse 將保留這一行。要更新的行是根據創建表時在 ORDER BY () DDL 語句中使用的排序鍵來標識的:

圖片圖片

為了滿足排序鍵上的重復數據刪除條件,我們必須為 read_start, article_id, user_id 列插入相同的值來更新一行。例如,當用戶開始閱讀一篇文章時,我們插入以下行:

INSERT INTO article_reads
            VALUES(1, 12, 0, now(), now(), 1);

現在我們在表中有了一行:

SELECT *
FROM article_reads

┌─user_id─┬─article_id─┬─read_to─┬──────────read_start─┬────────────read_end─┬─sign─┐
│       1 │         12 │       0 │ 2023-01-06 15:20:32 │ 2023-01-06 15:20:32 │    1 │
└─────────┴────────────┴─────────┴─────────────────────┴─────────────────────┴──────┘

一分鐘后,當用戶達到文章的 70% 時,我們插入以下 2 行:

INSERT INTO article_reads
            VALUES(1, 12, 0, '2023-01-06 15:20:32', now(), -1),
                  (1, 12, 70, '2023-01-06 15:20:32', now(), 1);

第一行的 sign=-1 是用來告訴 ClickHouse,它應該刪除現有的行(基于值在 ORDER BY 元組- read_start, article_id 和 user_id 列)。第二個插入的行( sign=1 )是 read_to 列設置為新值 70 的新行。

由于數據更新是在后臺進行的,因此最終的結果是一致的,我們應該對 sign 列進行過濾以獲得正確的結果:

  • https://clickhouse.com/docs/en/engines/table-engines/mergetree-family/collapsingmergetree/#table_engine-collapsingmergetree-collapsing-algorithm
SELECT
    article_id,
    user_id,
    max(read_end),
    max(read_to)
FROM article_reads
WHERE sign = 1
GROUP BY
    user_id,
    article_id

┌─article_id─┬─user_id─┬───────max(read_end)─┬─max(read_to)─┐
│         12 │       1 │ 2023-01-06 15:21:59 │           70 │
└────────────┴─────────┴─────────────────────┴──────────────┘

1 row in set. Elapsed: 0.004 sec.

現在,CollapsingMergreTree 引擎會在后臺有效地從存儲中刪除被取消的行,所以我們不必手動刪除它們。你可以在這里找到更多使用 CollapsingMergeTree 引擎的例子。

  • https://www.google.com/url?q=https://clickhouse.com/docs/en/engines/table-engines/mergetree-family/collapsingmergetree/%23example-of-use&sa=D&source=docs&ust=1678440218544759&usg=AOvVaw3vOSZ1Rbl24kvzHT48kuyp

使用版本控制和 ReplacingMergeTree 的 Upserts

對于更復雜的情況,我們可能希望使用基于 ReplacingMergeTree 引擎的版本控制。這個引擎實現了一種在其他dbms中執行UPSERT的有效方法,即使用一個特殊的版本列來跟蹤應該刪除哪些行。如果存在多個具有相同排序鍵的行,則只有具有最大版本的行保留在存儲中,而其他行則被刪除:

圖片圖片

對于前面的閱讀文章的例子,我們可以使用下面的結構:

CREATE TABLE article_reads
(
    `user_id` UInt32,
    `article_id` UInt32,
    `read_to` UInt8,
    `read_time` DateTime,
    `version` Int32
)
ENGINE = ReplacingMergeTree(version)
ORDER BY (article_id, user_id)

請注意特殊的 version 數字列,ReplacingMergeTree 引擎將使用它來標記要刪除的行。讓我們模擬一個用戶在 0 到 80% 的時間內閱讀一篇文章:

INSERT INTO article_reads
           VALUES(1, 12, 0, '2023-01-06 15:20:32', 1),
                 (1, 12, 30, '2023-01-06 15:21:42', 2),
                 (1, 12, 45, '2023-01-06 15:22:13', 3),
                 (1, 12, 80, '2023-01-06 15:23:10', 4);

這里,我們在跟蹤讀取進度時增加 version 列的值。刪除行的過程也是通過正常的合并周期在后臺執行的,所以我們需要根據查詢時的最新版本進行過濾:

SELECT *
FROM article_reads
WHERE (user_id = 1) AND (article_id = 12)
ORDER BY version DESC
LIMIT 1

┌─user_id─┬─article_id─┬─read_to─┬───────────read_time─┬─version─┐
│       1 │         12 │      80 │ 2023-01-06 15:23:10 │       5 │
└─────────┴────────────┴─────────┴─────────────────────┴─────────┘

或者我們可以使用 LIMIT 1 BY 來獲取最新版本的行列表:

SELECT
    user_id,
    article_id,
    read_to
FROM article_reads
ORDER BY version DESC
LIMIT 1 BY
    user_id,
    article_id

┌─user_id─┬─article_id─┬─read_to─┐
│       1 │         12 │      80 │
└─────────┴────────────┴─────────┘

再次,我們不必關心舊版本的刪除-這是由 ClickHouse 在后臺自動完成。

總結

在分析環境中更新和刪除數據可能具有挑戰性,并且會極大地影響數據庫性能。為了解決這個問題,ClickHouse 提供了多種強大的方法來更新和刪除不同情況下有效的數據:

  • 輕量刪除通過 DELETE FROM 語法刪除數據從 ClickHouse。這是最有效的刪除數據的方法,前提是不需要立即節省磁盤空間,并且用戶可以容忍刪除的數據“存在”在磁盤上。
  • 在需要立即節省磁盤空間的情況下,通過 ALTER…DELETE 進行基于突變的刪除。例如,遵從性需求需要保證從磁盤中刪除數據。
  • 在不規律和不頻繁的變化情況下,使用 ALTER…UPDATE 進行基于突變的更新
  • 使用 TTLs 定期刪除基于日期/時間的(過時的)數據;
  • https://clickhouse.com/docs/en/engines/table-engines/mergetree-family/mergetree#mergetree-table-ttl
  • 使用 CollapsingMergeTree 頻繁地更新或刪除單個行。
  • https://clickhouse.com/docs/en/engines/table-engines/mergetree-family/collapsingmergetree/
  • 使用 ReplacingMergeTree 實現基于版本控制的 upsert(插入/更新)。
  • https://clickhouse.com/docs/en/engines/table-engines/mergetree-family/replacingmergetree/
  • 定期刪除大數據塊時刪除分區。
  • https://clickhouse.com/docs/en/sql-reference/statements/alter/partition/#drop-partitionpart
  • 創建新列(并刪除舊列)可能也是更新整個表的更有效的方法。

Refs

  • https://clickhouse.com/blog/handling-updates-and-deletes-in-clickhouse
責任編輯:武曉燕 來源: 黑客下午茶
相關推薦

2009-09-25 11:34:54

Hibernate處理Hibernate批量

2010-11-22 15:34:17

MySQL多表更新

2010-02-04 14:06:01

Linux rpm包

2024-01-08 09:10:35

PostgreSQL數據庫管理系統

2009-07-24 14:02:39

ASP.NET 2.0

2011-09-06 10:26:23

SQL Azure故障

2013-06-18 17:54:14

網件ReadyNAS存儲

2018-04-28 09:06:40

Windows操作系統功能

2023-05-04 07:43:47

MySQL數據庫

2013-12-25 10:08:42

ember.js異步處理

2021-11-04 09:02:10

微軟Office應用程序

2024-01-25 16:28:10

2010-09-01 15:30:24

SQL刪除

2013-03-27 09:44:57

Surface RTSurface pro

2024-02-05 09:49:20

2010-09-01 16:08:03

遺留系統Java

2010-09-17 14:01:20

SQL插入

2010-08-24 09:19:59

2019-03-18 09:40:44

Linux命令編程語言

2023-02-06 15:24:50

軟件開發MVP功能
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 中文字幕第5页 | 亚洲精品www久久久久久广东 | 欧美精品久久久久久久久久 | 91视频一区 | 一区二区三区四区五区在线视频 | 日韩精品1区2区 | 国产精品不卡视频 | 成人精品一区亚洲午夜久久久 | 日本高清视频网站 | 免费福利视频一区二区三区 | 美日韩免费 | 盗摄精品av一区二区三区 | 一区二区三区影院 | 午夜激情视频 | 日韩综合在线播放 | 亚洲国产成人精品一区二区 | 青青草社区 | 一本久久a久久精品亚洲 | 欧美日韩一区二区三区在线观看 | 国产成人叼嘿视频在线观看 | 国产精品视频免费观看 | 超碰人人爱 | 中文一区| 久久大陆 | 婷婷午夜天 | 欧美日韩一区精品 | 成人福利电影 | www.888www看片 | 欧美亚洲激情 | 亚洲毛片在线观看 | 亚洲 中文 欧美 日韩 在线观看 | a级毛片毛片免费观看久潮喷 | 日韩精品一区二区三区第95 | 日韩免费网站 | 999精品在线 | 久久青青| 国产精品久久久久久238 | 欧美一级久久久猛烈a大片 日韩av免费在线观看 | 99精品国产一区二区三区 | 亚洲欧美日韩精品久久亚洲区 | 国产精品国产三级国产aⅴ浪潮 |