利用投影提升ClickHouse查询性能

ClickHouse中表排序键对与查询性能至关重要。本文介绍当使用非排序键作为查询条件时如何提升查询性能。通过对非排序键定义投影,然后物化排序结果,利用空间换时间策略,提升查询性能。

选择ClickHouse排序键的规则

当创建MergeTree表时,需要指定列的顺序作为排序键。排序键的顺序对查询性能影响很大,因为排序键决定需要计算的数据在磁盘上排列在一起的紧密程度。

当选择排序键时,应该遵循下面几个规则:

  • 把过滤条件中最常用的列排在最前面
  • 排序键的第一列应该为最常用的列且基数最低
  • 排序键不要超过3或4个,以为会影响插入性能

排序键举例

当查询过滤条件有多个列时,我们如何定义排序键,下面通过示例进行说明。

CREATE TABLE deleteme
(
    `product_id` UInt64,
    `client_id` UInt64
)
ENGINE = MergeTree
PARTITION BY product_id % 10
ORDER BY (product_id, client_id) AS
SELECT number % 100 product_id, number % 100 client_id
FROM numbers(100000000)

整个表占用空间较小,仅~7MB:

SELECT formatReadableSize(total_bytes)
FROM system.tables
WHERE name = 'deleteme'
FORMAT Vertical

Row 1:
──────
formatReadableSize(total_bytes): 7.64 MiB

运行带product_id的查询条件,仅需要~1M行,因为表第一个排序键为product_id:

SELECT *
FROM deleteme
WHERE product_id = 10
FORMAT `Null`

0 rows in set. Elapsed: 0.014 sec. Processed 1.03 million rows, 16.52 MB (72.40 million rows/s., 1.16 GB/s.)

但如果过滤条件使用client_id(第二个排序键),ClickHouse查询时间增加仅3倍,由于相同的client_id在product_id排序的磁盘上不同块中重复存在,ClickHouse需要读取和处理更多的数据来运行查询:

SELECT *
FROM deleteme
WHERE client_id = 10
FORMAT `Null`

0 rows in set. Elapsed: 0.048 sec. Processed 2.97 million rows, 31.98 MB (61.42 million rows/s., 661.52 MB/s.)

利用投影提升性能

下面通过定义投影解决上面查询性能问题。投影概念很简单,即对client_id定义排序并重新排序,从而提升对client_id的查询性能。请看示例:

ALTER TABLE deleteme
    ADD PROJECTION deleteme_by_client_id
    (
        SELECT *
        ORDER BY client_id
    )
ALTER TABLE deleteme
    MATERIALIZE PROJECTION deleteme_by_client_id

PROJECTION 定义了数据按client_id进行排序,当新的部分需要合并时,会以client_id为顺序组织数据。在查询时ClickHouse会透明地使用按Client_id排序的部分。

现在再次执行上节中的查询,可以看到仅读取~1M行数据:

SELECT *
FROM deleteme
WHERE client_id = 10
FORMAT `Null`

Query id: 51a55fec-d526-480b-870b-424a0c6471d3

0 rows in set. Elapsed: 0.052 sec. Processed 1.25 million rows, 18.28 MB (24.17 million rows/s., 352.53 MB/s.)

为了确认projection对性能有提升作用,我们可以检查query_log:

SELECT projections
FROM system.query_log
WHERE (event_time > (now() - toIntervalMinute(5))) AND (query_id = '51a55fec-d526-480b-870b-424a0c6471d3')
LIMIT 1
FORMAT Vertical

Row 1:
──────
projections: ['default.deleteme.deleteme_by_client_id']

当然采用投影会重复存储数据,但在一定场景中可以接受空间和时间的平衡,特别针对较小的表。

命令行查看查询性能

在命令行中执行查询,ClickHouse会自动生成查询性能统计。对于相同的查询会有多种写法,每种方式的查询性能可能有差异。通过分析性能统计,可能会发现最佳的查询方案。

我们可以使用 FORMAT Null 子句执行查询,则仅返回查询性能统计。上面的示例我们就使用了该功能。

SELECT *
FROM system.query_log
WHERE event_time > (now() - toIntervalMinute(10))
FORMAT `Null`

Query id: 7a125064-5422-471c-a170-e18601b2d631

Ok.

0 rows in set. Elapsed: 0.019 sec. Processed 49.86 thousand rows, 1.81 MB (2.61 million rows/s., 94.45 MB/s.)

FORMAT Vertical

我们也可以使用FORMAT Vertical子句,是的查询结果按列方式排列,对于返回数据行较少,但数据列时查看数据比较方便。

SELECT * FROM table_with_a_lot_of_columns FORMAT Vertical

Row 1:
────────
type:                                  QueryFinish
event_date:                            2022-09-22
event_time:                            2022-09-22 09:29:58
event_time_microseconds:               2022-09-22 09:29:58.298699
query_start_time:                      2022-09-22 09:29:58
query_start_time_microseconds:         2022-09-22 09:29:58.296902
query_duration_ms:                     1
read_rows:                             0
read_bytes:                            0
written_rows:                          60
written_bytes:                         8879
result_rows:                           0
result_bytes:                          0
memory_usage:                          4325156
current_database:                      public

总结

本文介绍了排序键对查询的作用,并通过示例对比使用projection提升查询性能,最后也提及如何在命令行下查询性能统计信息。参考文档:https://www.tinybird.co/clickhouse/knowledge-base/improve-performance-inverted-index

你可能感兴趣的:(ClickHouse,clickhouse,投影)