概述
ClickHouse是俄罗斯的Yandex于2016年开源的列式存储数据库(DBMS),以下是对其的详细介绍:
一、主要应用场景
ClickHouse主要用于在线分析处理查询(OLAP),能够使用SQL查询实时生成分析数据报告。与联机事务处理(OLTP)不同,OLAP专注于分析处理,主要是对数据的查询。
二、技术特点
-
列式存储:
- 好处:对于列的聚合、计数、求和等统计操作优于行式存储;由于某一列的数据类型都是相同的,针对数据存储更容易进行数据压缩,每一列选择更优的数据压缩算法,大大提高了数据的压缩比重;由于数据压缩比更好,一方面节省了磁盘空间,另一方面对于cache也有了更大的发挥空间。
-
DBMS功能:
- 几乎覆盖了标准SQL的大部分语法,包括DDL和DML,以及配套的各种函数、用户管理及权限管理、数据的备份与恢复。
-
多样化引擎:
- ClickHouse和MySQL类似,把表级的存储引擎插件化,根据表的不同需求可以设定不同的存储引擎。
-
高吞吐写入能力:
- ClickHouse采用类LSM Tree的结构,数据写入后定期在后台Compaction。通过类LSM tree的结构,ClickHouse在数据导入时全部是顺序append写,写入后数据段不可更改,在后台compaction时也是多个段merge sort后顺序写回磁盘。顺序写的特性,充分利用了磁盘的吞吐能力,即便在HDD上也有着优异的写入性能。官方公开benchmark测试显示能够达到50MB~200MB/s的写入吞吐能力,按照每行100Byte估算,大约相当于50W~200W条/s的写入速度。
-
并行数据处理:
- ClickHouse将数据划分为多个partition,每个partition再进一步划分为多个index granularity(索引粒度),然后通过多个CPU核心分别处理其中的一部分来实现并行数据处理。在这种设计下,单条Query就能利用整机所有CPU。所以,ClickHouse即使对于大量数据的查询也能够化整为零平行处理。但是有一个弊端就是对于单条查询使用多cpu,就不利于同时并发多条查询。所以对于高qps的查询业务,ClickHouse并不是强项。
-
其他特点:
- 急速处理、硬件效率高、线性可扩展、容错、功能丰富、高度可靠。
- 支持多主机异步复制,并且可以跨多个数据中心进行部署。所有节点都相等,这可以避免出现单点故障。单个节点或整个数据中心的停机时间不会影响系统的读写可用性。
- 简单易用,开箱即用。它简化了所有数据处理,将所有结构化数据吸收到系统中,并且立即可用于构建报告。
- 提供了许多针对OLAP场景的特定于域的功能和功能。
- 可以在垂直和水平方向上很好地缩放。
三、性能表现
ClickHouse的工作速度比传统方法快100~1000倍,性能超过了当前市场上可比的面向列的数据库管理系统。每秒钟每台服务器每秒处理数亿至十亿多行和数十千兆字节的数据。ClickHouse会充分利用所有可用的硬件,以尽可能快地处理每个查询。单个查询的峰值处理性能超过每秒2TB(解压缩后,仅使用的列)。
四、限制与不足
- 不支持事务,不支持真正的删除/更新。
- 不支持高并发,官方建议qps为100,可以通过修改配置文件增加连接数。
- 不支持二级索引。
- 不擅长多表json。
- 元数据管理需要人为干预。
五、使用建议
- 尽量做1000以上批量的写入,避免逐行insert或小批量的insert、update、delete操作。
- 绝大多数请求都是用于读访问的,要求实时返回结果。
- 数据需要以大批次(大于1000行)进行更新,而不是单行更新,或者根本没有更新操作。
- 读取数据时,会从数据库中提取出大量的行,但只用到一小部分列。
综上所述,ClickHouse是一款功能强大、性能卓越的列式存储数据库,特别适用于在线分析处理查询场景。然而,它也存在一些限制和不足,在使用时需要充分考虑这些因素。
分布式架构
ClickHouse分布式架构是一种高效的数据处理架构,特别适用于大数据量(TB级别)和高并发查询的场景。以下是对ClickHouse分布式架构的详细解析:
一、架构特点
-
多主架构:
- ClickHouse采用多主架构,每个节点都可以独立对外提供服务,无需主从节点之间的数据同步,从而简化了系统架构。
- 节点之间无共享,同步由第三方分布式协调组件(如ZooKeeper)提供。
-
分布式协调:
- ClickHouse的分布式架构依赖于ZooKeeper等分布式协调组件进行节点间的通信和协调。
- ZooKeeper用于管理集群中的元数据,如节点的状态、配置信息等,并确保数据的一致性。
-
高度可扩展性:
- ClickHouse支持通过添加节点实现计算和存储能力的水平扩展。
- 无论是处理单台服务器的数据,还是跨越多台机器进行数据分布式处理,ClickHouse都能高效运作。
二、核心组件
-
Shard(分片):
- 分片是ClickHouse分布式架构中的基本单位,用于将数据分散到多个节点上进行存储和计算。
- 每个分片都是一个
独立的数据库实例
,可以包含多个副本以提高数据的可靠性。
-
Replica(副本):
- 副本是ClickHouse中实现
数据冗余
和容错
的重要手段。 - 每个分片可以有多个副本,这些副本之间的数据是同步的,以确保在节点故障时数据的完整性和可用性。
- 副本的实现依赖于ReplicatedMergeTree等复制表引擎。
- 副本是ClickHouse中实现
-
ZooKeeper Service:
- ZooKeeper是ClickHouse分布式架构中的关键组件,用于管理集群的元数据和协调节点间的操作。
- 它提供了节点注册、状态监控、数据同步等功能,确保集群的稳定性和一致性。
三、数据组织方式
-
列式存储:
- ClickHouse采用列式存储方式,将同一列的数据存储在一起。
- 这种存储方式可以显著减少I/O操作和数据读取量,从而提升查询效率。
-
MergeTree表引擎:
- MergeTree是ClickHouse中最核心的存储引擎之一,具有分区、排序、索引和分段存储的特性。
- 它支持对大规模数据进行高效的增量插入和查询。
-
分布式表引擎:
- 分布式表引擎是ClickHouse实现分布式查询的关键组件。
- 它将分布式表映射到指定集群、数据库下对应的本地表上,从而在查询时能够自动将请求转发给对应的本地表进行计算。
四、分布式查询原理
-
查询分发:
- 在分布式查询中,请求节点会将查询改写并转发给所有分片(Shard)。
- 每个分片在收到查询请求后,会在本地进行计算并将结果返回给请求节点。
-
结果合并:
- 请求节点在收到所有分片的结果后,会进行结果的合并和排序等操作。
- 最终将合并后的结果返回给用户。
五、优势与局限性
-
优势:
- 高性能:ClickHouse采用列式存储和多线程并行计算,能够在大数据量下实现秒级的查询响应。
- 实时性:支持对数据的实时插入和查询,可以实现对实时数据的分析与监控。
- 可扩展性:通过添加节点可以实现计算和存储能力的水平扩展。
-
局限性:
- 复杂的SQL优化受限:由于ClickHouse的多主架构没有统一的任务调度器,因此只能实现简单的任务调度能力,无法支持复杂的SQL优化。
- 数据再平衡困难:在扩容或缩容时,需要用户自行将数据迁移到新的节点上,给运维带来了复杂度。
综上所述,ClickHouse分布式架构以其高性能、实时性和可扩展性在大数据处理领域展现出了强大的优势。然而,在复杂的SQL优化和数据再平衡方面仍存在一定的局限性。因此,在选择和使用ClickHouse时,需要根据具体的应用场景和需求进行权衡和考虑。
核心架构
ClickHouse的核心架构主要分为两个部分:ClickHouse执行过程架构和ClickHouse数据存储架构。以下是对这两个部分的详细解析:
一、ClickHouse执行过程架构
-
Parser分析器:
- 负责将SQL语句解析成AST(抽象语法树)对象。
- 不同的SQL语句会经由不同的Parser实现类解析,例如ParserRenameQuery、ParserDropQuery、ParserAlterQuery用于解析DDL查询语句,ParserInsertQuery用于解析INSERT语句,ParserSelectQuery用于解析SELECT语句等。
-
Interpreter解释器:
- 负责解释AST,并进一步创建查询的执行管道。
- Interpreter解释器会根据解释器的类型,聚合它所需要的资源,并串联起整个查询过程。
-
IStorage接口:
- 表示一张表,不同的实现对应不同的表引擎,如StorageMergeTree、StorageMemory等。
- IStorage中最重要的方法是read和write,负责数据的读写操作。此外,还有alter、rename和drop等方法用于表的修改。
-
IBlockInputStream和IBlockOutputStream:
- IBlockInputStream具有read方法,能够在数据可用时获取下一个块。
- IBlockOutputStream具有write方法,能够将块写到某处。
- 这两个接口用于处理数据块,实现数据的读取、转换和写入操作。
二、ClickHouse数据存储架构
-
Column对象:
- 表示内存中的列(实际上是列块),使用IColumn接口。
- IColumn接口提供了用于实现各种关系操作符的辅助方法,如插入数据的insertRangeFrom和insertFrom方法、用于分页的cut方法、以及用于过滤的filter方法等。
- Column对象分为接口和实现两个部分,具体实现对象根据数据类型的不同,由相应的对象实现,例如ColumnString、ColumnArray和ColumnTuple等。
-
Field对象:
- 表示单个值,使用Field类。
- Field是UInt64、Int64、Float64、String和Array组成的联合。
- 与Column对象的泛化设计思路不同,Field对象使用了聚合的设计模式,内部聚合了Null、UInt64、String和Array等13种数据类型及相应的处理逻辑。
-
IDataType:
- 负责序列化和反序列化操作,读写二进制或文本形式的列或单个值构成的块。
- IDataType直接与表的数据类型相对应,如DataTypeUInt32、DataTypeDateTime、DataTypeString等。
- IDataType与IColumn之间的关联并不大,不同的数据类型在内存中能够用相同的IColumn实现来表示。同时,相同的数据类型也可以用不同的IColumn实现来表示。
-
Block对象:
- 表示内存中表的子集(chunk)的容器,是由三元组(IColumn, IDataType, 列名)构成的集合。
- 在查询执行期间,数据是按Block进行处理的。Block包含了数据(在IColumn对象中)、数据的类型信息(告诉如何处理该列)以及列名(来自表的原始列名或人为指定的用于临时计算结果的名字)。
-
表引擎:
- ClickHouse通过特定的表引擎支持特定的场景,如MergeTree表引擎用于数据的分区、索引和压缩存储等。
- 不同的表引擎由不同的子类实现,负责根据AST查询语句返回指定列的原始数据,后续数据处理由解释器对象完成。
综上所述,ClickHouse的核心架构通过Parser分析器、Interpreter解释器、IStorage接口、IBlockInputStream和IBlockOutputStream等组件实现了SQL语句的解析、执行和数据存储等功能。同时,通过Column对象、Field对象、IDataType和Block对象等数据结构实现了数据的列式存储和高效处理。
为什么速度这么快
存储层:并发插入
在 ClickHouse 中,每个表由多个“表部分”组成。每当用户向表中插入数据(INSERT 语句)时,都会创建一个部分。查询始终针对查询开始时存在的所有表部分执行。
为了避免积累过多的部分,ClickHouse 在后台运行合并操作,将多个(小)部分连续组合成一个更大的部分。
这种方法有几个优点:一方面,单个插入是“本地”的,因为它们不需要更新全局数据结构,即每个表的数据结构。因此,多个同时插入不需要相互同步或与现有表数据同步,因此插入可以几乎以磁盘 I/O 的速度执行。
存储层:并发插入和选择查询隔离
另一方面,合并部分是用户不可见的后台操作,即不会影响并发的 SELECT 查询。事实上,这种架构非常有效地隔离了插入和选择,以至于许多其他数据库都采用了它。
存储层:合并时间可控
与其他数据库不同,ClickHouse 还能够在合并操作期间执行其他数据转换。例如:
-
替换合并仅保留输入部分中行的最新版本,并丢弃所有其他行版本。替换合并可以被认为是合并时的清理操作。
-
聚合合并将输入部分的中间聚合状态合并为新的聚合状态。虽然这看起来很难理解,但它实际上只实现了增量聚合。
-
TTL(生存时间)合并根据某些基于时间的规则压缩、移动或删除行。
这些转换的目的是将工作(计算)从用户查询运行时间转移到合并时间。这很重要,原因有二:
一方面,如果用户可以利用“转换后的”数据(例如预聚合数据),查询速度可能会变得更快,有时会提高 1000 倍甚至更多。
另一方面,合并的大部分运行时间都花在加载输入部分和保存输出部分上。在合并期间转换数据的额外工作通常不会对合并的运行时间产生太大影响。所有这些魔法都是完全透明的,不会影响查询的结果(除了它们的性能)。
最先进的查询处理
最后,ClickHouse 使用矢量化查询处理层,尽可能并行化查询执行,以利用所有资源来实现最高的速度和效率。
“矢量化”意味着查询计划运算符会批量传递中间结果行,而不是单行传递。这样可以更好地利用 CPU 缓存,并允许运算符应用 SIMD 指令一次处理多个值。事实上,许多运算符都有多个版本-每个 SIMD 指令集生成一个版本。ClickHouse 将根据其运行的硬件的功能自动选择最新和最快的版本。
现代系统有数十个 CPU 核心。为了利用所有核心,ClickHouse 将查询计划展开为多个通道,通常每个核心一个。每个通道处理不相交的表数据范围。这样,数据库的性能就会随着可用核心的数量“垂直”扩展。
对细节的一丝不苟
“ClickHouse 是一个怪异的系统 - 你们有 20 个版本的哈希表。你们拥有所有这些令人惊叹的东西,而大多数系统只有一个哈希表 … ClickHouse 拥有如此惊人的性能,因为它拥有所有这些专门的组件” CMU 数据库教授 Andy Pavlo
ClickHouse 的与众不同之处在于它对底层优化的细致关注。构建一个简单可用的数据库是一回事,但将其设计为跨各种查询类型、数据结构、分布和索引配置提供速度才是“怪胎系统”艺术的闪光点。
哈希表。我们以哈希表为例。哈希表是连接和聚合使用的中心数据结构。作为程序员,需要考虑以下设计决策:
- 要选择的哈希函数,
- 冲突解决:开放寻址或链接,
- 内存布局:键和值使用一个数组还是单独的数组?
- 填充因子:何时以及如何调整大小?如何在调整大小时移动值?
- 删除:哈希表是否应允许驱逐条目?
第三方库提供的标准哈希表在功能上可以工作,但速度不快。出色的性能需要细致的基准测试和实验。
ClickHouse 中的哈希表实现根据查询和数据的具体情况从30 多个预编译哈希表变体中选择一种。
算法。算法也是如此。例如,在排序中,您可能会考虑:
- 将对什么进行排序:数字、元组、字符串还是结构?
- 数据在 RAM 中吗?
- 排序是否要求稳定?
- 是否应该对所有数据进行排序或部分排序是否足够?
优势是什么?
ClickHouse的优势主要体现在以下几个方面:
一、高性能
- 极速查询:ClickHouse专注于大规模数据分析和处理,具有出色的查询性能和吞吐量。它可以处理百亿甚至万亿级别的数据,并在秒级别提供查询结果。
- 高效压缩:使用高效的压缩算法,如LZ4、ZSTD和Delta压缩等,节省存储空间。其中LZ4适用于高吞吐量的数据,ZSTD适用于低存储空间的场景,Delta压缩则适用于存储连续递增或递减的数值类型数据。此外,ClickHouse还使用数据字典压缩技术,对于一些列中具有重复值的情况,可将重复值存储在字典中,并使用字典索引来代替真实值,以减小数据存储空间并提高查询性能。
二、列式存储
- 数据组织:数据按列进行组织,属于同一列的数据会被保存在一起,列与列之间也会由不同的文件分别保存。这种存储方式使得在进行数据分析和聚合操作时更加高效,因为同一列的数据具有相同的数据类型和相似的特征,便于压缩和优化。
- 减少扫描:在执行数据查询时,列式存储可以减少数据扫描范围和数据传输时的大小,提高了数据查询的效率。
三、分布式架构
- 水平扩展:ClickHouse采用分布式架构,可以方便地通过添加更多的节点来处理更大规模的数据,实现水平扩展。
- 负载均衡:它能够自动处理数据的分布和负载均衡,从而提供更好的可扩展性和容错性。
四、丰富的功能和灵活的数据模型
- 动态模式:ClickHouse支持动态模式,可以方便地存储和查询各种类型的数据,包括结构化和半结构化数据。
- 复杂查询:支持复杂的SQL查询和聚合操作,包括IP转化、URL分析、预估计算、HyperLogLog等,还可以进行高级数据分析和数据挖掘。此外,它还支持数组和嵌套数据结构。
五、优化查询引擎
- 向量化执行:ClickHouse利用CPU的SIMD指令实现了向量化执行,通过数据并行以提高性能。
- 并行处理:ClickHouse将数据划分为多个partition,每个partition再进一步划分为多个index granularity(索引粒度),然后通过多个CPU核心分别处理其中的一部分来实现并行数据处理。
六、易于使用和维护
- 简单配置:ClickHouse具有相对简单的安装和配置过程。
- 用户友好:提供了友好的用户界面和管理工具,还支持与其他数据处理和分析工具(如Apache Spark、Presto等)无缝集成。
七、开源与社区支持
- 开源项目:ClickHouse是一个开源项目,用户可以获取到最新的特性和修复。
- 社区支持:有活跃的社区支持,用户能够从社区中获取帮助和经验分享。
综上所述,ClickHouse在大数据分析和处理领域具有显著的优势,特别适用于对查询性能要求高、数据量大且以分析为主的应用场景。
缺点是什么?
ClickHouse的缺点主要包括以下几个方面:
一、事务支持不足
ClickHouse不支持ACID(原子性、一致性、隔离性、持久性)事务特性,这意味着在数据一致性、完整性保证方面存在局限。因此,在需要强一致性保证的金融交易、在线事务处理等场景中,ClickHouse可能不是最佳选择。
二、修改和删除操作受限
ClickHouse设计之初就倾向于批量处理和读操作,对于高频率的修改或删除已存在数据的能力较弱,仅支持批量删除或修改。这在一定程度上限制了它在需要频繁更新或删除数据的业务场景中的应用,同时也不符合GDPR(通用数据保护条例)对于数据修改和删除的灵活性要求。
三、行级查询性能不佳
由于ClickHouse采用列式存储,其索引设计和优化更多针对列级查询,而非行级查询。因此,在根据主键进行行粒度查询时,性能可能不如行式数据库。这使得在需要频繁根据主键检索单行数据的业务场景中,ClickHouse可能不是最优选择。
四、高并发性能受限
在高并发查询场景下,ClickHouse的性能可能会受到较大影响。这是因为ClickHouse在查询时会充分利用服务器资源,但在高并发情况下,资源竞争和调度开销会增加。因此,在需要处理大量并发查询的互联网业务、实时分析系统等场景中,ClickHouse的性能可能无法满足需求。
五、数据类型支持有限
ClickHouse不支持所有数据类型,如JSON和XML等复杂数据类型。这限制了其在处理多样化数据方面的灵活性。在需要存储和查询复杂数据结构的业务场景中,ClickHouse可能无法直接支持,需要进行数据转换或预处理。
综上所述,ClickHouse在事务支持、修改和删除操作、行级查询性能、高并发性能以及数据类型支持方面存在一定的缺点。因此,在选择数据库系统时,需要根据具体业务需求进行权衡和选择。
常见SQL语句
ClickHouse是一个用于在线分析处理(OLAP)的列式数据库管理系统(DBMS),其查询语法类似于SQL,但也具有一些独特的特点和函数。以下是一些ClickHouse语句的举例和说明:
一、数据库操作
- 创建数据库
CREATE DATABASE IF NOT EXISTS my_database;
此语句用于创建一个名为my_database
的数据库,如果该数据库已存在,则不会重复创建。
- 删除数据库
DROP DATABASE IF EXISTS my_database;
此语句用于删除名为my_database
的数据库,如果该数据库存在,则会被删除。
二、表操作
- 创建表
CREATE TABLE IF NOT EXISTS my_table (
id UInt64,
name String,
age UInt8
) ENGINE = MergeTree() ORDER BY (id);
此语句用于创建一个名为my_table
的表,并指定了表的列和数据类型,以及使用MergeTree
引擎和按id
排序。
- 删除表
DROP TABLE IF EXISTS my_table;
此语句用于删除名为my_table
的表,如果该表存在,则会被删除。
-
修改表
- 增加列
ALTER TABLE my_table ADD COLUMN address String;
此语句用于在
my_table
表中增加一个名为address
的列,数据类型为String
。- 修改列
ALTER TABLE my_table MODIFY COLUMN age UInt16;
此语句用于将
my_table
表中的age
列的数据类型修改为UInt16
。- 删除列
ALTER TABLE my_table DROP COLUMN address;
此语句用于删除
my_table
表中的address
列。
三、数据操作
- 插入数据
INSERT INTO my_table (id, name, age) VALUES (1, 'Alice', 30), (2, 'Bob', 25);
此语句用于向my_table
表中插入两行数据。
-
查询数据
- 基本查询
SELECT * FROM my_table;
此语句用于查询
my_table
表中的所有数据。- 条件查询
SELECT * FROM my_table WHERE age > 25;
此语句用于查询
my_table
表中age
大于25的所有数据。- 聚合查询
SELECT age, COUNT(*) AS count FROM my_table GROUP BY age;
此语句用于按
age
列对my_table
表进行分组,并计算每个年龄的人数。- 排序和限制
SELECT * FROM my_table ORDER BY age DESC LIMIT 5;
此语句用于按
age
列降序查询my_table
表中的数据,并限制返回结果的行数为5。 -
更新数据(注:ClickHouse通常用于只读或批量写入场景,更新操作不是其强项,但在某些版本和设置下支持)
ALTER TABLE my_table UPDATE age = 35 WHERE name = 'Alice';
此语句用于将my_table
表中name
为Alice
的记录的age
修改为35。
- 删除数据(同样,ClickHouse更擅长批量删除而非逐行删除)
ALTER TABLE my_table DELETE WHERE name = 'Bob';
此语句用于删除my_table
表中name
为Bob
的记录。
四、高级查询
- JOIN查询
SELECT t1.id, t1.name, t2.age
FROM my_table1 AS t1
JOIN my_table2 AS t2 ON t1.id = t2.id;
此语句用于将my_table1
和my_table2
两个表按id
列进行连接,并返回连接后的结果。
- 子查询
SELECT name FROM my_table1 WHERE id IN (SELECT id FROM my_table2 WHERE age > 30);
此语句用于查询my_table1
表中id
在my_table2
表中age
大于30的记录所对应的name
。
- 窗口函数
SELECT id, name, age, COUNT(*) OVER w
FROM my_table
WINDOW w AS (PARTITION BY age);
此语句使用了窗口函数COUNT(*)
来计算每个age
分组中的行数,并返回每个记录对应的行数。
以上是ClickHouse的一些常用语句和示例,希望能帮助你更好地理解和使用ClickHouse数据库。