ClickHouse 中的嵌套数据结构

在这篇文章中,我们将了解 ClickHouse for MySQL 中的嵌套数据结构,以及如何将其与 PMM 结合使用来查看查询。

嵌套结构在关系数据库管理系统中并不常见。通常情况下,它只是平面表。有时,将非结构化信息存储在结构化数据库中会很方便。

我们正在努力将 ClickHouse 调整为用于 Percona 监控和管理 (PMM) 的长期存储,尤其是存储有关查询的详细信息。我们试图解决的问题之一是,对导致特定查询失败的不同错误进行计数。

例如,对于日期为 2017-08-17 的查询:

"SELECT foo FROM bar WHERE id=?"

被执行了 1000 次。其中 25 次失败的错误代码为“1212”,8 次失败的错误代码为“1250”。当然,在关系数据中进行存储的传统方法是创建一个表 "Date, QueryID, ErrorCode, ErrorCnt",然后对这个表执行 JOIN。遗憾的是,列式数据库在多个 Join 的情况下表现不佳,通常建议使用非规范化表。

我们可以为每个可能的 ErrorCode 创建一个列,但这并不是最优解。可能有成千上万的列,而且大多数时候它们都是空的。

在这种情况下,ClickHouse 提出了嵌套数据结构。对于我们的情况,这些可以定义为:

CREATE TABLE queries
(
    Period Date,
    QueryID UInt32,
    Fingerprint String,
    Errors Nested
    (
        ErrorCode String,
        ErrorCnt UInt32
    )
)Engine=MergeTree(Period,QueryID,8192);

这个解决方案有明显的问题:我们如何在这个表中插入数据?我们如何提取它?

我们先从 INSERT 开始。插入可能如下所示:

INSERT INTO queries VALUES ('2017-08-17',5,'SELECT foo FROM bar WHERE id=?',['1220','1230','1212'],[5,6,2])

这意味着 2017-08-17 期间插入的查询出现了 5 次错误 1220,6 次错误 1230,2 次错误 1212。

那么在不同的日期,它可能会产生不同的错误:

INSERT INTO queries VALUES ('2017-08-18',5,'SELECT foo FROM bar WHERE id=?',['1220','1240','1258'],[3,2,1])

让我们看一下 SELECT 数据的方法。非常基础的 SELECT:

SELECT *
FROM queries
|_____Period_|_QueryID_|_Fingerprint_|_Errors.ErrorCode_______|_Errors.ErrorCnt_|
| 2017-08-17 |       5 | SELECT foo  | ['1220','1230','1212'] | [5,6,2]         |
| 2017-08-18 |       5 | SELECT foo  | ['1220','1240','1260'] | [3,16,12]       |
|____________|_________|_____________|________________________|_________________|

如果我们想使用更熟悉的表格输出,则可以使用 ARRAY JOIN 扩展:

SELECT *
FROM queries
ARRAY JOIN Errors
┌─────Period─┬─QueryID─┬─Fingerprint─┬─Errors.ErrorCode─┬─Errors.ErrorCnt─┐
│ 2017-08-17 │       5 │ SELECT foo  │ 1220             │            5    │
│ 2017-08-17 │       5 │ SELECT foo  │ 1230             │            6    │
│ 2017-08-17 │       5 │ SELECT foo  │ 1212             │            2    │
│ 2017-08-18 │       5 │ SELECT foo  │ 1220             │            3    │
│ 2017-08-18 │       5 │ SELECT foo  │ 1240             │           16    │
│ 2017-08-18 │       5 │ SELECT foo  │ 1260             │           12    │
└────────────┴─────────┴─────────────┴──────────────────┴─────────────────┘

但是,通常我们希望看到多个期间的聚合,这可以通过传统的聚合函数来完成:

SELECT 
    QueryID,
    Errors.ErrorCode,
    SUM(Errors.ErrorCnt)
FROM queries
ARRAY JOIN Errors
GROUP BY 
    QueryID,
    Errors.ErrorCode
┌─QueryID─┬─Errors.ErrorCode─┬─SUM(Errors.ErrorCnt)─┐
│       5 │ 1212             │                 2    │
│       5 │ 1230             │                 6    │
│       5 │ 1260             │                12    │
│       5 │ 1240             │                16    │
│       5 │ 1220             │                 8    │
└─────────┴──────────────────┴──────────────────────┘

如果我们别出心裁,每个 QueryID 只返回一行,我们也可以这么做:

展开阅读全文

本文系作者在时代Java发表,未经许可,不得转载。

如有侵权,请联系nowjava@qq.com删除。

编辑于

关注时代Java

关注时代Java