博客 / 工程

ClickHouse 24.6 版本发布

author avatar
ClickHouse 团队
2024 年 7 月 11 日 - 18 分钟阅读

又一个月过去了,这意味着又到了发布新版本的时候了!

ClickHouse 24.6 版本包含 23 个新功能 🎁 24 项性能优化 🛷 59 个错误修复 🐛

新贡献者

与往常一样,我们特别欢迎 24.6 版本的所有新贡献者!ClickHouse 的受欢迎程度很大程度上归功于社区的贡献。看到社区不断壮大总是令人感到谦卑。

以下是新贡献者的姓名

Artem Mustafin, Danila Puzov, Francesco Ciocchetti, Grigorii Sokolik, HappenLee, Kris Buytaert, Lee sungju, Mikhail Gorshkov, Philipp Schreiber, Pratima Patel, Sariel, TTPO100AJIEX, Tim MacDonald, Xu Jia, ZhiHong Zhang, allegrinisante, anonymous, chloro, haohang, iceFireser, morning-color, pn, sarielwxm, wudidapaopao, xogoodnow

提示:如果您好奇我们如何生成此列表… 点击此处

您还可以查看演示文稿的幻灯片

最佳表排序

由 Igor Markelov 贡献

MergeTree 表中行的物理磁盘顺序 其 ORDER BY 键定义。

提醒一下,ORDER BY 键和相关的物理行顺序有三个目的

  1. 构建用于范围请求的稀疏索引(也可以指定为 PRIMARY KEY)。
  2. 合并模式定义键,例如用于 Aggregating- 或 ReplacingMergeTree 表。
  3. 提供一种通过在列文件中共址数据来提高压缩率的方法。

对于上述第三个目的,此版本引入了一个名为 optimize_row_order 的新设置。此设置专门用于普通 MergeTree 引擎表。在按 ORDER BY 列排序后,它会在数据摄取期间自动按剩余列的基数对表行进行排序,从而确保最佳压缩。

如果数据暴露模式,则通用压缩编解码器(如 LZ4ZSTD)可实现最大压缩率。例如,相同值的长运行通常压缩效果非常好。通过按列值物理存储排序的行(从基数最低的列开始),可以实现这种相同值的长运行。下图说明了这一点

optimal_table_sorting_01.png

由于上图中的行在磁盘上按列值排序(从基数最低的列开始),因此每列都存在相同值的长运行。如上所述,这有利于表部分的列文件的压缩率。

作为反例,下图说明了首先按高基数列排序磁盘上的行的效果

optimal_table_sorting_02.png

由于行首先按高基数列值排序,因此通常不再可能基于其他列的值对行进行排序以创建相同值的长运行。因此,列文件的压缩率是次优的

使用新的 optimize_row_order 设置,ClickHouse 自动实现最佳数据压缩。ClickHouse 将行存储在磁盘上,首先(像往常一样)按 ORDER BY 列排序。此外,对于具有相同 ORDER BY 列值的行范围,行按剩余列的值排序,并按范围本地列基数升序排列

optimal_table_sorting_03.png

此优化仅应用于在插入时创建的数据部分,而不应用于部分合并期间。由于普通 MergeTree 表的大多数合并只是连接 ORDER BY 键的非重叠范围,因此通常会保留已优化的行顺序。

INSERT 预计会花费 30-50% 的时间,具体取决于数据特征。

LZ4ZSTD 的压缩率预计平均提高 20-40%。

此设置最适用于没有 ORDER BY 键或低基数 ORDER BY 键的表,即只有少量不同 ORDER BY 键值的表。具有高基数 ORDER BY 键的表,例如 DateTime64 类型的 timestamp 列,预计不会从此设置中受益。

为了演示这种新的优化,我们将 10 亿行 公共 PyPI 下载统计数据集 加载到没有 optimize_row_order 设置的表和具有该设置的表中。

我们创建没有新设置的表

CREATE OR REPLACE TABLE pypi
(
    `timestamp` DateTime64(6),
    `date` Date MATERIALIZED timestamp,
    `country_code` LowCardinality(String),
    `url` String,
    `project` String,
    `file` Tuple(filename String, project String, version String, type Enum8('bdist_wheel' = 0, 'sdist' = 1, 'bdist_egg' = 2, 'bdist_wininst' = 3, 'bdist_dumb' = 4, 'bdist_msi' = 5, 'bdist_rpm' = 6, 'bdist_dmg' = 7)),
    `installer` Tuple(name LowCardinality(String), version LowCardinality(String)),
    `python` LowCardinality(String),
    `implementation` Tuple(name LowCardinality(String), version LowCardinality(String)),
    `distro` Tuple(name LowCardinality(String), version LowCardinality(String), id LowCardinality(String), libc Tuple(lib Enum8('' = 0, 'glibc' = 1, 'libc' = 2), version LowCardinality(String))),
    `system` Tuple(name LowCardinality(String), release String),
    `cpu` LowCardinality(String),
    `openssl_version` LowCardinality(String),
    `setuptools_version` LowCardinality(String),
    `rustc_version` LowCardinality(String),
    `tls_protocol` Enum8('TLSv1.2' = 0, 'TLSv1.3' = 1),
    `tls_cipher` Enum8('ECDHE-RSA-AES128-GCM-SHA256' = 0, 'ECDHE-RSA-CHACHA20-POLY1305' = 1, 'ECDHE-RSA-AES128-SHA256' = 2, 'TLS_AES_256_GCM_SHA384' = 3, 'AES128-GCM-SHA256' = 4, 'TLS_AES_128_GCM_SHA256' = 5, 'ECDHE-RSA-AES256-GCM-SHA384' = 6, 'AES128-SHA' = 7, 'ECDHE-RSA-AES128-SHA' = 8, 'AES128-GCM' = 9)
)
Engine = MergeTree
ORDER BY (project);

并将数据加载到该表中。请注意,我们增加了 min_insert_block_size_rows 设置的值,以更好地演示优化效果

INSERT INTO pypi
SELECT
    *
FROM s3(
    'https://storage.googleapis.com/clickhouse_public_datasets/pypi/file_downloads/sample/2023/{0..61}-*.parquet')
SETTINGS
    input_format_null_as_default = 1,
    input_format_parquet_import_nested = 1,
    min_insert_block_size_bytes = 0,
    min_insert_block_size_rows = 60_000_000;

接下来,我们使用新设置创建相同的表

CREATE TABLE pypi_opt
(
    `timestamp` DateTime64(6),
    `date` Date MATERIALIZED timestamp,
    `country_code` LowCardinality(String),
    `url` String,
    `project` String,
    `file` Tuple(filename String, project String, version String, type Enum8('bdist_wheel' = 0, 'sdist' = 1, 'bdist_egg' = 2, 'bdist_wininst' = 3, 'bdist_dumb' = 4, 'bdist_msi' = 5, 'bdist_rpm' = 6, 'bdist_dmg' = 7)),
    `installer` Tuple(name LowCardinality(String), version LowCardinality(String)),
    `python` LowCardinality(String),
    `implementation` Tuple(name LowCardinality(String), version LowCardinality(String)),
    `distro` Tuple(name LowCardinality(String), version LowCardinality(String), id LowCardinality(String), libc Tuple(lib Enum8('' = 0, 'glibc' = 1, 'libc' = 2), version LowCardinality(String))),
    `system` Tuple(name LowCardinality(String), release String),
    `cpu` LowCardinality(String),
    `openssl_version` LowCardinality(String),
    `setuptools_version` LowCardinality(String),
    `rustc_version` LowCardinality(String),
    `tls_protocol` Enum8('TLSv1.2' = 0, 'TLSv1.3' = 1),
    `tls_cipher` Enum8('ECDHE-RSA-AES128-GCM-SHA256' = 0, 'ECDHE-RSA-CHACHA20-POLY1305' = 1, 'ECDHE-RSA-AES128-SHA256' = 2, 'TLS_AES_256_GCM_SHA384' = 3, 'AES128-GCM-SHA256' = 4, 'TLS_AES_128_GCM_SHA256' = 5, 'ECDHE-RSA-AES256-GCM-SHA384' = 6, 'AES128-SHA' = 7, 'ECDHE-RSA-AES128-SHA' = 8, 'AES128-GCM' = 9)
)
Engine = MergeTree
ORDER BY (project)
SETTINGS optimize_row_order = 1;

并将数据加载到表中

INSERT INTO pypi_opt
SELECT
    *
FROM s3(
    'https://storage.googleapis.com/clickhouse_public_datasets/pypi/file_downloads/sample/2023/{0..61}-*.parquet')
SETTINGS
    input_format_null_as_default = 1,
    input_format_parquet_import_nested = 1,
    min_insert_block_size_bytes = 0,
    min_insert_block_size_rows = 60_000_000;

让我们比较两个表的存储大小和压缩率

SELECT
    `table`,
    formatReadableQuantity(sum(rows)) AS rows,
    formatReadableSize(sum(data_uncompressed_bytes)) AS uncompressed,
    formatReadableSize(sum(data_compressed_bytes)) AS compressed,
    round(sum(data_uncompressed_bytes) / sum(data_compressed_bytes), 0) AS ratio
FROM system.parts
WHERE active AND (database = 'default') AND startsWith(`table`, 'pypi')
GROUP BY `table`
ORDER BY `table` ASC;


   ┌─table────┬─rows─────────┬─uncompressed─┬─compressed─┬─ratio─┐
1. │ pypi     │ 1.01 billion │ 227.97 GiB   │ 25.36 GiB  │     92. │ pypi_opt │ 1.01 billion │ 227.97 GiB   │ 17.52 GiB  │    13 │
   └──────────┴──────────────┴──────────────┴────────────┴───────┘

如您所见,具有新设置的表的数据压缩率提高了约 30%。

chDB 2.0

由 Auxten Wang 贡献

chDB 是 ClickHouse 的 n 进程内版本,适用于各种语言,最著名的是 Python。它今年早些时候加入了 ClickHouse 家族,并于本周发布了 Python 库的 beta 2.0 版本。

要安装该版本,您必须在安装期间指定版本,如下所示

pip install chdb==2.0.0b1

在此版本中,ClickHouse 引擎升级到 24.5 版本,现在可以直接查询 Pandas DataFrames、Arrow 表和 Python 对象。

让我们首先生成一个包含 1 亿行的 CSV 文件

import pandas as pd
import datetime as dt
import random

rows = 100_000_000
now = dt.date.today()

df = pd.DataFrame({
  "score": [random.randint(0, 1_000_000) for _ in range(0, rows)],
  "result": [random.choice(['win', 'lose', 'draw']) for _ in range(0, rows)],
  "dateOfBirth": [now - dt.timedelta(days = random.randint(5_000, 30_000)) for _ in range(0, rows)]
})

df.to_csv("scores.csv", index=False)

然后,我们可以编写以下代码,将数据加载回 Pandas DataFrame,并使用 chDB 的 Python 表引擎查询 DataFrame

import pandas as pd
import chdb
import time

df = pd.read_csv("scores.csv")

start = time.time()
print(chdb.query("""
SELECT sum(score), avg(score), median(score),
       avgIf(score, dateOfBirth > '1980-01-01') as avgIf,
       countIf(result = 'win') AS wins,
       countIf(result = 'draw') AS draws,
       countIf(result = 'lose') AS losses,
       count()
FROM Python(df)
""", "Vertical"))
end = time.time()
print(f"{end-start} seconds")

输出如下

Row 1:
──────
sum(score):    49998155002154
avg(score):    499981.55002154
median(score): 508259
avgIf:         499938.84709508
wins:          33340305
draws:         33334238
losses:        33325457
count():       100000000

0.4595322608947754 seconds

我们可以将 CSV 文件加载到 PyArrow 表中并查询该表

import pyarrow.csv

table = pyarrow.csv.read_csv("scores.csv")

start = time.time()
print(chdb.query("""
SELECT sum(score), avg(score), median(score),
       avgIf(score, dateOfBirth > '1980-01-01') as avgIf,
       countIf(result = 'win') AS wins,
       countIf(result = 'draw') AS draws,
       countIf(result = 'lose') AS losses,
       count()
FROM Python(table)
""", "Vertical"))
end = time.time()
print(f"{end-start} seconds")

下面显示了该代码块的输出

Row 1:
──────
sum(score):    49998155002154
avg(score):    499981.55002154
median(score): 493265
avgIf:         499955.15289763256
wins:          33340305
draws:         33334238
losses:        33325457
count():       100000000

3.0047709941864014 seconds

我们还可以查询 Python 字典,只要值是列表即可

x = {"col1": [random.uniform(0, 1) for _ in range(0, 1_000_000)]}

print(chdb.query("""
SELECT avg(col1), max(col1)
FROM Python(x)
""", "DataFrame"))

   avg(col1)  max(col1)
0   0.499888   0.999996

试一试,让我们知道您的进展如何!

希尔伯特曲线

由 Artem Mustafin 贡献

具有数学背景的用户可能熟悉空间填充曲线的概念。在 24.6 版本中,我们添加了对希尔伯特曲线的支持,以补充现有的 Morton 编码函数。这些曲线有可能加速某些查询,这在时间序列和地理数据中很常见。

空间填充曲线是连续曲线,它穿过给定的多维空间中的每个点,通常在单位正方形或立方体内,从而完全填充空间。这些曲线对许多人来说都很有吸引力,主要是因为它们挑战了直观的概念,即一维线不能覆盖二维面积或更高维的体积。最著名的例子包括希尔伯特曲线和更简单(也稍早)的 Peano 曲线,这两条曲线都是迭代构造的,以增加其在空间内的复杂性和密度。空间填充曲线在数据库中具有实际应用,因为它们提供了一种有效的方式将多维数据映射到一维,同时保留局部性。

考虑一个二维坐标系,其中一个函数将任何点映射到一维线,同时保留局部性,确保原始空间中接近的点在线上仍然接近。此线使用单个数值对高维数据进行排序。通过想象一个二维空间划分为四个象限,每个象限进一步划分为四个,从而产生 16 个部分来可视化此过程。这种递归过程虽然概念上是无限的,但在有限深度(例如深度 3)更容易掌握,从而产生一个 8x8 网格,其中包含 64 个象限。此函数生成的曲线穿过所有象限,并在最终线上保持原始点的邻近性。

hilbert_curves.png

重要的是,我们越深入递归和分割空间,线上更多的点就越稳定在其等效的二维位置。在无穷远处,空间被完全“填充”。

用户可能也熟悉术语来自 Delta lake 等湖格式的 Z 阶 - 也称为 Morton 阶。这是另一种空间填充曲线,它也保留空间局部性,类似于我们上面的希尔伯特示例。Morton 阶通过 Clickhouse 中的 mortenEncode 函数 支持,它不如希尔伯特曲线那样好地保留局部性,但在某些情况下由于其更简单的计算和实现而受到青睐。

有关空间填充曲线及其与无限数学的关系的精彩介绍,我们推荐此视频

那么,为什么这可能有助于优化数据库中的查询呢?

我们可以有效地将多个列编码为单个值,并通过保持数据的空间局部性,将结果值用作表中的排序顺序。这在提高范围查询和最近邻搜索的性能方面非常有用,并且在编码列在范围和分布方面具有相似属性,同时还具有大量不同值时最有效。空间填充曲线的讨论属性确保在高维空间中彼此接近的值(相同的范围查询将匹配)将具有相似的排序值,并且将是同一粒度的一部分。因此,需要读取的粒度更少,查询速度更快!

列也不应相关 - 在这种情况下,ClickHouse 排序键使用的传统词典排序更有效,因为按第一列排序将固有地对第二列进行排序。

虽然数值 IOT 数据(如时间戳和传感器值)通常满足这些属性,但更直观的示例是地理经纬度坐标。让我们看一个关于如何使用希尔伯特编码来加速这些类型的查询的简单示例。

考虑 NOAA 全球历史气候网络数据,其中包含过去 120 年的 10 亿个天气测量值。每一行都是时间点和站点的测量值。我们在 2xcore r5.large 实例上执行以下操作。

我们稍微修改了默认架构,添加了 mercator_xmercator_y 列。这些值来自纬度和经度值的墨卡托投影,作为 UDF 实现,允许在 2d 表面上可视化数据。此投影通常用于将点投影到指定高度和宽度的像素空间中,如之前的博客中所述。我们使用它将坐标从 Float32 投影到无符号整数(使用最大 Int32 值 4294967295 作为我们的高度和宽度),这是我们的 hilbertEncode 函数所要求的。

墨卡托投影仪是一种流行的投影,通常用于地图。这有很多优点;最重要的是,它在局部尺度上保留了角度和形状,使其成为导航的绝佳选择。它还具有恒定方位(行进方向)是直线的优点,从而使导航变得简单。

CREATE OR REPLACE FUNCTION mercator AS (coord) -> (
   ((coord.1) + 180) * (4294967295 / 360),
   (4294967295 / 2) - ((4294967295 * ln(tan((pi() / 4) + ((((coord.2) * pi()) / 180) / 2)))) / (2 * pi()))
)

CREATE TABLE noaa
(
	`station_id` LowCardinality(String),
	`date` Date32,
	`tempAvg` Int32 COMMENT 'Average temperature (tenths of a degrees C)',
	`tempMax` Int32 COMMENT 'Maximum temperature (tenths of degrees C)',
	`tempMin` Int32 COMMENT 'Minimum temperature (tenths of degrees C)',
	`precipitation` UInt32 COMMENT 'Precipitation (tenths of mm)',
	`snowfall` UInt32 COMMENT 'Snowfall (mm)',
	`snowDepth` UInt32 COMMENT 'Snow depth (mm)',
	`percentDailySun` UInt8 COMMENT 'Daily percent of possible sunshine (percent)',
	`averageWindSpeed` UInt32 COMMENT 'Average daily wind speed (tenths of meters per second)',
	`maxWindSpeed` UInt32 COMMENT 'Peak gust wind speed (tenths of meters per second)',
	`weatherType` Enum8('Normal' = 0, 'Fog' = 1, 'Heavy Fog' = 2, 'Thunder' = 3, 'Small Hail' = 4, 'Hail' = 5, 'Glaze' = 6, 'Dust/Ash' = 7, 'Smoke/Haze' = 8, 'Blowing/Drifting Snow' = 9, 'Tornado' = 10, 'High Winds' = 11, 'Blowing Spray' = 12, 'Mist' = 13, 'Drizzle' = 14, 'Freezing Drizzle' = 15, 'Rain' = 16, 'Freezing Rain' = 17, 'Snow' = 18, 'Unknown Precipitation' = 19, 'Ground Fog' = 21, 'Freezing Fog' = 22),
	`location` Point,
	`elevation` Float32,
	`name` LowCardinality(String),
	`mercator_x` UInt32 MATERIALIZED mercator(location).1,
	`mercator_y` UInt32 MATERIALIZED mercator(location).2
)
ENGINE = MergeTree
ORDER BY (station_id, date)

INSERT INTO noaa SELECT * FROM s3('https://datasets-documentation.s3.eu-west-3.amazonaws.com/noaa/noaa_enriched.parquet') WHERE location.1 < 180 AND location.1 > -180 AND location.2 > -90 AND location.2 < 90

假设我们希望计算由边界框表示的区域的统计信息。例如,让我们计算一年中每周阿尔卑斯山的总降雪量和平均积雪深度(可能为我们提供滑雪的最佳时间!)。

hilbert_curves_02.png

上面的排序 (station_id, date) 在这里没有提供任何实际好处,并导致对所有行进行线性扫描。

WITH
	mercator((5.152588, 44.184654)) AS bottom_left,
	mercator((16.226807, 47.778548)) AS upper_right
SELECT
	toWeek(date) AS week,
	sum(snowfall) / 10 AS total_snowfall,
	avg(snowDepth) AS avg_snowDepth
FROM noaa
WHERE (mercator_x >= (bottom_left.1)) AND (mercator_x < (upper_right.1)) AND (mercator_y >= (upper_right.2)) AND (mercator_y < (bottom_left.2))
GROUP BY week
ORDER BY week ASC

54 rows in set. Elapsed: 1.449 sec. Processed 1.05 billion rows, 4.61 GB (726.45 million rows/s., 3.18 GB/s.)

┌─week─┬─total_snowfall─┬──────avg_snowDepth─┐
│    00150.52388947519907 │
│    156.7164.85788967420854 │
│    244181.53357163761027 │
│    313.3190.36173190191738 │
│    425.2199.41843468092216 │
│    530.7207.35987294422503 │
│    618.8222.9651218746731 │
│    77.6233.50080515297907 │
│    82.8234.66253449285057 │
│    919231.94969343126792 │
...
│   485.189.46301878043126 │
│   4931.1103.70976325737577 │
│   5011.2119.3421940216704 │
│   5139133.65286953585073 │
│   5220.6138.1020341499629 │
│   532125.68478260869566 │
└─week─┴─total_snowfall──┴──────avg_snowDepth─┘

对于这种类型的查询,一个自然的选择可能是按 (mercator_x, mercator_y) 对表进行排序 - 首先按 mercator_x 排序,然后按 mercator_y 排序。通过将读取的行数减少到 4200 万,这实际上显着提高了查询性能时间

CREATE TABLE noaa_lat_lon
(
	`station_id` LowCardinality(String),
	…
	`mercator_x` UInt32 MATERIALIZED mercator(location).1,
	`mercator_y` UInt32 MATERIALIZED mercator(location).2
)
ENGINE = MergeTree
ORDER BY (mercator_x, mercator_y)

--populate from existing
INSERT INTO noaa_lat_lon SELECT * FROM noaa

WITH
	mercator((5.152588, 44.184654)) AS bottom_left,
	mercator((16.226807, 47.778548)) AS upper_right
SELECT
	toWeek(date) AS week,
	sum(snowfall) / 10 AS total_snowfall,
	avg(snowDepth) AS avg_snowDepth
FROM noaa_lat_lon
WHERE (mercator_x >= (bottom_left.1)) AND (mercator_x < (upper_right.1)) AND (mercator_y >= (upper_right.2)) AND (mercator_y < (bottom_left.2))
GROUP BY week
ORDER BY week ASC

--results omitted for brevity
54 rows in set. Elapsed: 0.197 sec. Processed 42.37 million rows, 213.44 MB (214.70 million rows/s., 1.08 GB/s.)

顺便提一下,经验丰富的 ClickHouse 用户可能会尝试在此处使用函数 pointInPolygon。不幸的是,这目前没有利用索引,并且导致性能较慢

我们可以看到,通过使用 EXPLAIN indexes=1 子句,此键在过滤粒度方面相对有效,将要读取的粒度数量减少到 5172 个。

EXPLAIN indexes = 1
WITH
	mercator((5.152588, 44.184654)) AS bottom_left,
	mercator((16.226807, 47.778548)) AS upper_right
SELECT
	toWeek(date) AS week,
	sum(snowfall) / 10 AS total_snowfall,
	avg(snowDepth) AS avg_snowDepth
FROM noaa_lat_lon
WHERE (mercator_x >= (bottom_left.1)) AND (mercator_x < (upper_right.1)) AND (mercator_y >= (upper_right.2)) AND (mercator_y < (bottom_left.2))
GROUP BY week
ORDER BY week ASC

Markdown Image

但是,上述 2 级排序不能保证彼此接近的点驻留在同一粒度中。希尔伯特编码应提供此属性,使我们能够更有效地过滤粒度。为此,我们需要将表 ORDER BY 修改为 hilbertEncode(mercator_x, mercator_y)

CREATE TABLE noaa_hilbert
(
	`station_id` LowCardinality(String),
	...
	`mercator_x` UInt32 MATERIALIZED mercator(location).1,
	`mercator_y` UInt32 MATERIALIZED mercator(location).2
)
ENGINE = MergeTree
ORDER BY hilbertEncode(mercator_x, mercator_y)

WITH
	mercator((5.152588, 44.184654)) AS bottom_left,
	mercator((16.226807, 47.778548)) AS upper_right
SELECT
	toWeek(date) AS week,
	sum(snowfall) / 10 AS total_snowfall,
	avg(snowDepth) AS avg_snowDepth
FROM noaa_hilbert
WHERE (mercator_x >= (bottom_left.1)) AND (mercator_x < (upper_right.1)) AND (mercator_y >= (upper_right.2)) AND (mercator_y < (bottom_left.2))
GROUP BY week
ORDER BY week ASC

--results omitted for brevity

54 rows in set. Elapsed: 0.090 sec. Processed 3.15 million rows, 41.16 MB (35.16 million rows/s., 458.74 MB/s.)

我们的编码将查询性能减半至 0.09 秒,仅读取了 300 万行。我们可以使用 EXPLAIN indexes=1 确认更高效的粒度过滤。

Markdown Image

为什么不只使用希尔伯特或 Morton 编码对我的所有排序键进行编码?

首先,mortonEncode 和 hilbertEncode 函数仅限于无符号整数(因此需要使用上面的墨卡托投影)。其次,如前所述,如果您的列是相关的,那么使用空间填充曲线不会增加任何好处,反而会增加插入(和排序)时间开销。此外,如果仅按键中的第一列进行过滤,则经典排序更有效。如果仅按第二列进行过滤,则希尔伯特(或 Morton 排序)编码平均会更快。

分享这篇文章

订阅我们的新闻通讯

随时了解功能发布、产品路线图、支持和云产品!
正在加载表单...
关注我们
X imageSlack imageGitHub image
Telegram imageMeetup imageRss image
©2025ClickHouse, Inc. 总部位于美国加利福尼亚州湾区和荷兰阿姆斯特丹。