前言
本文翻译自 Altinity 针对 ClickHouse 的系列技术文章。面向联机分析处理(OLAP)的开源分析引擎 ClickHouse,因其优良的查询性能,PB 级的数据规模,简单的架构,被国内外公司广泛采用。
阿里云 EMR-OLAP 团队,基于开源 ClickHouse 进行了系列优化,提供了开源 OLAP 分析引擎 ClickHouse 的云上托管服务。EMR ClickHouse 完全兼容开源版本的产品特性,同时提供集群快速部署、集群管理、扩容、缩容和监控告警等云上产品功能,并且在开源的基础上优化了 ClickHouse 的读写性能,提升了 ClickHouse 与 EMR 其他组件快速集成的能力。访问 https://help.aliyun.com/document_detail/212195.html 了解详情。
译者:何源(荆杭),阿里云计算平台事业部高级产品专家
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 只返回一行,我们也可以这么做:
SELECT QueryID, groupArray((ecode, cnt)) FROM ( SELECT QueryID, ecode, sum(ecnt) AS cnt FROM queries ARRAY JOIN Errors.ErrorCode AS ecode, Errors.ErrorCnt AS ecnt GROUP BY QueryID, ecode ) GROUP BY QueryID ┌─QueryID─┬─groupArray(tuple(ecode, cnt))──────────────────────────────┐ │ 5 │ [('1230',6),('1212',2),('1260',12),('1220',8),('1240',16)] │ └─────────┴────────────────────────────────────────────────────────────┘
结论
ClickHouse 提供了灵活的方式来存储数据,尽管它是一个列式数据库,但可以实现较低的结构化程度,并提供各种函数来提取和聚合数据。
后续
您已经了解了在 ClickHouse 中处理实时更新相关内容,本系列还包括其他内容:
- 在 ClickHouse 中处理实时更新
- 使用新的 TTL move,将数据存储在合适的地方
- 在 ClickHouse 物化视图中使用 Join
- ClickHouse 聚合函数和聚合状态
- ClickHouse 中的嵌套数据结构(本文)
原文链接:https://altinity.com/blog/2017/8/30/nested-data-structures-in-clickhouse
获取更多 EMR ClickHouse 相关信息,可查看产品文档:
https://help.aliyun.com/document_detail/212195.html
钉钉扫描下方二维码加入产品交流群一起参与讨论~