【问题标题】:How to use `toYYYYMMDD(timestamp)` in primary key in clickhouse如何在clickhouse的主键中使用`toYYYYMMDD(timestamp)`
【发布时间】:2020-01-01 10:40:53
【问题描述】:

我使用 clickhouse 作为我的项目底层数据库及其MergeTree 表引擎。我正在尝试使用Custom Partitioning Key 功能,我创建了这样的表:

CREATE TABLE MyTable
(    
        UserID UUID,     
        ResourceID UUID,        
        TimeStamp DateTime,         
        Usage Int64,
        Version UInt32
)
ENGINE = ReplacingMergeTree(Version)
PARTITION BY toYYYYMM(TimeStamp)
ORDER BY (toYYYYMMDD(TimeStamp), UserID, ResourceID , TimeStamp)

分区键是toYYYYMM(TimeStamp),这意味着clickhouse按月分隔数据。同时,我使用toYYYYMMDD(TimeStamp) 作为主索引的第一列。我提取了 1 年的数据(每月大约 30-40 百万行)进行测试。

当我使用TimeStamp 过滤条件查询数据时,我得到一些令人困惑的结果。这是我的 sql:

select count(*) from MyTable WHERE 
TimeStamp>=toDateTime('2019-01-30 00:00:00') 
and 
TimeStamp<toDateTime('2019-02-04 00:00:00')

它处理了7562万行(1月和2月的全部数据)!这意味着查询条件可以命中正确的分区但错过了主键。但是如果手动转换TimeStamp,像这样:

select count(*) from MyTable WHERE
toYYYYMMDD(TimeStamp)>=toYYYYMMDD(toDateTime('2019-01-30 00:00:00')) 
and 
toYYYYMMDD(TimeStamp)<toYYYYMMDD(toDateTime('2019-02-04 00:00:00'))

然后它只处理了 700 万行(5-6 天的数据)。这意味着查询条件命中了主键。

让我困惑的是,既然 clickhouse 可以在不转换 TimeStamp 的情况下命中正确的分区,为什么它也不能自动命中主键?我真的需要手动转换时间戳吗?

【问题讨论】:

    标签: primary-key partition clickhouse


    【解决方案1】:

    您的表定义向用于定义行数据索引的 ORDER BY 中添加了太多信息。我猜当您将过滤条件指定为 toDateTime('2019-01-30 00:00:00') 时,ClickHouse 无法使用索引的第一部分。结果,它最终读取了整个部分。尝试按如下方式定义您的表:

    CREATE TABLE MyTable (
            UserID UUID,
            ResourceID UUID,
            TimeStamp DateTime,
            Usage Int64,
            Version UInt32 ) 
    ENGINE = ReplacingMergeTree(Version)
    PARTITION BY toYYYYMM(TimeStamp)
    ORDER BY (UserID, ResourceID ,TimeStamp)
    

    在这种情况下,您应该看到两个查询的性能相同。在这两种情况下,它可能比过滤器与索引的第一列完全匹配的最快查询慢一点,但比不匹配的情况快很多。

    有关正在发生的事情的更多信息,请查看 ClickHouse 日志,该日志位于 /var/log/clickhouse-server/clickhouse-server.log 中。您会看到消息告诉您 ClickHouse 实际读取了多少数据来进行计数。这是一个例子。

    2019.08.29 14:46:39.507239 [ 41 ] {fc8cb747-5db2-40ac-afeb-32b61192de9a} <Debug> executeQuery: (from 127.0.0.1:49598) SELECT count(*) FROM MyTable WHERE (TimeStamp >= toDateTime('2019-09-30 00:00:00')) AND (TimeStamp < toDateTime('2019-10-04 00:00:00')) 
    ...
    2019.08.29 14:46:39.518160 [ 41 ] {fc8cb747-5db2-40ac-afeb-32b61192de9a} <Debug> default.MyTable (SelectExecutor): Selected 2 parts by date, 2 parts by key, 63 marks to read from 11 ranges
    2019.08.29 14:46:39.518739 [ 41 ] {fc8cb747-5db2-40ac-afeb-32b61192de9a} <Trace> default.MyTable (SelectExecutor): Reading approx. 516096 rows with 2 streams
    

    在这种情况下,我们可以看到它选择了相对较少的标记(即数据颗粒)来读取。这让您了解主键索引的工作效率。

    【讨论】:

    • 感谢您的回复。如您所说,主索引中的第一列 - toYYYYMMDD(TimeStamp) 就像一个“隐藏”列,所以我们必须通过手动转换 TimeStamp 来匹配它?
    • ClickHouse 在转换函数方面非常聪明。选择部件时,它可以识别 toYYYYMM() 随输入的 DateTime 值单调变化。它将自动应用转换来选择零件。我实际上希望它使用索引中的第一列,但 ClickHouse 可能看不到
    • (输入错误)ClickHouse 在转换函数方面非常聪明。它跟踪函数的单调性,并且可以正确地将 DateTime 值的转换应用于选择部分。我实际上希望它在索引中做同样的事情,但似乎没有。您更明确的过滤条件帮助了它。在这种情况下,系统日志消息没有帮助,至少对我而言。
    猜你喜欢
    • 1970-01-01
    • 2021-12-24
    • 2016-12-05
    • 2019-10-05
    • 2021-03-07
    • 2019-09-22
    • 1970-01-01
    • 1970-01-01
    • 2021-10-15
    相关资源
    最近更新 更多