国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

ClickHouse主鍵索引最佳實(shí)踐

這篇具有很好參考價值的文章主要介紹了ClickHouse主鍵索引最佳實(shí)踐。希望對大家有所幫助。如果存在錯誤或未考慮完全的地方,請大家不吝賜教,您也可以點(diǎn)擊"舉報違法"按鈕提交疑問。

在本文中,我們將深入研究ClickHouse索引。我們將對此進(jìn)行詳細(xì)說明和討論:

  • ClickHouse的索引與傳統(tǒng)的關(guān)系數(shù)據(jù)庫有何不同
  • ClickHouse是怎樣構(gòu)建和使用主鍵稀疏索引的
  • ClickHouse索引的最佳實(shí)踐

這篇文章主要關(guān)注稀疏索引,clickhouse主鍵使用的就是稀疏索引。

數(shù)據(jù)集

在本文中,我們將使用一個匿名的web流量數(shù)據(jù)集。

  • 我們將使用樣本數(shù)據(jù)集中的887萬行(事件)的子集。
  • 未壓縮的數(shù)據(jù)大小為887萬個事件和大約700mb。當(dāng)存儲在ClickHouse時,壓縮為200mb。
  • 在我們的子集中,每行包含三列,表示在特定時間(EventTime列)單擊URL (URL列)的互聯(lián)網(wǎng)用戶(UserID列)。

通過這三個列,我們已經(jīng)可以制定一些典型的web分析查詢,如:

  • 某個用戶點(diǎn)擊次數(shù)最多的前10個url是什么?
  • 點(diǎn)擊某個URL次數(shù)最多的前10名用戶是誰?
  • 用戶點(diǎn)擊特定URL的最頻繁時間(比如一周中的幾天)是什么?

測試環(huán)境

本文檔中給出的所有運(yùn)行時數(shù)據(jù)都是在帶有Apple M1 Pro芯片和16GB RAM的MacBook Pro上本地運(yùn)行ClickHouse 22.2.1。

全表掃描

為了了解在沒有主鍵的情況下如何對數(shù)據(jù)集執(zhí)行查詢,我們通過執(zhí)行以下SQL DDL語句(使用MergeTree表引擎)創(chuàng)建了一個表:

CREATE TABLE hits_NoPrimaryKey
(
    `UserID` UInt32,
    `URL` String,
    `EventTime` DateTime
)
ENGINE = MergeTree
PRIMARY KEY tuple();

接下來,使用以下插入SQL將命中數(shù)據(jù)集的一個子集插入到表中。這個SQL使用URL表函數(shù)和類型推斷從clickhouse.com加載一個數(shù)據(jù)集的一部分?jǐn)?shù)據(jù):

INSERT INTO hits_NoPrimaryKey SELECT
   intHash32(c11::UInt64) AS UserID,
   c15 AS URL,
   c5 AS EventTime
FROM url('https://datasets.clickhouse.com/hits/tsv/hits_v1.tsv.xz')
WHERE URL != '';

結(jié)果:

Ok.

0 rows in set. Elapsed: 145.993 sec. Processed 8.87 million rows, 18.40 GB (60.78 thousand rows/s., 126.06 MB/s.)

ClickHouse客戶端輸出了執(zhí)行結(jié)果,插入了887萬行數(shù)據(jù)。

最后,為了簡化本文后面的討論,并使圖表和結(jié)果可重現(xiàn),我們使用FINAL關(guān)鍵字optimize該表:

OPTIMIZE TABLE hits_NoPrimaryKey FINAL;

NOTE

一般來說,不需要也不建議在加載數(shù)據(jù)后立即執(zhí)行optimize。對于這個示例,為什么需要這樣做是很明顯的。

現(xiàn)在我們執(zhí)行第一個web分析查詢。以下是用戶id為749927693的互聯(lián)網(wǎng)用戶點(diǎn)擊次數(shù)最多的前10個url:

SELECT URL, count(URL) as Count
FROM hits_NoPrimaryKey
WHERE UserID = 749927693
GROUP BY URL
ORDER BY Count DESC
LIMIT 10;

結(jié)果:

┌─URL────────────────────────────┬─Count─┐
│ http://auto.ru/chatay-barana.. │   170 │
│ http://auto.ru/chatay-id=371...│    52 │
│ http://public_search           │    45 │
│ http://kovrik-medvedevushku-...│    36 │
│ http://forumal                 │    33 │
│ http://korablitz.ru/L_1OFFER...│    14 │
│ http://auto.ru/chatay-id=371...│    14 │
│ http://auto.ru/chatay-john-D...│    13 │
│ http://auto.ru/chatay-john-D...│    10 │
│ http://wot/html?page/23600_m...│     9 │
└────────────────────────────────┴───────┘

10 rows in set. Elapsed: 0.022 sec.
Processed 8.87 million rows,
70.45 MB (398.53 million rows/s., 3.17 GB/s.)

ClickHouse客戶端輸出表明,ClickHouse執(zhí)行了一個完整的表掃描!我們的表的887萬行中的每一行都被加載到ClickHouse中,這不是可擴(kuò)展的。

為了使這種(方式)更有效和更快,我們需要使用一個具有適當(dāng)主鍵的表。這將允許ClickHouse自動(基于主鍵的列)創(chuàng)建一個稀疏的主索引,然后可以用于顯著加快我們示例查詢的執(zhí)行。

包含主鍵的表

創(chuàng)建一個包含聯(lián)合主鍵UserID和URL列的表:

CREATE TABLE hits_UserID_URL
(
    `UserID` UInt32,
    `URL` String,
    `EventTime` DateTime
)
ENGINE = MergeTree
PRIMARY KEY (UserID, URL)
ORDER BY (UserID, URL, EventTime)
SETTINGS index_granularity = 8192, index_granularity_bytes = 0;

為了簡化本文后面的討論,并使圖和結(jié)果可重現(xiàn),使用DDL語句有如下說明:

  • 通過ORDER BY子句指定表的復(fù)合排序鍵

  • 通過設(shè)置配置控制主索引有多少索引項(xiàng):

    • index_granularity: 顯式設(shè)置為其默認(rèn)值8192。這意味著對于每一組8192行,主索引將有一個索引條目,例如,如果表包含16384行,那么索引將有兩個索引條目。

    • index_granularity_bytes
      設(shè)置為0表示禁止

      自適應(yīng)索引粒度

      。自適應(yīng)索引粒度意味著ClickHouse自動為一組n行創(chuàng)建一個索引條目

      • 如果n小于8192,但n行的合并行數(shù)據(jù)大小大于或等于10MB (index_granularity_bytes的默認(rèn)值)或
      • n達(dá)到8192

上面DDL語句中的主鍵會基于兩個指定的鍵列創(chuàng)建主索引。

插入數(shù)據(jù):

INSERT INTO hits_UserID_URL SELECT
   intHash32(c11::UInt64) AS UserID,
   c15 AS URL,
   c5 AS EventTime
FROM url('https://datasets.clickhouse.com/hits/tsv/hits_v1.tsv.xz')
WHERE URL != '';

結(jié)果:

0 rows in set. Elapsed: 149.432 sec. Processed 8.87 million rows, 18.40 GB (59.38 thousand rows/s., 123.16 MB/s.)

optimize表:

OPTIMIZE TABLE hits_UserID_URL FINAL;

我們可以使用下面的查詢來獲取關(guān)于表的元數(shù)據(jù):

SELECT
    part_type,
    path,
    formatReadableQuantity(rows) AS rows,
    formatReadableSize(data_uncompressed_bytes) AS data_uncompressed_bytes,
    formatReadableSize(data_compressed_bytes) AS data_compressed_bytes,
    formatReadableSize(primary_key_bytes_in_memory) AS primary_key_bytes_in_memory,
    marks,
    formatReadableSize(bytes_on_disk) AS bytes_on_disk
FROM system.parts
WHERE (table = 'hits_UserID_URL') AND (active = 1)
FORMAT Vertical;

結(jié)果:

part_type:                   Wide
path:                        ./store/d9f/d9f36a1a-d2e6-46d4-8fb5-ffe9ad0d5aed/all_1_9_2/
rows:                        8.87 million
data_uncompressed_bytes:     733.28 MiB
data_compressed_bytes:       206.94 MiB
primary_key_bytes_in_memory: 96.93 KiB
marks:                       1083
bytes_on_disk:               207.07 MiB


1 rows in set. Elapsed: 0.003 sec.

客戶端輸出表明:

  • 表數(shù)據(jù)以wide format存儲在一個特定目錄,每個列有一個數(shù)據(jù)文件和mark文件。
  • 表有887萬行數(shù)據(jù)。
  • 未壓縮的數(shù)據(jù)有733.28 MB。
  • 壓縮之后的數(shù)據(jù)有206.94 MB。
  • 有1083個主鍵索引條目,大小是96.93 KB。
  • 在磁盤上,表的數(shù)據(jù)、標(biāo)記文件和主索引文件總共占用207.07 MB。

針對海量數(shù)據(jù)規(guī)模的索引設(shè)計(jì)

在傳統(tǒng)的關(guān)系數(shù)據(jù)庫管理系統(tǒng)中,每個表行包含一個主索引。對于我們的數(shù)據(jù)集,這將導(dǎo)致主索引——通常是一個B(+)-Tree的數(shù)據(jù)結(jié)構(gòu)——包含887萬個條目。

這樣的索引允許快速定位特定的行,從而提高查找點(diǎn)查和更新的效率。在B(+)-Tree數(shù)據(jù)結(jié)構(gòu)中搜索一個條目的平均時間復(fù)雜度為O(log2n)。對于一個有887萬行的表,這意味著需要23步來定位任何索引條目。

這種能力是有代價的:額外的磁盤和內(nèi)存開銷,以及向表中添加新行和向索引中添加條目時更高的插入成本(有時還需要重新平衡B-Tree)。

考慮到與B-Tee索引相關(guān)的挑戰(zhàn),ClickHouse中的表引擎使用了一種不同的方法。ClickHouseMergeTree Engine引擎系列被設(shè)計(jì)和優(yōu)化用來處理大量數(shù)據(jù)。

這些表被設(shè)計(jì)為每秒接收數(shù)百萬行插入,并存儲非常大(100 pb)的數(shù)據(jù)量。

數(shù)據(jù)被一批一批的快速寫入表中,并在后臺應(yīng)用合并規(guī)則。

在ClickHouse中,每個數(shù)據(jù)部分(data part)都有自己的主索引。當(dāng)他們被合并時,合并部分的主索引也被合并。

在大規(guī)模中情況下,磁盤和內(nèi)存的效率是非常重要的。因此,不是為每一行創(chuàng)建索引,而是為一組數(shù)據(jù)行(稱為顆粒(granule))構(gòu)建一個索引條目。

之所以可以使用這種稀疏索引,是因?yàn)镃lickHouse會按照主鍵列的順序?qū)⒁唤M行存儲在磁盤上。

與直接定位單個行(如基于B-Tree的索引)不同,稀疏主索引允許它快速(通過對索引項(xiàng)進(jìn)行二分查找)識別可能匹配查詢的行組。

然后潛在的匹配行組(顆粒)以并行的方式被加載到ClickHouse引擎中,以便找到匹配的行。

這種索引設(shè)計(jì)允許主索引很小(它可以而且必須完全適合主內(nèi)存),同時仍然顯著加快查詢執(zhí)行時間:特別是對于數(shù)據(jù)分析用例中常見的范圍查詢。

下面詳細(xì)說明了ClickHouse是如何構(gòu)建和使用其稀疏主索引的。在本文后面,我們將討論如何選擇、移除和排序用于構(gòu)建索引的表列(主鍵列)的一些最佳實(shí)踐。

數(shù)據(jù)按照主鍵排序存儲在磁盤上

上面創(chuàng)建的表有:

  • 聯(lián)合主鍵 (UserID, URL)
  • 聯(lián)合排序鍵 (UserID, URL, EventTime)。

NOTE

  • 如果我們只指定了排序鍵,那么主鍵將隱式定義為排序鍵。
  • 為了提高內(nèi)存效率,我們顯式地指定了一個主鍵,只包含查詢過濾的列?;谥麈I的主索引被完全加載到主內(nèi)存中。
  • 為了上下文的一致性和最大的壓縮比例,我們單獨(dú)定義了排序鍵,排序鍵包含當(dāng)前表所有的列(和壓縮算法有關(guān),一般排序之后又更好的壓縮率)。
  • 如果同時指定了主鍵和排序鍵,則主鍵必須是排序鍵的前綴。

插入的行按照主鍵列(以及排序鍵的附加EventTime列)的字典序(從小到大)存儲在磁盤上。

NOTE

ClickHouse允許插入具有相同主鍵列的多行數(shù)據(jù)。在這種情況下(參見下圖中的第1行和第2行),最終的順序是由指定的排序鍵決定的,這里是EventTime列的值。

如下圖所示:ClickHouse是列存數(shù)據(jù)庫。

  • 在磁盤上,每個表都有一個數(shù)據(jù)文件(*.bin),該列的所有值都以壓縮格式存儲,并且
  • 在這個例子中,這887萬行按主鍵列(以及附加的排序鍵列)的字典升序存儲在磁盤上
    • UserID第一位,
    • 然后是URL,
    • 最后是EventTime:

ClickHouse主鍵索引最佳實(shí)踐UserID.bin,URL.bin,和EventTime.bin是UserID,URL,和EventTime列的數(shù)據(jù)文件。

NOTE

  • 因?yàn)橹麈I定義了磁盤上行的字典順序,所以一個表只能有一個主鍵。
  • 我們從0開始對行進(jìn)行編號,以便與ClickHouse內(nèi)部行編號方案對齊,該方案也用于記錄消息。

數(shù)據(jù)被組織成顆粒以進(jìn)行并行數(shù)據(jù)處理

出于數(shù)據(jù)處理的目的,表的列值在邏輯上被劃分為多個顆粒。顆粒是流進(jìn)ClickHouse進(jìn)行數(shù)據(jù)處理的最小的不可分割數(shù)據(jù)集。這意味著,ClickHouse不是讀取單獨(dú)的行,而是始終讀取(以流方式并并行地)整個行組(顆粒)。

NOTE

列值并不物理地存儲在顆粒中,顆粒只是用于查詢處理的列值的邏輯組織方式。

下圖顯示了如何將表中的887萬行(列值)組織成1083個顆粒,這是表的DDL語句包含設(shè)置index_granularity(設(shè)置為默認(rèn)值8192)的結(jié)果。

ClickHouse主鍵索引最佳實(shí)踐

第一個(根據(jù)磁盤上的物理順序)8192行(它們的列值)在邏輯上屬于顆粒0,然后下一個8192行(它們的列值)屬于顆粒1,以此類推。

NOTE

  • 最后一個顆粒(1082顆粒)是少于8192行的。

  • 我們在本指南開頭的“DDL 語句詳細(xì)信息”中提到,我們禁用了自適應(yīng)索引粒度(為了簡化本指南中的討論,并使圖表和結(jié)果可重現(xiàn))。

    因此,示例表中所有顆粒(除了最后一個)都具有相同大小。

  • 對于具有自適應(yīng)索引粒度的表(默認(rèn)情況下索引粒度是自適應(yīng)的),某些粒度的大小可以小于 8192 行,具體取決于行數(shù)據(jù)大小。

  • 我們將主鍵列(UserID, URL)中的一些列值標(biāo)記為橙色。

    這些橙色標(biāo)記的列值是每個顆粒中每個主鍵列的最小值。這里的例外是最后一個顆粒(上圖中的顆粒1082),最后一個顆粒我們標(biāo)記的是最大的值。

    正如我們將在下面看到的,這些橙色標(biāo)記的列值將是表主索引中的條目。

  • 我們從0開始對行進(jìn)行編號,以便與ClickHouse內(nèi)部行編號方案對齊,該方案也用于記錄消息。

每個顆粒對應(yīng)主索引的一個條目

主索引是基于上圖中顯示的顆粒創(chuàng)建的。這個索引是一個未壓縮的扁平數(shù)組文件(primary.idx),包含從0開始的所謂的數(shù)字索引標(biāo)記。

下面的圖顯示了索引存儲了每個顆粒的最小主鍵列值(在上面的圖中用橙色標(biāo)記的值)。 例如:

  • 第一個索引條目(下圖中的“mark 0”)存儲上圖中顆粒0的主鍵列的最小值,
  • 第二個索引條目(下圖中的“mark 1”)存儲上圖中顆粒1的主鍵列的最小值,以此類推。

ClickHouse主鍵索引最佳實(shí)踐

在我們的表中,索引總共有1083個條目,887萬行數(shù)據(jù)和1083個顆粒:

ClickHouse主鍵索引最佳實(shí)踐

NOTE

  • 最后一個索引條目(上圖中的“mark 1082”)存儲了上圖中顆粒1082的主鍵列的最大值。
  • 索引條目(索引標(biāo)記)不是基于表中的特定行,而是基于顆粒。例如,對于上圖中的索引條目‘mark 0’,在我們的表中沒有UserID為240.923且URL為“goal://metry=10000467796a411…”的行,相反,對于該表,有一個顆粒0,在該顆粒中,最小UserID值是240.923,最小URL值是“goal://metry=10000467796a411…”,這兩個值來自不同的行。
  • 主索引文件完全加載到主內(nèi)存中。如果文件大于可用的空閑內(nèi)存空間,則ClickHouse將發(fā)生錯誤。

主鍵條目稱為索引標(biāo)記,因?yàn)槊總€索引條目都標(biāo)志著特定數(shù)據(jù)范圍的開始。對于示例表:

  • UserID index marks:
    主索引中存儲的UserID值按升序排序。
    上圖中的‘mark 1’指示顆粒1中所有表行的UserID值,以及隨后所有顆粒中的UserID值,都保證大于或等于4.073.710。

    正如我們稍后將看到的, 當(dāng)查詢對主鍵的第一列進(jìn)行過濾時,此全局有序使ClickHouse能夠?qū)Φ谝粋€鍵列的索引標(biāo)記使用二分查找算法。

  • URL index marks:
    主鍵列UserID和URL有相同的基數(shù),這意味著第一列之后的所有主鍵列的索引標(biāo)記通常只表示每個顆粒的數(shù)據(jù)范圍。
    例如,‘mark 0’中的URL列所有的值都大于等于goal://metry=10000467796a411..., 然后顆粒1中的URL并不是如此,這是因?yàn)椤甿ark 1‘與‘mark 0‘具有不同的UserID列值。

    稍后我們將更詳細(xì)地討論這對查詢執(zhí)行性能的影響。

主索引被用來選擇顆粒

現(xiàn)在,我們可以在主索引的支持下執(zhí)行查詢。

下面計(jì)算UserID 749927693點(diǎn)擊次數(shù)最多的10個url。

SELECT URL, count(URL) AS Count
FROM hits_UserID_URL
WHERE UserID = 749927693
GROUP BY URL
ORDER BY Count DESC
LIMIT 10;

結(jié)果:

┌─URL────────────────────────────┬─Count─┐
│ http://auto.ru/chatay-barana.. │   170 │
│ http://auto.ru/chatay-id=371...│    52 │
│ http://public_search           │    45 │
│ http://kovrik-medvedevushku-...│    36 │
│ http://forumal                 │    33 │
│ http://korablitz.ru/L_1OFFER...│    14 │
│ http://auto.ru/chatay-id=371...│    14 │
│ http://auto.ru/chatay-john-D...│    13 │
│ http://auto.ru/chatay-john-D...│    10 │
│ http://wot/html?page/23600_m...│     9 │
└────────────────────────────────┴───────┘

10 rows in set. Elapsed: 0.005 sec.
Processed 8.19 thousand rows,
740.18 KB (1.53 million rows/s., 138.59 MB/s.)

ClickHouse客戶端的輸出顯示,沒有進(jìn)行全表掃描,只有8.19千行流到ClickHouse。

如果trace logging打開了,那ClickHouse服務(wù)端日志會顯示ClickHouse正在對1083個UserID索引標(biāo)記執(zhí)行二分查找以便識別可能包含UserID列值為749927693的行的顆粒。這需要19個步驟,平均時間復(fù)雜度為O(log2 n):

...Executor): Key condition: (column 0 in [749927693, 749927693])
...Executor): Running binary search on index range for part all_1_9_2 (1083 marks)
...Executor): Found (LEFT) boundary mark: 176
...Executor): Found (RIGHT) boundary mark: 177
...Executor): Found continuous range in 19 steps
...Executor): Selected 1/1 parts by partition key, 1 parts by primary key,
              1/1083 marks by primary key, 1 marks to read from 1 ranges
...Reading ...approx. 8192 rows starting from 1441792

我們可以在上面的跟蹤日志中看到,1083個現(xiàn)有標(biāo)記中有一個滿足查詢。

Mark 176 was identified (the 'found left boundary mark' is inclusive, the 'found right boundary mark' is exclusive), and therefore all 8192 rows from granule 176 (which starts at row 1.441.792 - we will see that later on in this article) are then streamed into ClickHouse in order to find the actual rows with a UserID column value of 749927693.

我們也可以通過使用EXPLAIN來重現(xiàn)這個結(jié)果:

EXPLAIN indexes = 1
SELECT URL, count(URL) AS Count
FROM hits_UserID_URL
WHERE UserID = 749927693
GROUP BY URL
ORDER BY Count DESC
LIMIT 10;

結(jié)果如下:

┌─explain───────────────────────────────────────────────────────────────────────────────┐
│ Expression (Projection)                                                               │
│   Limit (preliminary LIMIT (without OFFSET))                                          │
│     Sorting (Sorting for ORDER BY)                                                    │
│       Expression (Before ORDER BY)                                                    │
│         Aggregating                                                                   │
│           Expression (Before GROUP BY)                                                │
│             Filter (WHERE)                                                            │
│               SettingQuotaAndLimits (Set limits and quota after reading from storage) │
│                 ReadFromMergeTree                                                     │
│                 Indexes:                                                              │
│                   PrimaryKey                                                          │
│                     Keys:                                                             │
│                       UserID                                                          │
│                     Condition: (UserID in [749927693, 749927693])                     │
│                     Parts: 1/1                                                        │
│                     Granules: 1/1083                                                  │
└───────────────────────────────────────────────────────────────────────────────────────┘

16 rows in set. Elapsed: 0.003 sec.

客戶端輸出顯示,在1083個顆粒中選擇了一個可能包含UserID列值為749927693的行。

CONCLUSION

當(dāng)查詢對聯(lián)合主鍵的一部分并且是第一個主鍵進(jìn)行過濾時,ClickHouse將主鍵索引標(biāo)記運(yùn)行二分查找算法。

正如上面所討論的,ClickHouse使用它的稀疏主索引來快速(通過二分查找算法)選擇可能包含匹配查詢的行的顆粒。

這是ClickHouse查詢執(zhí)行的第一階段(顆粒選擇)。

第二階段(數(shù)據(jù)讀取中), ClickHouse定位所選的顆粒,以便將它們的所有行流到ClickHouse引擎中,以便找到實(shí)際匹配查詢的行。

我們將在下一節(jié)更詳細(xì)地討論第二階段。

標(biāo)記文件用來定位顆粒

下圖描述了上表主索引文件的一部分。

ClickHouse主鍵索引最佳實(shí)踐

如上所述,通過對索引的1083個UserID標(biāo)記進(jìn)行二分搜索,確定了第176個標(biāo)記。因此,它對應(yīng)的顆粒176可能包含UserID列值為749.927.693的行。

顆粒選擇的具體過程
上圖顯示,標(biāo)記176是第一個UserID值小于749.927.693的索引條目,并且下一個標(biāo)記(標(biāo)記177)的顆粒177的最小UserID值大于該值的索引條目。因此,只有標(biāo)記176對應(yīng)的顆粒176可能包含UserID列值為749.927.693的行。

為了確認(rèn)(或排除)顆粒176中的某些行包含UserID列值為749.927.693,需要將屬于此顆粒的所有8192行讀取到ClickHouse。

為了讀取這部分?jǐn)?shù)據(jù),ClickHouse需要知道顆粒176的物理地址。

在ClickHouse中,我們表的所有顆粒的物理位置都存儲在標(biāo)記文件中。與數(shù)據(jù)文件類似,每個表的列有一個標(biāo)記文件。

下圖顯示了三個標(biāo)記文件UserID.mrk、URL.mrk、EventTime.mrk,為表的UserID、URL和EventTime列存儲顆粒的物理位置。

ClickHouse主鍵索引最佳實(shí)踐

我們已經(jīng)討論了主索引是一個扁平的未壓縮數(shù)組文件(primary.idx),其中包含從0開始編號的索引標(biāo)記。

類似地,標(biāo)記文件也是一個扁平的未壓縮數(shù)組文件(*.mrk),其中包含從0開始編號的標(biāo)記。

一旦ClickHouse確定并選擇了可能包含查詢所需的匹配行的顆粒的索引標(biāo)記,就可以在標(biāo)記文件數(shù)組中查找,以獲得顆粒的物理位置。

每個特定列的標(biāo)記文件條目以偏移量的形式存儲兩個位置:

  • 第一個偏移量(上圖中的'block_offset')是在包含所選顆粒的壓縮版本的壓縮列數(shù)據(jù)文件中定位塊。這個壓縮塊可能包含幾個壓縮的顆粒。所定位的壓縮文件塊在讀取時被解壓到內(nèi)存中。
  • 標(biāo)記文件的第二個偏移量(上圖中的“granule_offset”)提供了顆粒在解壓數(shù)據(jù)塊中的位置。

定位到的顆粒中的所有8192行數(shù)據(jù)都會被ClickHouse加載然后進(jìn)一步處理。

為什么需要MARK文件

為什么主索引不直接包含與索引標(biāo)記相對應(yīng)的顆粒的物理位置?

因?yàn)镃lickHouse設(shè)計(jì)的場景就是超大規(guī)模數(shù)據(jù),非常高效地使用磁盤和內(nèi)存非常重要。

主索引文件需要放入內(nèi)存中。

對于我們的示例查詢,ClickHouse使用了主索引,并選擇了可能包含與查詢匹配的行的單個顆粒。只有對于這一個顆粒,ClickHouse才需定位物理位置,以便將相應(yīng)的行組讀取以進(jìn)一步的處理。

而且,只有UserID和URL列需要這個偏移量信息。

對于查詢中不使用的列,例如EventTime,不需要偏移量信息。

對于我們的示例查詢,Clickhouse只需要UserID數(shù)據(jù)文件(UserID.bin)中176顆粒的兩個物理位置偏移,以及URL數(shù)據(jù)文件(URL.data)中176顆粒的兩個物理位置偏移。

由mark文件提供的間接方法避免了直接在主索引中存儲所有三個列的所有1083個顆粒的物理位置的條目:因此避免了在主內(nèi)存中有不必要的(可能未使用的)數(shù)據(jù)。

下面的圖表和文本說明了我們的查詢示例,ClickHouse如何在UserID.bin數(shù)據(jù)文件中定位176顆粒。

ClickHouse主鍵索引最佳實(shí)踐

我們在本文前面討論過,ClickHouse選擇了主索引標(biāo)記176,因此176顆??赡馨樵兯璧钠ヅ湫小?/p>

ClickHouse現(xiàn)在使用從索引中選擇的標(biāo)記號(176)在UserID.mark中進(jìn)行位置數(shù)組查找,以獲得兩個偏移量,用于定位顆粒176。

如圖所示,第一個偏移量是定位UserID.bin數(shù)據(jù)文件中的壓縮文件塊,該數(shù)據(jù)文件包含顆粒176的壓縮數(shù)據(jù)。

一旦所定位的文件塊被解壓縮到主內(nèi)存中,就可以使用標(biāo)記文件的第二個偏移量在未壓縮的數(shù)據(jù)中定位顆粒176。

ClickHouse需要從UserID.bin數(shù)據(jù)文件和URL.bin數(shù)據(jù)文件中定位(讀取)顆粒176,以便執(zhí)行我們的示例查詢(UserID為749.927.693的互聯(lián)網(wǎng)用戶點(diǎn)擊次數(shù)最多的10個url)。

上圖顯示了ClickHouse如何定位UserID.bin數(shù)據(jù)文件的顆粒。

同時,ClickHouse對URL.bin數(shù)據(jù)文件的顆粒176執(zhí)行相同的操作。這兩個不同的顆粒被對齊并加載到ClickHouse引擎以進(jìn)行進(jìn)一步的處理,即聚合并計(jì)算UserID為749.927.693的所有行的每組URL值,最后以計(jì)數(shù)降序輸出10個最大的URL組。

查詢使用第二位主鍵的性能問題

當(dāng)查詢對復(fù)合鍵的一部分并且是第一個主鍵列進(jìn)行過濾時,ClickHouse將對主鍵列的索引標(biāo)記運(yùn)行二分查找。

但是,當(dāng)查詢對聯(lián)合主鍵的一部分但不是第一個鍵列進(jìn)行過濾時,會發(fā)生什么情況?

NOTE

我們討論了這樣一種場景:查詢不是顯式地對第一個主鍵列進(jìn)行過濾,而是對第一個主鍵列之后的任何鍵列進(jìn)行過濾。

當(dāng)查詢同時對第一個主鍵列和第一個主鍵列之后的任何鍵列進(jìn)行過濾時,ClickHouse將對第一個主鍵列的索引標(biāo)記運(yùn)行二分查找。

我們使用一個查詢來計(jì)算最點(diǎn)擊"http://public_search"的最多的前10名用戶:

SELECT UserID, count(UserID) AS Count
FROM hits_UserID_URL
WHERE URL = 'http://public_search'
GROUP BY UserID
ORDER BY Count DESC
LIMIT 10;

結(jié)果是:

┌─────UserID─┬─Count─┐
│ 2459550954 │  3741 │
│ 1084649151 │  2484 │
│  723361875 │   729 │
│ 3087145896 │   695 │
│ 2754931092 │   672 │
│ 1509037307 │   582 │
│ 3085460200 │   573 │
│ 2454360090 │   556 │
│ 3884990840 │   539 │
│  765730816 │   536 │
└────────────┴───────┘

10 rows in set. Elapsed: 0.086 sec.
Processed 8.81 million rows,
799.69 MB (102.11 million rows/s., 9.27 GB/s.)

客戶端輸出表明,盡管URL列是聯(lián)合主鍵的一部分,ClickHouse幾乎執(zhí)行了一一次全表掃描!ClickHouse從表的887萬行中讀取881萬行。

如果啟用了trace日志,那么ClickHouse服務(wù)日志文件顯示,ClickHouse在1083個URL索引標(biāo)記上使用了通用的排除搜索,以便識別那些可能包含URL列值為"http://public_search"的行。

...Executor): Key condition: (column 1 in ['http://public_search',
                                           'http://public_search'])
...Executor): Used generic exclusion search over index for part all_1_9_2
              with 1537 steps
...Executor): Selected 1/1 parts by partition key, 1 parts by primary key,
              1076/1083 marks by primary key, 1076 marks to read from 5 ranges
...Executor): Reading approx. 8814592 rows with 10 streams

我們可以在上面的跟蹤日志示例中看到,1083個顆粒中有1076個(通過標(biāo)記)被選中,因?yàn)榭赡馨哂衅ヅ銾RL值的行。

這將導(dǎo)致881萬行被讀取到ClickHouse引擎中(通過使用10個流并行地讀取),以便識別實(shí)際包含URL值"http://public_search"的行。

然而,稍后僅僅39個顆粒包含匹配的行。

雖然基于聯(lián)合主鍵(UserID, URL)的主索引對于加快過濾具有特定UserID值的行的查詢非常有用,但對于過濾具有特定URL值的行的查詢,索引并沒有提供顯著的幫助。

原因是URL列不是第一個主鍵列,因此ClickHouse是使用一個通用的排除搜索算法(而不是二分查找)查找URL列的索引標(biāo)志,和UserID主鍵列不同,它的算法的有效性依賴于URL列的基數(shù)。

為了說明,我們給出通用的排除搜索算法的工作原理:

通用排除搜索算法

下面將演示當(dāng)通過第一個列之后的任何列選擇顆粒時,當(dāng)前一個鍵列具有或高或低的基數(shù)時,ClickHouse通用排除搜索算法 是如何工作的。

作為這兩種情況的例子,我們將假設(shè):

  • 搜索URL值為"W3"的行。
  • 點(diǎn)擊表抽象簡化為只有簡單值的UserID和UserID。
  • 相同聯(lián)合主鍵(UserID、URL)。這意味著行首先按UserID值排序,具有相同UserID值的行然后再按URL排序。
  • 顆粒大小為2,即每個顆粒包含兩行。

在下面的圖表中,我們用橙色標(biāo)注了每個顆粒的最小鍵列值。

前綴主鍵低基數(shù)

假設(shè)UserID具有較低的基數(shù)。在這種情況下,相同的UserID值很可能分布在多個表行和顆粒上,從而分布在索引標(biāo)記上。對于具有相同UserID的索引標(biāo)記,索引標(biāo)記的URL值按升序排序(因?yàn)楸硇惺紫劝碪serID排序,然后按URL排序)。這使得有效的過濾如下所述:

ClickHouse主鍵索引最佳實(shí)踐

在上圖中,我們的抽象樣本數(shù)據(jù)的顆粒選擇過程有三種不同的場景:

  1. 如果索引標(biāo)記0的(最小)URL值小于W3,并且緊接索引標(biāo)記的URL值也小于W3,則可以排除索引標(biāo)記0,因?yàn)闃?biāo)記0、標(biāo)記1和標(biāo)記2具有相同的UserID值。注意,這個排除前提條件確保顆粒0和下一個顆粒1完全由U1 UserID值組成,這樣ClickHouse就可以假設(shè)顆粒0中的最大URL值也小于W3并排除該顆粒。
  2. 如果索引標(biāo)記1的URL值小于(或等于)W3,并且后續(xù)索引標(biāo)記的URL值大于(或等于)W3,則選擇索引標(biāo)記1,因?yàn)檫@意味著粒度1可能包含URL為W3的行)。
  3. 可以排除URL值大于W3的索引標(biāo)記2和3,因?yàn)橹魉饕乃饕龢?biāo)記存儲了每個顆粒的最小鍵列值,因此顆粒2和3不可能包含URL值W3。

前綴主鍵高基數(shù)

當(dāng)UserID具有較高的基數(shù)時,相同的UserID值不太可能分布在多個表行和顆粒上。這意味著索引標(biāo)記的URL值不是單調(diào)遞增的:

ClickHouse主鍵索引最佳實(shí)踐

正如在上面的圖表中所看到的,所有URL值小于W3的標(biāo)記都被選中,以便將其關(guān)聯(lián)的顆粒的行加載到ClickHouse引擎中。

這是因?yàn)殡m然圖中的所有索引標(biāo)記都屬于上面描述的場景1,但它們不滿足前面提到的排除前提條件,即兩個直接隨后的索引標(biāo)記都具有與當(dāng)前標(biāo)記相同的UserID值,因此不能被排除。

例如,考慮索引標(biāo)記0,其URL值小于W3,并且其直接后續(xù)索引標(biāo)記的URL值也小于W3。這不能排除,因?yàn)閮蓚€直接隨后的索引標(biāo)記1和2與當(dāng)前標(biāo)記0沒有相同的UserID值。

請注意,隨后的兩個索引標(biāo)記需要具有相同的UserID值。這確保了當(dāng)前和下一個標(biāo)記的顆粒完全由U1 UserID值組成。如果僅僅是下一個標(biāo)記具有相同的UserID,那么下一個標(biāo)記的URL值可能來自具有不同UserID的表行——當(dāng)您查看上面的圖表時,確實(shí)是這樣的情況,即W2來自U2而不是U1的行。

這最終阻止了ClickHouse對顆粒0中的最大URL值進(jìn)行假設(shè)。相反,它必須假設(shè)顆粒0可能包含URL值為W3的行,并被迫選擇標(biāo)記0。

同樣的情況也適用于標(biāo)記1、2和3。

結(jié)論

當(dāng)查詢對聯(lián)合主鍵的一部分列(但不是第一個鍵列)進(jìn)行過濾時,ClickHouse使用的通用排除搜索算法(而不是二分查找)在前一個鍵列基數(shù)較低時最有效。

在我們的示例數(shù)據(jù)集中,兩個鍵列(UserID、URL)都具有類似的高基數(shù),并且,如前所述,當(dāng)URL列的前一個鍵列具有較高基數(shù)時,通用排除搜索算法不是很有效。

看下跳數(shù)索引

因?yàn)閁serID和URL具有較高的基數(shù),根據(jù)URL過濾數(shù)據(jù)不是特別有效,對URL列創(chuàng)建二級跳數(shù)索引同樣也不會有太多改善。

例如,這兩個語句在我們的表的URL列上創(chuàng)建并填充一個minmax跳數(shù)索引。

ALTER TABLE hits_UserID_URL ADD INDEX url_skipping_index URL TYPE minmax GRANULARITY 4;
ALTER TABLE hits_UserID_URL MATERIALIZE INDEX url_skipping_index;

ClickHouse現(xiàn)在創(chuàng)建了一個額外的索引來存儲—每組4個連續(xù)的顆粒(注意上面ALTER TABLE語句中的GRANULARITY 4子句)—最小和最大的URL值:

ClickHouse主鍵索引最佳實(shí)踐

第一個索引條目(上圖中的mark 0)存儲屬于表的前4個顆粒的行的最小和最大URL值。

第二個索引條目(mark 1)存儲屬于表中下一個4個顆粒的行的最小和最大URL值,依此類推。

(ClickHouse還為跳數(shù)索引創(chuàng)建了一個特殊的標(biāo)記文件,用于定位與索引標(biāo)記相關(guān)聯(lián)的顆粒組。)

由于UserID和URL的基數(shù)相似,在執(zhí)行對URL的查詢過濾時,這個二級跳數(shù)索引不能幫助排除選擇的顆粒。

正在尋找的特定URL值('http://public_search')很可能是索引為每組顆粒存儲的最小值和最大值之間的值,導(dǎo)致ClickHouse被迫選擇這組顆粒(因?yàn)樗鼈兛赡馨ヅ洳樵兊男?。

因此,如果我們想顯著提高過濾具有特定URL的行的示例查詢的速度,那么我們需要使用針對該查詢優(yōu)化的主索引。

此外,如果我們想保持過濾具有特定UserID的行的示例查詢的良好性能,那么我們需要使用多個主索引。

下面是實(shí)現(xiàn)這一目標(biāo)的方法。

使用多個主鍵索引進(jìn)行調(diào)優(yōu)

如果我們想顯著加快我們的兩個示例查詢——一個過濾具有特定UserID的行,一個過濾具有特定URL的行——那么我們需要使用多個主索引,通過使用這三個方法中的一個:

  • 新建一個不同主鍵的新表。
  • 創(chuàng)建一個物化視圖。
  • 增加projection。

這三個方法都會有效地將示例數(shù)據(jù)復(fù)制到另一個表中,以便重新組織表的主索引和行排序順序。

然而,這三個選項(xiàng)的不同之處在于,附加表對于查詢和插入語句的路由對用戶的透明程度。

當(dāng)創(chuàng)建有不同主鍵的第二個表時,查詢必須顯式地發(fā)送給最適合查詢的表版本,并且必須顯式地插入新數(shù)據(jù)到兩個表中,以保持表的同步:

ClickHouse主鍵索引最佳實(shí)踐

在物化視圖中,額外的表被隱藏,數(shù)據(jù)自動在兩個表之間保持同步:

ClickHouse主鍵索引最佳實(shí)踐

projection方式是最透明的選項(xiàng),因?yàn)槌俗詣颖3蛛[藏的附加表與數(shù)據(jù)變化同步外,ClickHouse還會自動選擇最有效的表版本進(jìn)行查詢:

ClickHouse主鍵索引最佳實(shí)踐

下面我們使用真實(shí)的例子詳細(xì)討論下這三種方式。

通過輔助表使用聯(lián)合主鍵索引

我們創(chuàng)建一個新的附加表,其中我們在主鍵中切換鍵列的順序(與原始表相比):

CREATE TABLE hits_URL_UserID
(
    `UserID` UInt32,
    `URL` String,
    `EventTime` DateTime
)
ENGINE = MergeTree
PRIMARY KEY (URL, UserID)
ORDER BY (URL, UserID, EventTime)
SETTINGS index_granularity = 8192, index_granularity_bytes = 0;

寫入887萬行源表數(shù)據(jù):

INSERT INTO hits_URL_UserID
SELECT * from hits_UserID_URL;

結(jié)果:

Ok.

0 rows in set. Elapsed: 2.898 sec. Processed 8.87 million rows, 838.84 MB (3.06 million rows/s., 289.46 MB/s.)

最后optimize下:

OPTIMIZE TABLE hits_URL_UserID FINAL;

因?yàn)槲覀兦袚Q了主鍵中列的順序,插入的行現(xiàn)在以不同的字典順序存儲在磁盤上(與我們的原始表相比),因此該表的1083個顆粒也包含了與以前不同的值:

ClickHouse主鍵索引最佳實(shí)踐

主鍵索引如下:

ClickHouse主鍵索引最佳實(shí)踐

現(xiàn)在計(jì)算最頻繁點(diǎn)擊URL"http://public_search"的前10名用戶,這時候的查詢速度是明顯加快的:

SELECT UserID, count(UserID) AS Count
FROM hits_URL_UserID
WHERE URL = 'http://public_search'
GROUP BY UserID
ORDER BY Count DESC
LIMIT 10;

結(jié)果:

┌─────UserID─┬─Count─┐
│ 2459550954 │  3741 │
│ 1084649151 │  2484 │
│  723361875 │   729 │
│ 3087145896 │   695 │
│ 2754931092 │   672 │
│ 1509037307 │   582 │
│ 3085460200 │   573 │
│ 2454360090 │   556 │
│ 3884990840 │   539 │
│  765730816 │   536 │
└────────────┴───────┘

10 rows in set. Elapsed: 0.017 sec.
Processed 319.49 thousand rows,
11.38 MB (18.41 million rows/s., 655.75 MB/s.)

現(xiàn)在沒有全表掃描了,ClickHouse執(zhí)行高效了很多。

對于原始表中的主索引(其中UserID是第一個鍵列,URL是第二個鍵列),ClickHouse在索引標(biāo)記上使用了通用排除搜索來執(zhí)行該查詢,但這不是很有效,因?yàn)閁serID和URL的基數(shù)同樣很高。

將URL作為主索引的第一列,ClickHouse現(xiàn)在對索引標(biāo)記運(yùn)行二分搜索。ClickHouse服務(wù)器日志文件中對應(yīng)的跟蹤日志:

...Executor): Key condition: (column 0 in ['http://public_search',
                                           'http://public_search'])
...Executor): Running binary search on index range for part all_1_9_2 (1083 marks)
...Executor): Found (LEFT) boundary mark: 644
...Executor): Found (RIGHT) boundary mark: 683
...Executor): Found continuous range in 19 steps
...Executor): Selected 1/1 parts by partition key, 1 parts by primary key,
              39/1083 marks by primary key, 39 marks to read from 1 ranges
...Executor): Reading approx. 319488 rows with 2 streams

ClickHouse只選擇了39個索引標(biāo)記,而不是使用通用排除搜索時的1076個。

請注意,輔助表經(jīng)過了優(yōu)化,以加快對url的示例查詢過濾的執(zhí)行。

像之前我們查詢過濾URL一樣,如果我們現(xiàn)在對輔助表查詢過濾UserID,性能同樣會比較差,因?yàn)楝F(xiàn)在UserID是第二主索引鍵列,所以ClickHouse將使用通用排除搜索算法查找顆粒,這對于類似高基數(shù)的UserID和URL來說不是很有效。

點(diǎn)擊下面了解詳情:

SELECT URL, count(URL) AS Count
FROM hits_URL_UserID
WHERE UserID = 749927693
GROUP BY URL
ORDER BY Count DESC
LIMIT 10;

結(jié)果

┌─URL────────────────────────────┬─Count─┐
│ http://auto.ru/chatay-barana.. │   170 │
│ http://auto.ru/chatay-id=371...│    52 │
│ http://public_search           │    45 │
│ http://kovrik-medvedevushku-...│    36 │
│ http://forumal                 │    33 │
│ http://korablitz.ru/L_1OFFER...│    14 │
│ http://auto.ru/chatay-id=371...│    14 │
│ http://auto.ru/chatay-john-D...│    13 │
│ http://auto.ru/chatay-john-D...│    10 │
│ http://wot/html?page/23600_m...│     9 │
└────────────────────────────────┴───────┘

10 rows in set. Elapsed: 0.024 sec.
Processed 8.02 million rows,
73.04 MB (340.26 million rows/s., 3.10 GB/s.)

服務(wù)端日志:
...Executor): Key condition: (column 1 in [749927693, 749927693])
...Executor): Used generic exclusion search over index for part all_1_9_2
              with 1453 steps
...Executor): Selected 1/1 parts by partition key, 1 parts by primary key,
              980/1083 marks by primary key, 980 marks to read from 23 ranges
...Executor): Reading approx. 8028160 rows with 10 streams

現(xiàn)在我們有了兩張表。優(yōu)化了對UserID和URL的查詢過濾,分別:

ClickHouse主鍵索引最佳實(shí)踐

通過物化視圖使用聯(lián)合主鍵

在原表上創(chuàng)建物化視圖:

CREATE MATERIALIZED VIEW mv_hits_URL_UserID
ENGINE = MergeTree()
PRIMARY KEY (URL, UserID)
ORDER BY (URL, UserID, EventTime)
POPULATE
AS SELECT * FROM hits_UserID_URL;

結(jié)果:

Ok.

0 rows in set. Elapsed: 2.935 sec. Processed 8.87 million rows, 838.84 MB (3.02 million rows/s., 285.84 MB/s.)

NOTE

  • 我們在視圖的主鍵中切換鍵列的順序(與原始表相比)
  • 物化視圖由一個隱藏表支持,該表的行順序和主索引基于給定的主鍵定義
  • 我們使用POPULATE關(guān)鍵字,以便用源表hits_UserID_URL中的所有887萬行立即導(dǎo)入新的物化視圖
  • 如果在源表hits_UserID_URL中插入了新行,那么這些行也會自動插入到隱藏表中
  • 實(shí)際上,隱式創(chuàng)建的隱藏表的行順序和主索引與我們上面顯式創(chuàng)建的輔助表相同:

ClickHouse主鍵索引最佳實(shí)踐

ClickHouse將隱藏表的列數(shù)據(jù)文件(.bin)、標(biāo)記文件(.mrk2)和主索引(primary.idx)存儲在ClickHouse服務(wù)器的數(shù)據(jù)目錄的一個特殊文件夾中:

ClickHouse主鍵索引最佳實(shí)踐

物化視圖背后的隱藏表(和它的主索引)現(xiàn)在可以用來顯著加快我們在URL列上查詢過濾的執(zhí)行速度:

SELECT UserID, count(UserID) AS Count
FROM mv_hits_URL_UserID
WHERE URL = 'http://public_search'
GROUP BY UserID
ORDER BY Count DESC
LIMIT 10;

結(jié)果:

┌─────UserID─┬─Count─┐
│ 2459550954 │  3741 │
│ 1084649151 │  2484 │
│  723361875 │   729 │
│ 3087145896 │   695 │
│ 2754931092 │   672 │
│ 1509037307 │   582 │
│ 3085460200 │   573 │
│ 2454360090 │   556 │
│ 3884990840 │   539 │
│  765730816 │   536 │
└────────────┴───────┘

10 rows in set. Elapsed: 0.026 sec.
Processed 335.87 thousand rows,
13.54 MB (12.91 million rows/s., 520.38 MB/s.)

物化視圖背后隱藏表(及其主索引)實(shí)際上與我們顯式創(chuàng)建的輔助表是相同的,所以查詢的執(zhí)行方式與顯式創(chuàng)建的表相同。

ClickHouse服務(wù)器日志文件中相應(yīng)的跟蹤日志確認(rèn)了ClickHouse正在對索引標(biāo)記運(yùn)行二分搜索:

...Executor): Key condition: (column 0 in ['http://public_search',
                                           'http://public_search'])
...Executor): Running binary search on index range ...
...
...Executor): Selected 4/4 parts by partition key, 4 parts by primary key,
              41/1083 marks by primary key, 41 marks to read from 4 ranges
...Executor): Reading approx. 335872 rows with 4 streams

通過projections使用聯(lián)合主鍵索引

Projections目前是一個實(shí)驗(yàn)性的功能,因此我們需要告訴ClickHouse:

SET allow_experimental_projection_optimization = 1;

在原表上創(chuàng)建projection:

ALTER TABLE hits_UserID_URL
    ADD PROJECTION prj_url_userid
    (
        SELECT *
        ORDER BY (URL, UserID)
    );

物化projection:

ALTER TABLE hits_UserID_URL
    MATERIALIZE PROJECTION prj_url_userid;

NOTE

  • 該projection正在創(chuàng)建一個隱藏表,該表的行順序和主索引基于該projection的給定order BY子句
  • 我們使用MATERIALIZE關(guān)鍵字,以便立即用源表hits_UserID_URL的所有887萬行導(dǎo)入隱藏表
  • 如果在源表hits_UserID_URL中插入了新行,那么這些行也會自動插入到隱藏表中
  • 查詢總是(從語法上)針對源表hits_UserID_URL,但是如果隱藏表的行順序和主索引允許更有效地執(zhí)行查詢,那么將使用該隱藏表
  • 實(shí)際上,隱式創(chuàng)建的隱藏表的行順序和主索引與我們顯式創(chuàng)建的輔助表相同:

ClickHouse主鍵索引最佳實(shí)踐

ClickHouse將隱藏表的列數(shù)據(jù)文件(.bin)、標(biāo)記文件(.mrk2)和主索引(primary.idx)存儲在一個特殊的文件夾中(在下面的截圖中用橙色標(biāo)記),緊挨著源表的數(shù)據(jù)文件、標(biāo)記文件和主索引文件:

ClickHouse主鍵索引最佳實(shí)踐

由投影創(chuàng)建的隱藏表(以及它的主索引)現(xiàn)在可以(隱式地)用于顯著加快URL列上查詢過濾的執(zhí)行。注意,查詢在語法上針對投影的源表。

SELECT UserID, count(UserID) AS Count
FROM hits_UserID_URL
WHERE URL = 'http://public_search'
GROUP BY UserID
ORDER BY Count DESC
LIMIT 10;

結(jié)果:

┌─────UserID─┬─Count─┐
│ 2459550954 │  3741 │
│ 1084649151 │  2484 │
│  723361875 │   729 │
│ 3087145896 │   695 │
│ 2754931092 │   672 │
│ 1509037307 │   582 │
│ 3085460200 │   573 │
│ 2454360090 │   556 │
│ 3884990840 │   539 │
│  765730816 │   536 │
└────────────┴───────┘

10 rows in set. Elapsed: 0.029 sec.
Processed 319.49 thousand rows, 1
1.38 MB (11.05 million rows/s., 393.58 MB/s.)

因?yàn)橛赏队皠?chuàng)建的隱藏表(及其主索引)實(shí)際上與我們顯式創(chuàng)建的輔助表相同,所以查詢的執(zhí)行方式與顯式創(chuàng)建的表相同。

ClickHouse服務(wù)器日志文件中跟蹤日志確認(rèn)了ClickHouse正在對索引標(biāo)記運(yùn)行二分搜索:

...Executor): Key condition: (column 0 in ['http://public_search',
                                           'http://public_search'])
...Executor): Running binary search on index range for part prj_url_userid (1083 marks)
...Executor): ...
...Executor): Choose complete Normal projection prj_url_userid
...Executor): projection required columns: URL, UserID
...Executor): Selected 1/1 parts by partition key, 1 parts by primary key,
              39/1083 marks by primary key, 39 marks to read from 1 ranges
...Executor): Reading approx. 319488 rows with 2 streams

移除無效的主鍵列

帶有聯(lián)合主鍵(UserID, URL)的表的主索引對于加快UserID的查詢過濾非常有用。但是,盡管URL列是聯(lián)合主鍵的一部分,但該索引在加速URL查詢過濾方面并沒有提供顯著的幫助。

反之亦然:具有復(fù)合主鍵(URL, UserID)的表的主索引加快了URL上的查詢過濾,但沒有為UserID上的查詢過濾提供太多支持。

由于主鍵列UserID和URL的基數(shù)同樣很高,過濾第二個鍵列的查詢不會因?yàn)榈诙€鍵列位于索引中而受益太多。

因此,從主索引中刪除第二個鍵列(從而減少索引的內(nèi)存消耗)并使用多個主索引是有意義的。

但是,如果復(fù)合主鍵中的鍵列在基數(shù)上有很大的差異,那么查詢按基數(shù)升序?qū)χ麈I列進(jìn)行排序是有益的。

主鍵鍵列之間的基數(shù)差越大,主鍵鍵列的順序越重要。我們將在以后的文章中對此進(jìn)行演示。請繼續(xù)關(guān)注。文章來源地址http://www.zghlxwxcb.cn/news/detail-427302.html

到了這里,關(guān)于ClickHouse主鍵索引最佳實(shí)踐的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!

本文來自互聯(lián)網(wǎng)用戶投稿,該文觀點(diǎn)僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務(wù),不擁有所有權(quán),不承擔(dān)相關(guān)法律責(zé)任。如若轉(zhuǎn)載,請注明出處: 如若內(nèi)容造成侵權(quán)/違法違規(guī)/事實(shí)不符,請點(diǎn)擊違法舉報進(jìn)行投訴反饋,一經(jīng)查實(shí),立即刪除!

領(lǐng)支付寶紅包贊助服務(wù)器費(fèi)用

相關(guān)文章

  • Mysql 索引(三)—— 不同索引的創(chuàng)建方式(主鍵索引、普通索引、唯一鍵索引)

    Mysql 索引(三)—— 不同索引的創(chuàng)建方式(主鍵索引、普通索引、唯一鍵索引)

    了解了主鍵索引的底層原理,主鍵索引其實(shí)就是 根據(jù)主鍵字段建立相關(guān)的數(shù)據(jù)結(jié)構(gòu) (B+樹),此后在 使用主鍵字段作為條件查詢時,會直接根據(jù)主鍵查找B+樹的葉子結(jié)點(diǎn)。 除了 主鍵索引外,普通索引和唯一鍵索引也是如此,只不過普通索引要稍微繞一點(diǎn),下面會具體介紹。

    2024年02月03日
    瀏覽(19)
  • 【面試】Mysql主鍵索引普通索引索引和唯一索引的區(qū)別是什么?

    【面試】Mysql主鍵索引普通索引索引和唯一索引的區(qū)別是什么?

    在 MySQL 中, 索引是在存儲引擎層實(shí)現(xiàn)的, 所以并沒有統(tǒng)?的索引標(biāo)準(zhǔn), 由于 InnoDB 存儲引擎在 MySQL數(shù)據(jù)庫中使?最為?泛, 下?以 InnoDB 為例來分析?下其中的索引模型.在 InnoDB 中, 表都是根據(jù)主鍵順序以索引的形式存放的, InnoDB 使?了 B+ 樹索引模型,所以數(shù)據(jù)都是存儲在 B+ 樹

    2023年04月17日
    瀏覽(25)
  • mysql中主鍵索引和聯(lián)合索引的原理解析

    mysql中主鍵索引和聯(lián)合索引的原理解析

    主鍵索引 :按照主鍵數(shù)據(jù)從小到大按照從左到右進(jìn)行排序,葉節(jié)點(diǎn)只存儲數(shù)據(jù)區(qū); 接著將上面的頁生成出來,頁只存儲索引和指針,指針指向數(shù)據(jù)域,當(dāng)通過主鍵查找數(shù)據(jù)時,從B+樹的頭部開始尋址數(shù)據(jù)、讀取數(shù)據(jù)。 上面為索引頁 下面為數(shù)據(jù)頁 查詢select * from table where a=6,

    2024年04月08日
    瀏覽(36)
  • 淺談mysql的主鍵和索引

    淺談mysql的主鍵和索引

    在上一篇文章《count(1)、count(*)、count(字段)哪個更靠譜》中,我們提到過主鍵是優(yōu)化不了count的查詢效率的,需要建索引才可以,那么,是不是意味著主鍵的效率還不如一般的索引呢?懷著這個疑問,我們一起來了解下mysql主鍵和索引的相關(guān)知識。 mysql數(shù)據(jù)庫的MYISAM和InnoDB引擎

    2024年02月08日
    瀏覽(33)
  • 【Mysql】復(fù)合主鍵的索引

    【Mysql】復(fù)合主鍵的索引

    復(fù)合主鍵在where中使用查詢的時候到底走不走索引呢?例如下表: 當(dāng)執(zhí)行以下SQL的時候到底走不走索引呢? ? ? ? ? ? ? Explain結(jié)果: 使用索引 ? 使用索引 ? 使用索引 ? 不使用索引 ? 不使用索引 ? 使用索引 ? 結(jié)論:Mysql復(fù)合主鍵的順序十分重要,WHERE查詢條件中會按列匹配

    2023年04月25日
    瀏覽(22)
  • 【后端面經(jīng)】MySQL主鍵、唯一索引、聯(lián)合索引的區(qū)別和作用

    目錄 0. 簡介 1. 主鍵 2. 唯一索引 3. 聯(lián)合索引 4. 索引對數(shù)據(jù)庫操作的影響 5. 其他索引 5.1 普通索引 5.2 全文索引 5.3 前綴索引 6. 總結(jié) 7. 參考資料 索引是一類特殊的 文件 ,用來存儲檢索信息,使數(shù)據(jù)庫查找更加快速。 主鍵是一類特殊的唯一索引,選擇某一列元素作為主鍵,用

    2024年02月09日
    瀏覽(34)
  • mysql索引--普通索引,唯一索引,主鍵索引,參照完整性約束,數(shù)據(jù)完整性約束

    -- 方法1:create index -- 對employee表的員工部門號列創(chuàng)建普通索引depart_ind -- create index depart_ind on employees(員工部門號); -- 對employee表的姓名和地址列創(chuàng)建復(fù)合索引ad_ind; -- create index ad_ind on employees(姓名,地址); -- 對departments表的部門名稱列創(chuàng)建唯一索引un_ind; -- create unique index un_ind

    2023年04月21日
    瀏覽(22)
  • 主鍵、外鍵、建表范式、MySQL索引、用戶管理

    1.1 問題 完成如下練習(xí): 練習(xí)主鍵的創(chuàng)建、查看、刪除、添加、驗(yàn)證主鍵 練習(xí)復(fù)合主鍵的使用 練習(xí)與auto_increment連用的效果 1.2 方案 主鍵使用規(guī)則: 表頭值不允許重復(fù),不允許賦NULL值 一個表中只能有一個primary key 表頭 多個表頭做主鍵,稱為復(fù)合主鍵,必須一起創(chuàng)建和刪除

    2024年01月16日
    瀏覽(26)
  • mysql的主鍵索引為什么不能null

    這是一個非常奇怪且有趣的問題??梢酝ㄟ^官方文檔進(jìn)行解讀 https://dev.mysql.com/doc/refman/5.7/en/glossary.html A special value in SQL, indicating the absence of data. Any arithmetic operation or equality test involving a NULL value, in turn produces a NULL result. (Thus it is similar to the IEEE floating-point concept of NaN, “not

    2024年02月14日
    瀏覽(28)
  • 一文徹底搞清楚MySQL的主鍵、外鍵、約束和各種索引

    一文徹底搞清楚MySQL的主鍵、外鍵、約束和各種索引

    主鍵用于唯一標(biāo)識表中每一行數(shù)據(jù),外鍵用于建立表與表之間關(guān)聯(lián)關(guān)系,約束用于限制表中數(shù)據(jù)的規(guī)則,索引用于加速查詢。 主鍵是一種用于唯一標(biāo)識表中每一行數(shù)據(jù)的標(biāo)識符。在Mysql中,主鍵可以是一個或多個列的組合,但是必須滿足以下條件: 主鍵列的值必須唯一,不能

    2024年02月08日
    瀏覽(18)

覺得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請作者喝杯咖啡吧~博客贊助

支付寶掃一掃領(lǐng)取紅包,優(yōu)惠每天領(lǐng)

二維碼1

領(lǐng)取紅包

二維碼2

領(lǐng)紅包