├── 01introduction ├── getstart.md └── whatis.md ├── 02use ├── external.md ├── interface.md └── query.md ├── 03tableengine └── engine.md ├── README.md └── Reference.md /01introduction/getstart.md: -------------------------------------------------------------------------------- 1 | 新手上路 2 | ===================== 3 | 4 | ## 系统要求 5 | 6 | 这不是一个跨平台的系统,他需要Linux Ubuntu(12.04以后),x86_64架构,SSE4.2支持,测试SSE支持使用: 7 | 8 | grep -q sse4_2 /proc/cpuinfo && echo "SSE 4.2 supported" || echo "SSE 4.2 not supported" 9 | 10 | 我们建议使用Ubuntu Trusty 或Ubuntu Xenial或Ubuntu Precise,终端必须使用UTF-8编码(Ubuntu默认如此) 11 | 12 | ## 安装 13 | 14 | 对于测试和开发,可以在单机上安装 15 | 16 | ### 使用包安装 17 | 18 | In /etc/apt/sources.list (or in a separate /etc/apt/sources.list.d/clickhouse.list file), add the repository: 19 | 20 | On Ubuntu Trusty (14.04): 21 | 22 | deb http://repo.yandex.ru/clickhouse/trusty stable main 23 | For other Ubuntu versions, replace trusty to xenial or precise. 24 | 25 | Then run: 26 | 27 | sudo apt-key adv --keyserver keyserver.ubuntu.com --recv E0C56BD4 # optional 28 | sudo apt-get update 29 | sudo apt-get install clickhouse-client clickhouse-server-common 30 | 31 | 也可以自己下载安装: 32 | [http://repo.yandex.ru/clickhouse/trusty/pool/main/c/clickhouse/](http://repo.yandex.ru/clickhouse/trusty/pool/main/c/clickhouse/) 33 | [http://repo.yandex.ru/clickhouse/xenial/pool/main/c/clickhouse/](http://repo.yandex.ru/clickhouse/xenial/pool/main/c/clickhouse/) 34 | [http://repo.yandex.ru/clickhouse/precise/pool/main/c/clickhouse/](http://repo.yandex.ru/clickhouse/precise/pool/main/c/clickhouse/) 35 | 36 | ClickHouse包含了读取限制的设置,在"users.xml"文件中,与"config.xml"相邻。 37 | 38 | 默认情况下,允许默认用户从任何位置免密码读取数据。查看"user/default/networks",更多信息查阅:[Configuration files](https://clickhouse.yandex/reference_en.html#Configuration files) 39 | 40 | ### 从代码安装 41 | 42 | 跟着链接中的教程进行编译:[build.md](https://github.com/yandex/ClickHouse/blob/master/doc/build.md) 43 | 44 | 你可以编译后安装,也可以不安装直接使用 45 | 46 | Client: src/dbms/src/Client/ 47 | Server: src/dbms/src/Server/ 48 | 49 | 在server中创建数据目录,例如: 50 | 51 | /opt/clickhouse/data/default/ 52 | /opt/clickhouse/metadata/default/ 53 | 54 | 目录的配置在server的配置文件中 55 | 56 | 使用chown设置相应用户权限。日志路径在server的配置文件中(src/dbms/src/Server/config.xml). 57 | 58 | ### 其他方式安装 59 | 60 | Docker镜像: [ https://hub.docker.com/r/yandex/clickhouse-server/]( https://hub.docker.com/r/yandex/clickhouse-server/) 61 | 62 | Gentoo overlay: [https://github.com/kmeaw/clickhouse-overlay](https://github.com/kmeaw/clickhouse-overlay) 63 | 64 | ##启动 65 | 66 | 启动命令(作为后台服务): 67 | 68 | sudo service clickhouse-server start 69 | 70 | 查看日志: 71 | 72 | /var/log/clickhouse-server/ 73 | 74 | 如果服务没有正常启动,查看配置文件: 75 | 76 | /etc/clickhouse-server/config.xml 77 | 78 | 也可以在前台启动: 79 | 80 | clickhouse-server --config-file=/etc/clickhouse-server/config.xml 81 | 82 | 日志将会输出到控制台,开发阶段会更方便。默认的配置文件是./config.xml,如果是可以不需要加--config-file 83 | 84 | 使用下面的命令来连接server: 85 | 86 | clickhouse-client 87 | 88 | 默认情况下会连接到localhost:9000,使用default用户,没有密码。也可以连接到远程的服务器: 89 | 90 | clickhouse-client --host=example.com 91 | 92 | 更多信息参考“[Command-line client](https://clickhouse.yandex/reference_en.html#Command-line client)” 93 | 94 | 检查系统是否已经启动: 95 | 96 | milovidov@milovidov-Latitude-E6320:~/work/metrica/src/dbms/src/Client$ ./clickhouse-client 97 | ClickHouse client version 0.0.18749. 98 | Connecting to localhost:9000. 99 | Connected to ClickHouse server version 0.0.18749. 100 | 101 | :) SELECT 1 102 | 103 | SELECT 1 104 | 105 | ┌─1─┐ 106 | │ 1 │ 107 | └───┘ 108 | 109 | 1 rows in set. Elapsed: 0.003 sec. 110 | 111 | :) 112 | 113 | 以上信息代表启动成功。 114 | ### 测试数据 115 | 如果不是Yandex员工, 可以使用公开的数据集:[这里](https://github.com/yandex/ClickHouse/tree/master/doc/example_datasets) 116 | 117 | ### 如果有问题 118 | 可以在Stackoverflow上提问 119 | 120 | 可以在[GoogleGroups](https://groups.google.com/group/clickhouse)里讨论 121 | 122 | 或发邮件至[clickhouse-feedback@yandex-team.com](mailto:clickhouse-feedback@yandex-team.com) 123 | -------------------------------------------------------------------------------- /01introduction/whatis.md: -------------------------------------------------------------------------------- 1 | 什么是ClickHouse 2 | =================== 3 | ClickHouse是一个列式的OLAP数据库管理系统 4 | 在通常的面向行的数据库中,数据是按照这样的顺序存储: 5 | 6 |
 
  7 | 5123456789123456789     1       Eurobasket - Greece - Bosnia and Herzegovina - example.com      1       2011-09-01 01:03:02     6274717   1294101174      11409   612345678912345678      0       33      6       http://www.example.com/basketball/team/123/match/456789.html http://www.example.com/basketball/team/123/match/987654.html       0       1366    768     32      10      3183      0       0       13      0\0     1       1       0       0                       2011142 -1      0               0       01321     613     660     2011-09-01 08:01:17     0       0       0       0       utf-8   1466    0       0       0       5678901234567890123               277789954       0       0       0       0       0
  8 | 5234985259563631958     0       Consulting, Tax assessment, Accounting, Law       1       2011-09-01 01:03:02     6320881   2111222333      213     6458937489576391093     0       3       2       http://www.example.ru/         0       800     600       16      10      2       153.1   0       0       10      63      1       1       0       0                       2111678 000       0       588     368     240     2011-09-01 01:03:17     4       0       60310   0       windows-1251    1466    0       000               778899001       0       0       0       0       0
  9 | ...
 10 | 
11 | 12 | 换句话说,一行的所有数据是连续存储的。MySQL,Postgres,SQL Server等都是典型的行式数据库。 13 | 在列式数据库中,数据是这样存储的: 14 |
 15 | WatchID:    5385521489354350662     5385521490329509958     5385521489953706054     5385521490476781638     5385521490583269446     5385521490218868806     5385521491437850694   5385521491090174022      5385521490792669254     5385521490420695110     5385521491532181574     5385521491559694406     5385521491459625030     5385521492275175494   5385521492781318214      5385521492710027334     5385521492955615302     5385521493708759110     5385521494506434630     5385521493104611398
 16 | JavaEnable: 1       0       1       0       0       0       1       0       1       1       1       1       1       1       0       1       0       0       1       1
 17 | Title:      Yandex  Announcements - Investor Relations - Yandex     Yandex — Contact us — Moscow    Yandex — Mission        Ru      Yandex — History — History of Yandex    Yandex Financial Releases - Investor Relations - Yandex Yandex — Locations      Yandex Board of Directors - Corporate Governance - Yandex       Yandex — Technologies
 18 | GoodEvent:  1       1       1       1       1       1       1       1       1       1       1       1       1       1       1       1       1       1       1       1
 19 | EventTime:  2016-05-18 05:19:20     2016-05-18 08:10:20     2016-05-18 07:38:00     2016-05-18 01:13:08     2016-05-18 00:04:06     2016-05-18 04:21:30     2016-05-18 00:34:16     2016-05-18 07:35:49     2016-05-18 11:41:59     2016-05-18 01:13:32
 20 | ...
 21 | 
22 | 23 | 这些例子仅仅是用来展示数据是怎样排布的。 24 | 不同列的数据时分开存储的,而同一列的数据存储在一起。典型的列式数据库有: Vertica, Paraccel (Actian Matrix) (Amazon Redshift), Sybase IQ, Exasol, Infobright, InfiniDB, MonetDB (VectorWise) (Actian Vector), LucidDB, SAP HANA, Google Dremel, Google PowerDrill, Druid, kdb+ 等。 25 | 不同的存储方式会适应不同的场景。数据获取的场景是指请求如何发出,频率如何,比例如何。每种请求有多少数据被读出,包括按行,按列,按字节数;读取和更新数据之间的关系如何,活跃的数据有多大以及本地性如何;是否使用了事务,以及是否独立;对于数据持久性的要求如何,每种请求的延迟和吞吐量的要求如何等等。 26 | 27 | 整个系统的负载越高,针对场景定制化系统就越重要,定制化程度也越高。没有任何系统是能够很好的使用完全不同的各种场景的。如果一个系统适应很宽泛的场景需求,在负载很高的情况下,要么对每种场景的处理都很差,要么只在其中一种场景下工作的很好。 28 | 29 | 在OLAP(Online Analytical Processing)的场景下,具备以下特征: 30 | 31 | - 绝大多数请求是读请求。 32 | - 数据是批量更新的(每次1000行以上),而不是单条更新,或者从来不更新。 33 | - 数据不会被修改。 34 | - 读的时候,一次会读很多行,不过仅包括少量的列。 35 | - 表很宽,也就是说有大量的列 36 | - 请求频率相对不高(每台机器每秒小于几百个请求) 37 | - 对一般的请求,50ms左右的延迟是允许的。 38 | - 单列的数据很小,通常是数字或短字符串(比如60字节的URL信息) 39 | - 处理单个请求的时候需要很大的吞吐量(每台机器每秒上十亿行) 40 | - 没有事务要求 41 | - 对数据一致性要求低 42 | - 对每个请求来讲有一个表特别大,其他涉及的表都很小。 43 | - 查询的结果比原始数据小得多,也就是说数据被过滤或聚合了。结果的数据量能够放入单台机器的内存中。 44 | 45 | 很容易看出来OLAP的场景和其他的流行的场景有很大不同(比如OLTP或KV读取的场景),因此完全没有必要去尝试使用OLTP数据库或KV数据库来处理分析类的请求。例如不要使用MongoDB或Elliptics做数据分析,相比于使用OLAP数据库性能会很差。 46 | 47 | 列式数据库会更适应OLAP的场景(至少1000倍的性能优势),有以下两个原因: 48 | 49 | 1. 从IO来讲: 50 | - 对于分析类的请求,每一行只有少量的几列需要读。对于列式数据库,你可以只读取你需要的数据,比如100列中你只需要5列,你就节省了20倍的IO。 51 | - 因为数据是按包读取的,更容易压缩,列式数据库能得到更好的压缩效果。 52 | - 因为IO数据量减少,系统的缓存能够缓存更多的数据。 53 | 54 | 例如,“统计每个广告平台的记录的数量”这个请求需要读取“广告平台ID“这个列,未压缩时占用了1个字节。如果大部分的流量不是来自广告平台,你可以期待获得十倍的压缩。当使用一个快速的压缩算法时,每秒压缩几个G的数据是可能的。也就是说单台机器可以每秒处理数十亿行数据。而这个速度也实际达到了。 55 | 2. 从CPU来讲:(这部分没看懂) 56 | 因为执行一条请求需要处理大量的行,将整个操作向量而不是单独的行进行分发会更合理,或者实现一个请求引擎来避免分发的开销。如果不这样,对于任何half-decent disk subsystem,请求解释器将会占满CPU。Since executing a query requires processing a large number of rows, it helps to dispatch all operations for entire vectors instead of for separate rows, or to implement the query engine so that there is almost no dispatching cost. If you don't do this, with any half-decent disk subsystem, the query interpreter inevitably stalls the CPU. 57 | 58 | 因此将数据按列存储,并在可能的时候按列处理是很重要的。 59 | 60 | 有两种方法可以做到这一点: 61 | 62 | 1. 一个vector engine. 所有的操作为向量操作而写的,而不是单独的数据。这意味着你不需要经常调用操作,分发的开销就可以忽略不计。操作的代码包括了优化过的内部循环。A vector engine. All operations are written for vectors, instead of for separate values. This means you don't need to call operations very often, and dispatching costs are negligible. Operation code contains an optimized internal cycle. 63 | 2. 代码生成。为请求而生成的代码包括了简介的操作。Code generation. The code generated for the query has all the indirect calls in it. 64 | 65 | 在不同的数据库中并没有实现这些方法,因为对于简单的请求没有太大意义。然而也有例外,MemSQL使用了代码生成来降低处理SQL请求时的延迟。(相反,对于分析类的数据库更需要优化吞吐量而不是延迟) 66 | 67 | 注意一点,为了提高CPU的效率,查询语句必须是声明式的(SQL或MDX),或至少是向量式的,请求中应该只包含隐式循环,方便优化。 68 | 69 | 70 | # ClickHouse独有的特性 71 | 72 | 1. 真正面向列的数据库管理系统 73 | 2. 数据压缩 74 | 3. 磁盘存储数据 75 | 4. 多核上的并行处理 76 | 5. 多机的分布式处理 77 | 6. SQL支持 78 | 7. 向量引擎 79 | 8. 实时数据更新 80 | 9. 索引 81 | 10. 适应在线请求 82 | 11. 非精确计算的支持 83 | 12. 支持嵌套的数据结构,支持数组作为数据类型 84 | 13. 支持查询复杂度限制,包括限额 85 | 14. 数据多副本和数据完整性支持 86 | 87 | 下面是详细的信息: 88 | 89 | 1. 真正面向列的数据库管理系统 90 | 在真正面向列的数据库系统中,内容中没有任何”垃圾“数据。例如,固定长度的数据必须是支持的,避免存储一个长度信息。十亿个Uint8类型的数据必须实际占用大概1G未压缩的存储空间,不然会大大影响CPU的使用。保证数据未压缩情况下紧密的存储是很重要的,因为解压缩的效率也很大程度上决定于压缩前数据的大小。 91 | 92 | 有一些系统能够把列单独的存储,但是因为他们为其他场景而优化,因此无法高效的处理分析请求,这样就没有多少意义。比如HBase,BigTable,Cassandra,HyperTable等,在这些系统里,你只能达到几十万的吞吐量,而不是几亿。 93 | 94 | 同时要注意,,ClickHouse是一个数据库管理系统,而不是简单的一个数据库,它能够实时的在线创建表和创建数据库,加载数据,执行查询,而不需要重新配置或重启server。 95 | 96 | 2. 数据压缩 97 | 一些面向列的数据库存储系统(比如InfiniDB CE和MonetDB)并不压缩数据。但是数据压缩的确能够提高性能。 98 | 99 | 3. 磁盘存储 100 | 101 | 许多面向列的数据库比如(SAP HANA和Google PowerDrill)只能在内存中工作,但是即使使用几千台机器,内存的总量相比于我么要处理的数据量也太小了。 102 | 103 | 4. 多核并行处理 104 | 105 | 大型请求自然的进行并行处理。 106 | 107 | 5. 多机分布式处理 108 | 109 | 前面列举的列式的数据库几乎没有分布式处理的支持。 110 | 111 | 在ClickHouse中,数据会被分为不同的分片,每一个分片都是一组副本用来防止失败。数据在所有分片中并行处理,并且对用户透明。 112 | 113 | 6. SQL支持 114 | 115 | 如果你对标准的SQL不熟悉,我们无法讨论SQL支持的问题 116 | 117 | NULLs不支持。所有的函数都有不同的名字。然而有一个基于SQL的声明式查询语言,在大部分情况下和SQL没有区别。 118 | 119 | JOINs是支持的,子查询在FROM,IN,JOIN中支持,标量子查询支持。 120 | 121 | Correllated子查询不支持 122 | 123 | 7. 向量引擎 124 | 125 | 数据不仅仅是按列存储的,还是按向量来处理的,向量代表一部分列,这能够帮助我们获得更高的CPU表现。 126 | 127 | 8. 实时数据更新 128 | 129 | ClickHouse支持主键表。为了支持主键上的区域查询,数据使用merge tree增量存储。因此数据可以连续的添加到表中,而不会锁住查询。 130 | 131 | 9. 索引 132 | 133 | 拥有主键索引有助于帮助特殊的客户端(比如Metrica Counter)快速读取一段特定时间的数据,延迟在几十ms之内。 134 | 135 | 10. 适应在线请求 136 | 137 | 这个特性让我们可以把它作为web接口的后端。低延迟意味着请求能够在页面加载过程中在线的处理。 138 | 139 | 11. 非精确计算的支持 140 | 141 | 1. 系统包含了各种聚合函数用来估算数据量,中位数,分位数等。 142 | 2. 允许在一部分抽样数据上执行查询来得到估算结果,在这种情况下从磁盘加载的数据会更少。 143 | 3. 允许随机的在有限的数据中执行聚合函数,而不是全部的数据。当数据以某种分布存储时,者能够提供一个合理的计算结果,而使用更少的资源。 144 | 145 | 12. 支持嵌套的数据结构,支持数组作为数据类型 146 | 13. 支持查询复杂度限制,包括限额 147 | 14. 数据多副本和数据完整性支持 148 | 149 | 使用异步的多主复制。当数据写入到任何一个可用的副本时,会自动复制到剩余的副本中,系统保证在不同的副本中保持相同的数据。数据在发生错误时会自动恢复,或在复杂的情况下手动恢复。更多的信息请参考“数据副本”章节 150 | 151 | # ClickHouse可能是劣势的特性 152 | 153 | 1. 没有事务支持 154 | 2. 对聚合任务,查询的结果必须能够放入一台机器的内存中。当然原始数据可能会非常大。 155 | 3. 缺乏完美的UPDATE/DELETE实现 156 | 157 | # Yandex.Metrica 任务 158 | 159 | 我们需要基于点击和会话信息获取定制化的报告,并按照用户定制化的分段。生成报告的数据是实时更新的,并且请求必须实时获得结果。我们必须有能力为任何时间段生成报表数据。复杂的聚合运算很重要,比如独立的访客数据统计等。 160 | 161 | 2014年4月的数据,Yandex.Metrica大约每天收录120亿条数据。所有的数据必须被存储下来。单条请求需要在几秒钟内扫描数亿行数据,或在几百毫秒内扫描几百万行数据。 162 | 163 | ## 是否聚合数据 164 | 165 | 现在有一个很流行的观点,就是为了高效的计算统计数据,你必须把数据聚合,来减少数据的总量。然而这是一个非常受限的解决方案,因为以下原因: 166 | 167 | - 你必须使用一个预定义好的报告形式,而用户无法定制报告 168 | - 当聚合相当大数据量的key的时候,结果集并没有太小,聚合是无效的 169 | - 对于大量的报告形式来讲,有太多的聚合类型(组合爆炸) 170 | - 当使用可能性特别多的字段(比如URL)来进行聚合时,结果并没有减少很多(小于两倍),因此聚合后的数据量可能是增大而不是减小。 171 | - 用户不会查看我们计算出的所有报表,因此大部分的计算是无用的 172 | - 在进行各种聚合的时候,数据的逻辑完整性难以保证 173 | 174 | 如果我们不把数据进行聚合,而是针对原始数据进行计算,有可能会减少计算的总量。当然,预先聚合数据能够把相当大的一部分工作量放在离线完成,相对更加平稳,而在线计算需要计算的越快越好,因为用户正在等待结果。 175 | 176 | Yandex.Metrica有一个针对聚合数据的特殊系统叫做Metrage,用来生成大部分的报表工作。从2009年开始,Yandex.Metrica也用了一个特殊的OLAP数据库来处理非聚合数据,叫做OLAPServer。它在非聚合数据上工作的很好,但是有很多的限制,以至于很多需要的报表无法生成。包括缺少数据类型的支持(仅支持数字),无法实时增量的添加数据(只能每天覆盖写入)。OLAPServer不是一个数据库管理系统,而仅仅是一个特异化的数据库。为了解决OLAPServer的限制,以及解决在非聚合数据上生成报表的需求,我们开发了ClickHouse。 177 | 178 | # 在Yandex.Metrica和其他Yandex服务中的应用 179 | 180 | ClickHouse在Yandex.Metrica中有多种用途,其中最主要的任务时使用非聚合的数据实时生成报表信息。它使用了374台服务器,存储了超过8万亿行数据。压缩后数据的总量,不考虑冗余和重复,大概有800TB,非压缩数据(使用tsv格式)的数据大概有7PB。 181 | 182 | ClickHouse也被用来: 183 | 184 | - 存储WebVisor数据 185 | - 处理中间数据 186 | - 生成分析后的总体报表 187 | - 运行用来调试Metrica引擎的查询 188 | - 分析API和用户接口产生的日志 189 | 190 | ClickHouse在Yandex中有至少十几个其他的应用,在search verticals, Market, Direct, business analytics, mobile development, AdFox, personal service等等。 191 | 192 | # 可能的类似系统 193 | 194 | 目前没有ClickHouse类似的系统,目前(2016年3月),没有其他任何一个开源免费的系统拥有上面列出的所有特性。然而这些特性对于Yandex.Metrica是十分重要的。 195 | 196 | # 可能有点愚蠢的问题 197 | 198 | 1. 为什么没有使用类似MapReduce的系统? 199 | 200 | MapReduce类型的系统是分布式的计算系统,reduce阶段使用分布式的排序完成的。不考虑这个因素,MapReduce和其他的一些系统比如YAMR,Hadoop,YT等很类似。 201 | 202 | 这些系统并不适合做在线查询,因为延迟太大,没有办法作为web服务的后端。这些系统也不适合在线更新数据。 203 | 204 | 如果操作的结果和所有中间结果能够放入单台服务器的内存中,对Reduce操作来讲,分布式排序并不是一个合适的方案,而这个前提往往是成立的。在这种情况下,最适合的reduce操作的方式是使用哈希表。一个MapReduce操作常见的优化就是使用内存中的哈希表来组合操作。这一步通常是由用户手动完成。分布式的排序操作是简单的MapReduce任务耗费过长时间的最主要原因。 205 | 206 | 类似的MapReduce系统能够在集群中运行任何代码。但对于OLAP的场景,声明式的查询语言会比代码更合适,因为能够更快的实施。比如Hadoop有Hive和Pig。还有其他系统比如 Cloudera Impala, Shark (depricated) and Spark SQL for Spark, Presto, Apache Drill. 207 | 208 | 但是,这些任务在这类系统中的表现相比于专门优化过的系统要差很多,而且高延迟决定了无法在web服务中使用。 209 | 210 | YT允许存储把列分组单独存储,但是YT不是一个真正面向列的存储系统,没有固定长度的数据类型,也没有向量引擎。在YT中,任务是使用流式的任意代码执行的,因此也无法针对性的进行更好的优化。在2014-2016年,YT在开发动态排序表”dynamic table sorting",加入了Merge Tree, 强类型,SQL类似的查询语言的支持。动态排序表并不适应OLAP类的任务,因为数据是按行存储的。查询语句还在孵化阶段,目前无法真正全力开发。YT的开发者正在考虑把动态排序表用于OLTP和KV存储的场景中。 211 | 212 | # 性能表现 213 | 214 | 根据内部测试的结果,ClickHouse在所有可比较的系统中,使用可比较的操作场景,表现出最佳的性能表现。包括对于长查询最高的吞吐量,对于短查询最低的延迟,测试数据[点这里](https://clickhouse.yandex/benchmark.html) 215 | 216 | ### 单个大型查询的吞吐率 217 | 218 | 吞吐率使用每秒查询的行数或MB数来衡量。如果数据放在page cache中,不太复杂的查询可以让数据以单台服务器每秒2-10GB的速度进行处理,特别简单的情况可能可以达到30GB。如果数据没有在page cache中,速度取决于磁盘和数据压缩率。比如磁盘读取速度400MB/s,数据压缩率为3,就能够处理每秒1.2GB。要得到按行计算的吞吐率,就把这个除以使用的所有列的字节数之和。比如共10字节的情况下,速度就能到1到2亿行每秒。这个处理速度在分布式处理中几乎线性增长,只要聚合和排序后的结果中行数不要太大。(因为结果在单机内存中?) 219 | 220 | ### 处理短查询的延迟 221 | 222 | 如果查询使用了主键,并且没有选择太多行来处理(比如数十万行),也没有使用太多列,我们可以预期延迟在50ms左右,前提是数据在page cache中。否则,延迟主要由寻道次数决定。如果使用了rotating drives,系统也没有过载,延迟大概为寻道时间(10ms) * 列数 * 数据分片数。 223 | 224 | ### 处理大量短查询的吞吐率 225 | 226 | 在同样的场景下,ClickHouse单机可以处理数百个短查询,,由于这个量级的查询不是典型的分析类数据库的场景,我们通常建议不超过每秒100个请求。 227 | 228 | ### 数据插入的性能 229 | 230 | 我们建议单次插入数据的包不小于1000行,或每秒不超过一个请求。我们使用MergeTree来插入数据,插入速度在50-200MB每秒。如果插入的行在1K左右大小,大概能够做到5w-20w行每秒。如果单行很小,性能会更好(在 Yandex Banner System 中大概50w行每秒,在Graphite数据中超过100w行每秒),要提高性能,可以并行使用多个INSERT语句,性能能够线性提升。 231 | 232 | -------------------------------------------------------------------------------- /02use/external.md: -------------------------------------------------------------------------------- 1 | 查询时使用外部数据 2 | ================ 3 | 4 | 5 | -------------------------------------------------------------------------------- /02use/interface.md: -------------------------------------------------------------------------------- 1 | 接口 2 | ================== 3 | 要测试系统的负载能力,下载数据到表中,手动查询,可以使用clickhouse-client客户端程序 4 | 5 | ##HTTP接口 6 | 7 | HTTP接口让你能够使用任何语言与ClickHouse交互,我们使用JAVA,Perl甚至shell脚本进行工作。在其他部门还有Python,Go语言在使用HTTP接口,相比于原生接口,http接口限制较多,但是兼容性比较好。 8 | 9 | 默认情况下,clickhouse-server的HTTP端口在8123 10 | 11 | 如果你发送GET /请求,会返回字符串"Ok\n",这可以用来做健康检查 12 | 13 | $ curl 'http://localhost:8123/' 14 | Ok. 15 | 16 | query可以作为URL参数传入,使用GET或POST,或把请求的开头部分放在参数,剩余部分放在POST的内容中。成功情况下会返回200,body中是结果,如果发生错误,会收到500,body是错误原因。 17 | 18 | 使用GET方法时,'readonly'被设置,请求无法修改数据,POST则可以。示例: 19 | 20 | $ curl 'http://localhost:8123/?query=SELECT%201' 21 | 1 22 | 23 | $ wget -O- -q 'http://localhost:8123/?query=SELECT 1' 24 | 1 25 | 26 | $ GET 'http://localhost:8123/?query=SELECT 1' 27 | 1 28 | 29 | $ echo -ne 'GET /?query=SELECT%201 HTTP/1.0\r\n\r\n' | nc localhost 8123 30 | HTTP/1.0 200 OK 31 | Connection: Close 32 | Date: Fri, 16 Nov 2012 19:21:50 GMT 33 | 34 | 1 35 | 36 | 可以看到curl并不是很方便,因为空格需要URL编码。尽管wget处理好了,我们还是不建议用wget,因为当使用了keep-alive和Transfer-Encoding: chunked,它工作的有问题。 37 | 38 | $ echo 'SELECT 1' | curl 'http://localhost:8123/' --data-binary @- 39 | 1 40 | 41 | $ echo 'SELECT 1' | curl 'http://localhost:8123/?query=' --data-binary @- 42 | 1 43 | 44 | $ echo '1' | curl 'http://localhost:8123/?query=SELECT' --data-binary @- 45 | 1 46 | 47 | 当把请求的部分内容放在body时,两部分之间有回车 48 | 49 |
 50 | $ echo 'ECT 1' | curl 'http://localhost:8123/?query=SEL' --data-binary @-
 51 | Code: 59, e.displayText() = DB::Exception: Syntax error: failed at position 0: SEL
 52 | ECT 1
 53 | , expected One of: SHOW TABLES, SHOW DATABASES, SELECT, INSERT, CREATE, ATTACH, RENAME, DROP, DETACH, USE, SET, OPTIMIZE., e.what() = DB::Exception
 54 | 
55 | 56 | 默认情况下,返回的数据使用tab分割,可以使用FORMAT子句来指定格式: 57 | 58 |
 59 | $ echo 'SELECT 1 FORMAT Pretty' | curl 'http://localhost:8123/?' --data-binary @-
 60 | ┏━━━┓
 61 | ┃ 1 ┃
 62 | ┡━━━┩
 63 | │ 1 │
 64 | └───┘
 65 | 
66 | 67 | 对于INSERT操作,POST是必须的,你可以把请求的前半部分放在URL参数中,数据放在body中。数据可以是MYSQL dump出的tab分割的文件。在这种情况,INSERT可以替代LOAD DATA LOCAL INFILE from MySQL 68 | 69 | 例如: 70 | 71 | 建表: 72 | 73 | echo 'CREATE TABLE t (a UInt8) ENGINE = Memory' | POST 'http://localhost:8123/' 74 | 75 | 插入: 76 | 77 | echo 'INSERT INTO t VALUES (1),(2),(3)' | POST 'http://localhost:8123/' 78 | 79 | 数据语句分开: 80 | 81 | echo '(4),(5),(6)' | POST 'http://localhost:8123/?query=INSERT INTO t VALUES' 82 | 83 | 你可以指定任何数据格式,Values的格式和INSERT INTO t VALUES的时候一样 84 | 85 | echo '(7),(8),(9)' | POST 'http://localhost:8123/?query=INSERT INTO t FORMAT Values' 86 | 87 | 要插入tab分割的dump文件,指定: 88 | 89 | echo -ne '10\n11\n12\n' | POST 'http://localhost:8123/?query=INSERT INTO t FORMAT TabSeparated' 90 | 91 | 读出数据,数据是乱序的因为并行插入的: 92 | 93 |
 94 | $ GET 'http://localhost:8123/?query=SELECT a FROM t'
 95 | 7
 96 | 8
 97 | 9
 98 | 10
 99 | 11
100 | 12
101 | 1
102 | 2
103 | 3
104 | 4
105 | 5
106 | 6
107 | 
108 | 109 | 删除表 110 | 111 | POST 'http://localhost:8123/?query=DROP TABLE t' 112 | 113 | 请求成功时返回body为空 114 | 115 | 传输数据时你可以压缩数据,压缩使用的非标准格式,你需要使用一个特殊的压缩程序来工作(sudo apt-get install compressor-metrika-yandex) 116 | 117 | 如果你在URL中指定'compress=1', 服务器会把返回给你的数据做压缩。 118 | 119 | 如果你指定'decompress=1',服务器会解压POST中收到的数据。 120 | 121 | 这可以用来传输大量数据时降低网络流量,或者创建直接被压缩的dumps 122 | 123 | 使用database参数可以指定使用的数据库 124 | 125 |
126 | $ echo 'SELECT number FROM numbers LIMIT 10' | curl 'http://localhost:8123/?database=system' --data-binary @-
127 | 0
128 | 1
129 | 2
130 | 3
131 | 4
132 | 5
133 | 6
134 | 7
135 | 8
136 | 9
137 | 
138 | 139 | 默认情况下,请求使用默认的数据库,默认的数据库叫做‘default’,你可以使用表名前加数据库名和点的方式指定使用哪个数据库。 140 | 141 | 有两种方式可以配置用户名密码: 142 | 143 | echo 'SELECT 1' | curl 'http://user:password@localhost:8123/' -d @- 144 | echo 'SELECT 1' | curl 'http://localhost:8123/?user=user&password=password' -d @- 145 | 146 | 如果没有指定用户名,默认为'default',如果没有指定密码,默认为空。 147 | 148 | 你还可以在URL参数中设置各种参数,或者在配置文件中修改: 149 | 150 | http://localhost:8123/?profile=web&max_rows_to_read=1000000000&query=SELECT+1 151 | 152 | 更多信息参考"[Settings](https://clickhouse.yandex/reference_en.html#Settings)"章节。 153 | 154 |
155 | $ echo 'SELECT number FROM system.numbers LIMIT 10' | curl 'http://localhost:8123/?' --data-binary @-
156 | 0
157 | 1
158 | 2
159 | 3
160 | 4
161 | 5
162 | 6
163 | 7
164 | 8
165 | 9
166 | 
167 | 168 | 要看其他参数的信息,参考"[SET](https://clickhouse.yandex/reference_en.html#SET)"章节 169 | 170 | 和原生接口不同,HTTP接口不支持会话,会话设置等概念,也不允许打断一个请求(少部分情况下可以),不显示查询进度。解析数据格式等工作是在服务端进行,网络利用效率可能不高。 171 | 172 | 可选的参数:'query_id',可以穿入任何字符串作为queryID,更多信息:"[Settings, replace\_running\_query](https://clickhouse.yandex/reference_en.html#replace_running_query)" 173 | 174 | 可选参数:'quota_key',传入配额信息,具体参考:[Quotas](https://clickhouse.yandex/reference_en.html#Quotas) 175 | 176 | HTTP接口允许传入额外的数据(外部临时表),更多信息参考:[External data for query processing](https://clickhouse.yandex/reference_en.html#External data for query processing),[查询时使用外部数据](https://github.com/sparkthu/clickhouse-doc-cn/blob/master/02use/external.md) 177 | 178 | ## JDBC 驱动 179 | 有一个官方的JDBC驱动:[这里](https://github.com/yandex/clickhouse-jdbc) 180 | 181 | ## 第三方客户端 182 | 包括[Python](https://github.com/Infinidat/infi.clickhouse_orm), PHP([1](https://github.com/8bitov/clickhouse-php-client),[2](https://github.com/SevaCode/PhpClickHouseClient),[3](https://github.com/smi2/phpClickHouse)),[Go](https://github.com/roistat/go-clickhouse), Node.js([1](https://github.com/TimonKK/clickhouse), [2](https://github.com/apla/node-clickhouse), [Perl](https://github.com/elcamlost/perl-DBD-ClickHouse). 183 | 184 | 所有的库都没有经过官方测试,排序是随机的。 185 | 186 | ## 第三方GUI 187 | 有一个简单的WebUI:[SMI2](https://github.com/smi2/clickhouse-frontend) 188 | 189 | ## 原生接口(TCP) 190 | 原生接口是用来在clickhouse-client的命令行工具和服务器之间进行分布式查询的交互的,同样可以用来做C++程序接口。下面将仅介绍命令行工具: 191 | 192 | ### 命令行工具 193 | 194 |
195 | $ clickhouse-client
196 | ClickHouse client version 0.0.26176.
197 | Connecting to localhost:9000.
198 | Connected to ClickHouse server version 0.0.26176.
199 | 
200 | :) SELECT 1
201 | 
202 | 203 | clickhouse-client程序可用的参数如下: 204 | --host, -h - server name, by default - 'localhost'. 205 | IPv4或IPv6均可 206 | 207 | --port - The port to connect to, by default - '9000'. 208 | 注意HTTP端口和TCP端口不同,这里要使用TCP端口 209 | 210 | --user, -u - The username, by default - 'default'. 211 | 212 | --password - The password, by default - empty string. 213 | 214 | --query, -q - Query to process when using non-interactive mode. 215 | 非交互模式下使用 216 | 217 | --database, -d - Select the current default database, by default - the current DB from the server settings (by default, the 'default' DB).要使用的DB名称 218 | 219 | --multiline, -m - If specified, allow multiline queries (do not send request on Enter).如果指定了,则允许多行请求,回车时不发送请求 220 | 221 | --multiquery, -n - If specified, allow processing multiple queries separated by semicolons. 222 | Only works in non-interactive mode.如果有,可以执行分号连接的多个请求,只能在非交互模式下使用 223 | 224 | --format, -f - Use the specified default format to output the result.指定返回数据的格式 225 | 226 | --vertical, -E - If specified, use the Vertical format by default to output the result. This is the same as '--format=Vertical'. In this format, each value is printed on a separate line, which is helpful when displaying wide tables. 纵向展示,在展示宽表时很有用 227 | 228 | --time, -t - If specified, print the query execution time to 'stderr' in non-interactive mode.在非交互模式下,将执行时间输出到stderr 229 | 230 | --stacktrace - If specified, also prints the stack trace if an exception occurs.指定后,如果发生异常,打印出堆栈信息 231 | 232 | --config-file - Name of the configuration file that has additional settings or changed defaults for the settings listed above. 233 | 指定额外的配置文件的文件名或修改上述配置的默认值,默认情况下,会搜索以下路径: 234 | 235 | ./clickhouse-client.xml 236 | ~/./clickhouse-client/config.xml 237 | /etc/clickhouse-client/config.xml 238 | 239 | 只有第一个找到的文件会被使用 240 | 241 | 同样还可以指定任何查询时会用到的设置,例如clickhouse-client --max_threads=1,更多信息参考'[Settings](https://clickhouse.yandex/reference_en.html#Settings)'章节 242 | 243 | 客户端可以在交互模式或非交互模式(批量模式)下工作。要使用批量模式,指定'query'参数,或把数据传入stdin(会自动判断stdin不是terminal),或同时这两种。 244 | 类似HTTP接口,当使用'query'参数并把数据传入'stdin'中时,请求会在'query'参数和stdin中的内容之间插入一个换行,对于大型的INSERT操作很有用。 245 | 246 | 插入数据的示例: 247 |
248 |  echo -ne "1, 'some text', '2016-08-14 00:00:00'\n2, 'some more text', '2016-08-14 00:00:01'" | clickhouse-client --database=test --query="INSERT INTO test FORMAT CSV";
249 | 
250 | cat <<_EOF | clickhouse-client --database=test --query="INSERT INTO test FORMAT CSV";
251 | 3, 'some text', '2016-08-14 00:00:00'
252 | 4, 'some more text', '2016-08-14 00:00:01'
253 | _EOF
254 | 
255 | cat file.csv | clickhouse-client --database=test --query="INSERT INTO test FORMAT CSV";
256 | 
257 | 258 | 在批量模式下,默认的数据格式是tab分割的,可以在query中指定格式 259 | 260 | 默认情况下一个语句只能执行一条请求,要执行多条,可以使用multiquery参数,除了INSERT外的查询都可以。多条的查询请求会不加分隔的连续输出。 261 | 262 | 类似的,要执行大量的查询时,你也可以为每条请求执行一次clickhouse-client,每次程序启动大概需要花费几十ms 263 | 264 | 在交互模式下,会有一个命令行供输入请求。 265 | 266 | 默认情况下multiline未开启,此时要执行请求,输入Enter。末尾不需要分号。此时可以通过Enter前输入反斜杠 \ 来输入多行请求。 267 | 268 | 在multiline开启后,要执行请求输入分号并回车。如果行尾没有分号,会被要求继续输入下一行。任何分号后的内容会被忽略。 269 | 270 | 通过\G替代分号或加在分号后,可以使用纵向输出的格式,用来和MySQL CLI兼容。 271 | 272 | 命令行基于readline(以及history,或libedit),也就是说可以使用常见的快捷键,并保持输入历史,历史文件在/.clickhouse-client-history(可能是~/...?) 273 | 274 | 默认情况下,输出格式为PrettyCompact,可以通过FORMAT来改变。 275 | 276 | 要退出客户端,可以使用Ctrl+D,Ctrl+C或以下输入: 277 | "exit", "quit", "logout", "учше", "йгше", "дщпщге", "exit;", "quit;", "logout;", "учшеж", "йгшеж", "дщпщгеж", "q", "й", "\q", "\Q", ":q", "\й", "\Й", "Жй" 278 | 279 | 当执行请求时,客户端会显示: 280 | 1. 进度信息,不超过每秒10次的刷新。对于快速的请求,可能没有时间显示 281 | 2. 解析后的格式化请求信息,用来调试 282 | 3. 指定格式的结果 283 | 4. 结果的行数,花费的时间,平均处理速度 284 | 285 | 要取消一个长时间的查询,使用Ctrl+C,然而需要等待一小段时间来打断改请求。在某些环节无法取消请求。如果等不及再次按下了Ctrl+C,客户端会退出。 286 | 287 | 客户端工具也可以传输额外的信息(外部临时表),更多信息参考:"[External data for request processing](https://clickhouse.yandex/reference_en.html#External data for query processing),[查询时使用外部数据](https://github.com/sparkthu/clickhouse-doc-cn/blob/master/02use/external.md)" 288 | -------------------------------------------------------------------------------- /02use/query.md: -------------------------------------------------------------------------------- 1 | 查询语言 2 | ============= 3 | 4 | #语法 5 | ClickHouse中有两种解析器,一个是全SQL解析器(递归下降分析器),一个数据格式解析器(快速流解析器),第二个仅用于INSERT语句。INsert语句使用了两种解析器 6 | 7 | INSERT INTO t VALUES (1, 'Hello, world'), (2, 'abc'), (3, 'def') 8 | 9 | 10 | INSERT INTO t VALUES部分使用全解析器,数据部分使用流解析器。 11 | 12 | 数据可以使用任何格式,接收到请求后,服务器将不超过’max_query_size’的部分放在RAM中解析(默认为1MB),剩余部分使用流解析器。因此和MySQL一样,对于很大的INSERT语句也不会有任何问题。 13 | 14 | 在INSERT语句中使用使用Values格式时,数据看上去可以和SELECT语句的表达式进行相同的解析,然而并不是。Values的格式限制多很多。 15 | 接下来我们要讲全解析器,对于解析的更多信息,参考Formats章节(https://clickhouse.yandex/reference_en.html#Formats) 16 | 。 17 | 18 | ###空白 19 | 20 | 语法结构之间和前后可以有任意数量的空白字符,包括空格,tab,换行,CR,form feed. 21 | 22 | ###注释 23 | 24 | SQL和C风格的注释都是允许的,SQL风格: 从—到行尾,—之后的空格可以省略 25 | C风格:从/*到*/,允许多行。 26 | 27 | ###关键字 28 | 29 | 类似SELECT这种的关键字是大小写不敏感的,但和标准SQL不同,其他任何词(包括列名,方法名等)都是大小写敏感的。关键字使用的单词不是保留的,可以在其他命名中使用,他们仅在合适的上下文中被解析为关键字。 30 | 31 | ###标识符 32 | 标识符(列名,方法名,数据类型)可以使用引号,也可以不用。 33 | 34 | 不使用引号的标识符首字符必须是拉丁字符或下划线,后面可以是拉丁字符,下划线或数字,也就是满足这个正则表达式:
^[a-zA-Z_][0-9a-zA-Z_]*$
35 | 例如这些是合法的:x, _1, X_y__Z123_ 36 | 37 | 引号中的标识符:和MySQL一样,使用`id`的形式,中间可以使用任何字符,特殊字符(比如`符号本身)可以使用反斜杠转义。转义规则和字符串常量相同。我们推荐使用无需括号的标识符。 38 | 39 | ###常量 40 | 41 | 包括数字常量,字符串常量和混合常量 42 | 43 | ####数字常量 44 | 45 | 数字常量的解析方式顺序: 46 | 47 | 1. 首先使用64位有符号数,使用'strtoull'方法 48 | 2. 如果不成功,使用64位无符号数,'strtoll'方法 49 | 3. 还不成功,使用浮点数,'strtod'方法 50 | 4. 再不成功则返回错误 51 | 52 | 响应数据则使用结果能够适应的最小类型,例如1使用Uint8,而256使用Uint16,更多信息参考[Data types](https://clickhouse.yandex/reference_en.html#Data types) 53 | 54 | ####字符串常量 55 | 56 | 字符串常量必须使用单引号包裹。其中的特殊字符可以使用反斜杠转义。这些转义字符有特殊含义: 57 | 58 | \b, \f, \r, \n, \t, \0, \a, \v, \xHH 59 | 60 | 其他情况下,对于任意字符c,\c被转成字符c本身,也就是\\表示\,\'表示'字符。 61 | 62 | ####混合常量 63 | 64 | 数组中支持混合常量,例如:[1, 2, 3],还有元组,例如:(1, 'Hello, world!', 2) 65 | 66 | 事实上他们并不是常量,而是使用了数组构造函数或元组构造函数的表达式。更多信息参考[Operators](https://clickhouse.yandex/reference_en.html#Operators2) 67 | 68 | 数组至少包含一个元素,元组至少包含两个元素。元组是专门用于匹配IN从句中的SELECT请求结果的。通常情况下不能被存储到数据库中,(Memory表除外) 69 | 70 | ### 函数 71 | 72 | 函数写起来就是一个标识符,后面跟着一串(可能为空)的用小括号括起来的参数。和标准的SQL不同,即使是空的参数列表,括号也是必须的,比如now(). 73 | 74 | 函数分为普通函数和聚合函数。某些聚合函数可以跟两组参数,比如quantile(0.9)(x)。这种聚合函数被称为参数化函数,第一组参数就是参数化方法中的参数。对于没有带参数的聚合方法语法就和普通方法一样。 75 | 76 | ### 运算符 77 | 78 | 运算符在解析请求时会被转换为对应的函数,同时考虑运算符的优先级和结合性,例如 1+2+3+4被转换为 plus(plus(1, multiply(2, 3)), 4),更多内容请参考[Operators](https://clickhouse.yandex/reference_en.html#Operators2) 79 | 80 | ### 数据类型和表引擎 81 | 82 | 在CREATE语句中,数据类型和表引擎写法和标识符及函数一致。也就是说他们可能含有参数列表,也可能不含。更多信息参考[Data types](https://clickhouse.yandex/reference_en.html#Data types), "Table engines"以及"create" 83 | 84 | ### 别名 85 | 86 | 在SELECT请求中,表达式可以使用AS语法指定别名。表达式在AS左边,别名在AS右边。和标准SQL不同,别名不仅仅可以在表达式顶层指定,例如一下是合法的: 87 | 88 | SELECT (1 AS n) + 2, n 89 | 90 | 和标准SQL不同,别名可以用在任何语句,而不仅仅可以用在SELECT中。 91 | 92 | ###Asterisk 93 | 94 | ###表达式 95 | 96 | 表达式可以是函数,标识符,常量,运算符应用,包在括号中,放在子查询中,Asterisk中等。表达式可以包含别名。多个表达式使用逗号分隔。函数和运算符也可以使用表达式作为参数。 97 | 98 | # 请求 99 | ###建库 100 | 101 | CREATE DATABASE [IF NOT EXISTS] db_name 102 | 103 | 创建一个名为db_name的数据库,数据库仅仅是一系列表的目录。指定了 IF NOT EXISTS后,如果数据库已经存在,语句不会返回错误。 104 | 105 | ###建表 106 | 107 | 建表请求有多种格式 108 | 109 |
110 | CREATE [TEMPORARY] TABLE [IF NOT EXISTS] [db.]name
111 | (
112 |     name1 [type1] [DEFAULT|MATERIALIZED|ALIAS expr1],
113 |     name2 [type2] [DEFAULT|MATERIALIZED|ALIAS expr2],
114 |     ...
115 | ) ENGINE = engine
116 | 
117 | 118 | 在db数据库中简历一个name的表,如果没有指定db则使用当前数据库。结构是括号中指定的格式,引擎使用指定的引擎。表结构是所有列描述的列表。如果引擎支持索引,可以在引擎的参数中指定。 119 | 120 | 列描述最简单的情况是"name type",例如RegionID UInt32,也可以用表达式指定默认值,下面会有详细说明。 121 | 122 | CREATE [TEMPORARY] TABLE [IF NOT EXISTS] [db.]name AS [db2.]name2 [ENGINE = engine] 123 | 124 | 这个语句使用另一个表的结构建表,如果没有指定引擎,则会使用db2.name2的引擎。 125 | 126 | CREATE [TEMPORARY] TABLE [IF NOT EXISTS] [db.]name ENGINE = engine AS SELECT ... 127 | 128 | 上面的语句使用SELECT的结果作为结构建表,同时把SELECT结果的数据插入表中,引擎可以单独指定。 129 | 130 | 在所有上面的方法中,IF NOT EXISTS可以发挥作用。 131 | 132 | #### 默认值 133 | 134 | 列可以使用下面的语法指定一个默认值:DEFAULT expr, MATERIALIZED expr, ALIAS expr。 例如URLDomain String DEFAULT domain(URL) 135 | 136 | 如果没有指定默认值,对于数字会默认为0,字符串为空字符串,数组为空数组,日期为0000-00-00,时间为0000-00-00 00:00:00,不支持NULL 137 | 138 | 如果指定了默认值,类型可以省略,这种情况下使用默认值的类型,例如EventDate DEFAULT toDate(EventTime),EventDate的类型会被设置为Date。如果明确指定了类型,默认值的会被转换为目标类型。 139 | 例如Hits UInt32 DEFAULT 0 和 Hits UInt32 DEFAULT toUInt32(0)是完全相同的。 140 | 141 | 默认值的表达式中也可以使用表常量和其他列。建表和修改表结构时,会检查是否包含循环,INSERT语句中,会检查是否表达式中的所有列都已经传入了。 142 | 143 | "DEFAULT expr"表示普通的默认值,如果INSERT语句没有指定相应列,会自动使用表达式的值填充 144 | 145 | "MATERIALIZED expr"表示“物化列”,这样的列不允许在INSERT中指定,因为它必须计算得来。对于没有指定列的INSERT语句,这些列不会被考虑在内,使用SELECT *时,这些列也不会返回,因为将SELECT * 的结果使用不指定列的INSERT语句插入式很常见的行为,会引发问题。 146 | 147 | "ALIAS expr"是指别名,这样的列根本不会被存储起来。不能够使用INSERT插入数据,也不会包括在SELECT * 的返回值中,如果别名是在请求解析阶段展开的,可以使用在SELECT语句中。 148 | 149 | 当使用ALTER请求来添加新列时,这些列的老数据不会被写入。当读取的老数据没有这些新列的值时,默认情况下会在线计算。然而如果这些表达式使用了没有在SELECT语句中出现的列,这些列会被读取到,当然仅在需要的情况下。 150 | 151 | 如果向表中添加了新列,但是后来又修改了默认表达式,老数据的值将会更新。当运行后台合并操作时,原来没有值的列会在合并后被写入。 152 | 153 | 不允许为嵌套的数据结构设置默认值。 154 | 155 | ####临时表 156 | 157 | 如果指定了TEMPORARY,将会创建临时表,临时表的特征: 158 | 159 | - 连接结束或中断时,临时表会消失。 160 | - 临时表只能使用内存引擎,其它引擎不支持。 161 | - 临时表不能指定数据库,他们是在数据库外创建的 162 | - 如果临时表和普通表的名字相同,请求没有指定db的名字时,临时表会被使用 163 | - 在分布式处理中,一个请求用到的临时表会传给远端server。 164 | 165 | 通常情况下,临时表不是手动建立的,而是使用外部数据,或使用分布式的IN语句时被自动创建。 166 | -------------------------------------------------------------------------------- /03tableengine/engine.md: -------------------------------------------------------------------------------- 1 | ## ClickHouse 引擎 2 | 3 | Clickhouse 提供了丰富的存储引擎,存储引擎的类型决定了数据如何存放、如何做备份、如何被检索、是否使用索引。不同的存储引擎在数据写入/检索方面做平衡,以满足不同业务需求。 4 | 5 | Clickhouse 提供了十多种引擎,主要是用的是`MergeTree` 系表引擎。 6 | 7 | ### TinyLog 8 | 9 | 这是最简单的表引擎,它将数据存储在磁盘上。每列都存储在一个单独的压缩文件中。写入时,数据被附加到文件的末尾。该类型引擎不支持索引 这种引擎没有并发数据访问控制: 10 | 11 | - 同时对一张表进行读写操作,读操会错误 12 | - 同时在多个查询中进行写入操作,数据将被破坏 13 | 14 | 使用此表的典型方法是一次写入:只需要一次写入数据,然后根据需要多次读取它。查询在单个流处理中执行,换句话说该引擎适用于相对较小的表格(官方推荐建议一百万行以内)。如果你有很多小表,使用这个表引擎是很有意义的,因为他比Log Engines(另一个引擎下边会介绍)更简单(需要代开的文件更少)。当你有大量读写效率很低的小表时,而且在与另一个DBMS一起工作时已经被使用了,你可能会发现切换到使用TinyLog类型的表更容易。 在Yandex.Metrica中,TinyLog表用于小批量处理的中间数据。 15 | 16 | ``` 17 | CREATE TABLE test.tinyLog_test ( id String, name String) ENGINE = TinyLog 18 | 19 | insert into test.Log_test (id, name) values ('1', 'first'); 20 | ``` 21 | 22 | 找到数据目录({home}/clickhouse/data/data/test)数据在磁盘上的结构如下 23 | 24 | ``` 25 | [hc@dpnode04 test]$ tree -CL 5 ./tinyLog_test/ 26 | ./tinyLog_test/ 27 | ├── id.bin 28 | ├── name.bin 29 | └── sizes.json 30 | ``` 31 | 32 | a.bin 和 b.bin 是压缩过的对应的列的数据, sizes.json 中记录了每个 *.bin 文件的大小: 33 | 34 | ``` 35 | [hc@dpnode04 test]$ cat ./tinyLog_test/sizes.json 36 | {"yandex":{"id%2Ebin":{"size":"28"},"name%2Ebin":{"size":"32"}}} 37 | ``` 38 | 39 | ### Log 40 | 41 | Log引擎跟TinyLog引擎的区别是增加一个小的标记文件(*marks.mrk )* 留在列文件中。这些文件记录了每个数据块的偏移量。这种做的一个用处就是可以准确的切分读的范围,从而使并发读成为可能。但是,它是不能支持并发写的,一个写操作会阻塞其他读操作。 Log引擎不支持索引,同时因为有一个_marks.mrk 冗余数据,所以在写入数据时,一旦出现问题,这个表就报废了。同TinyLog差不多Log引擎使用的场景也是那种一次下入后面都是读取的场景,比如测试环境或者演示场景。 42 | 43 | ``` 44 | CREATE TABLE test.Log_test ( id String, name String) ENGINE = Log 45 | insert into test.Log_test (id, name) values ('1', 'abc'); 46 | insert into test.Log_test (id, name) values ('2', 'q'); 47 | insert into test.Log_test (id, name) values ('3', 'w'); 48 | insert into test.Log_test (id, name) values ('4', 'e'); 49 | insert into test.Log_test (id, name) values ('5', 'r'); 50 | ``` 51 | 52 | 数据({home}/clickhouse/data/data/test)在磁盘上的结构: 53 | 54 | ``` 55 | [hc@dpnode04 test]$ tree -CL 5 ./Log_test/ 56 | ./Log_test/ 57 | ├── id.bin 58 | ├── __marks.mrk 59 | ├── name.bin 60 | └── sizes.json 61 | ``` 62 | 63 | ### Memory 64 | 65 | Memory引擎 数据以未压缩的形式存储在RAM中。数据的存储方式与读取时的接收的格式完全相同。(在很多情况下,MergeTree引擎的性能几乎一样高)锁是短暂的:读写操作不会彼此阻塞。不支持索引。支持并发访问数据库。 简单的查询最快(超过10GB/s)服务器重启数据就会消失。 一般用到它的地方不多,除了用来测试,就是在需要非常高的性能,同时数据量又不太大(上限大概 1 亿行)的场景。 内存引擎由系统用于具有外部查询数据的临时表 ###Merge 66 | 67 | 一个工具引擎,本身不保存数据,只用于把指定库中的指定多个表链在一起。这样,读取操作可以并发执行,同时也可以利用原表的索引,但是,此引擎不支持写操作。 68 | 69 | 指定引擎的同时,需要指定要链接的库及表,库名可以使用一个表达式,表名可以使用正则表达式指定(只用表达的方式创建成功了,而且表结构得保持一致)。 70 | 71 | ``` 72 | create table test.me1 (id String, name String) ENGINE=TinyLog; 73 | create table test.me2 (id String, name String) ENGINE=TinyLog; 74 | create table test.me3 (id String, name String) ENGINE=TinyLog; 75 | 76 | insert into me1(id, name) values ('1', 'first'); 77 | insert into me2(id, name) values ('2', 'xxxx'); 78 | insert into me3(id, name) values ('12', 'i am in t3'); 79 | 80 | create table merge_test (id String, name String) ENGINE=Merge(currentDatabase(), 'me[1-9]\d*'); 81 | ``` 82 | 83 | 上面先建了 me1 , me2 , me3 ,三个表,然后用 Merge 引擎的merge_test表再把它们链接起来。 84 | 85 | 这样,查询的时候,就能同时取到三个表的数据了:![merge](https://note.youdao.com/yws/api/personal/file/3DC2D8C67EFB45E3AAD4985DDBDC87BC?method=download&shareKey=928852fb1bfa26246fabfbf84250bf9b) 86 | 87 | select 中, "_table" 这个列,是因为使用了 Merge 多出来的一个的一个 虚拟列 ,它表示原始数据的来源表,它不会出现在 show table 的结果当中,同时, select * 不会包含它。 88 | 89 | ### Distributed 90 | 91 | Distributed 引擎并不存储真实数据,而是来做分布式写入和查询,与其他引擎配合使用。比如:Distributed + MergeTree。并行执行查询操作。在查询期间,如果远程服务器上的表索引的话会被使用。数据不仅被读取,而且在远程服务器上进行部分处理(只要这是可能的)。例如,对于具有GROUP BY的查询,数据将在远程服务器上聚合,聚合函数的中间状态将发送到请求者服务器。然后数据将被进一步聚合。 92 | 93 | 示例: 94 | 95 | ``` 96 | Distributed(remote_group, database, table [, sharding_key]) 97 | eg. Distributed(logs, default, hits[, sharding_key]) 98 | ``` 99 | 100 | - *remote_group* 是配置文件(默认在 `/etc/clickhouse-server/config.xml` )中的 `remote_servers` 一节的配置信息。 101 | - *database* 是各服务器中的库名。可以使用返回字符串的常量表达式来代替数据库名称。例如:currentDatabase()。 102 | - *table* 是表名。 103 | - *sharding_key* 是一个 *寻址表达式* ,可以是一个列名,也可以是像 `rand()` 之类的函数调用,它与 `remote_servers` 中的 `weight` 共同作用,在写入时决定往哪个 *shard* 写。每个分片都可以在配置文件中定义一个weight 。默认情况下,weight 重等于1。数据以与shard weight成比例的量分布在shards中。例如,如果有两个碎片,第一个的weight 为9,而第二个的weight 为10,则第一个将发送9/19parts ,第二个将发送10/19。要判断一行数据发送到哪个shard ,通过分片表达式我们能得到一个数字,这个数字对总权重进行取余操作。余数命中哪个shard 的范围,这条数据就会在那个shard 上。例如,如果有两个碎片,第一个碎片的权重为9,其shard的范围是[0,9],第二个碎片的碎片权重为10,shard的范围[9,19]。假设该行sharding expression 为22 则公式为 22 对 总权重19 取 余得3 应该在第一个分片上。分片表达式可以是返回一个整数的来自常量和表中列的任何表达式。例如,您可以使用表达式rand()来随机分配数据,或使用'UserID'来分配剩余的用户ID(然后单个用户的数据将驻留在单个分片上,这简化了由用户运行IN和JOIN)。如果其中一列的分布不够均匀,则可以将其包装在散列函数中:intHash64(UserID)。划分的一个简单余数是分片的有限解决方案,并不总是合适的。它适用于大量数据(数十台服务器),但不适用于大量数据(数百台服务器或更多)。在后一种情况下,请使用主题区所需的分片方案,而不要使用分布式表中的条目。 104 | 105 | SELECT操作被发送到所有分片,并且无论数据如何分布在分片中(它们可以完全随机分发)都可以工作。当你添加一个新的分片时,你不必将旧数据传送给它。您可以用较重的权重编写新数据 - 数据将稍微不均匀分布,但查询将正确有效地工作。 106 | 107 | - host 远程服务器的地址。您可以使用域或IPv4或IPv6地址。如果指定域,则服务器在启动时发出DNS请求,并且只要服务器正在运行,结果就会被存储。如果DNS请求失败,服务器不会启动。如果您更改DNS记录,请重新启动服务器。 108 | - port 信使活动的TCP端口(配置中的'tcp_port',通常设置为9000)。不要将它与http_port混淆。 109 | - user 连接到远程服务器的用户的名称。默认值:default。该用户必须有权访问指定的服务器。 Access在users.xml文件中配置。有关更多信息,请参阅“Access rights”部分。 110 | - password 连接到远程服务器的密码(未加密)。默认值:空字符串 111 | 112 | 指定副本时,读取时将为每个分片选择一个可用副本。您可以配置算法以实现负载平衡(副本访问的首选项) - 请参阅“load_balancing”设置。如果与服务器的连接未建立,则会尝试连接一个短暂超时。如果连接失败,则将选择下一个副本,以此类推所有副本。如果连接尝试对所有副本都失败,则尝试将以相同的方式重复几次。这有助于恢复能力,但不提供完整的容错能力:远程服务器可能会接受连接,但可能无法工作,或工作效果不佳。 113 | 114 | 可以配置您想配置的集群数量(即:集群的个数没有限制),要查看您的群集,请使用'system.clusters'表 115 | 116 | Distributed引擎允许像本地服务器一样使用群集。但是,群集是不可扩展的:您必须将其配置写入服务器配置文件。 117 | 118 | 不支持查看其他Distributed表的Distributed 表(除非分布式表只有一个分片)。作为替代方法,使Distributed 表查看“final”表。Distributed 引擎要求将clusters写入配置文件。无需重新启动服务器即可更新配置文件中的群集。 119 | 120 | 如果您需要每次向未知的分片和副本集发送查询,则不需要创建分布式表 - 而是使用“remote”表函数。请参阅“Table functions”一节。 121 | 122 | - 有两种将数据写入群集的方法: 123 | 124 | 第一种,可以定义将哪些数据写入哪些服务器,并直接在每个分片上执行写入操作。也就是, 着眼于在分布式表中执行INSERT。这是最灵活的解决方案 - 您可以使用任何sharding scheme,由于所处领域的要求,这可能不是微不足道的。这也是最理想的解决方案,因为数据可以完全独立地写入不同的分片。(我理解的就是想往哪些服务器写哪些数据,直接通过分片去写入) 125 | 126 | 第二种,您可以在分布式表中执行INSERT。在这种情况下,表格将在服务器本身分配插入的数据。为了写入分布式表,它必须有一个sharding key(最后一个参数)。另外,如果只有一个分片,写入操作无需指定sharding key,因为它在这种情况下没有任何意义。 127 | 128 | 每个分片都可以在配置文件中定义'internal_replication'参数。 如果此参数设置为'true',则写入操作会选择第一个健康副本并向其写入数据。然后各个replica之间通过zookeeper自动同步数据,类似于replicated表的数据同步模式。如果分布式表“looks at”复制表,请使用此选项。简单来说,后台会自动创建数据备份。 如果它设置为'false'(默认),则数据将写入所有副本。实际上,这意味着分布式表本身复制数据。这比使用复制表更糟糕,因为副本的一致性未被检查,并且随着时间的推移,它们将包含稍微不同的数据。 129 | 130 | 在以下情况下,您应该关注分片方案: 131 | 132 | - 使用查询需要通过特定键连接数据(IN或JOIN)。如果数据被该键分割,则可以使用本地IN或JOIN而不是GLOBAL IN或GLOBAL JOIN,这样更有效。 133 | - 使用大量服务器(数百个或更多)以及大量小型查询(查询单个客户端 - 网站,广告商或合作伙伴)。为了使小型查询不影响整个群集,在单个分片上定位单个客户端的数据是有意义的。或者,正如我们在Yandex.Metrica中所做的那样,您可以设置双层分片:将整个群集划分为“layers”(层),其中layers可以由多个分片组成。单个客户端的数据位于单个layers上,但必要时可将碎片添加到layers中,并且数据随机分布在其中。为每个layer创建分布式表,并为全局查询创建一个共享分布式表。 134 | 135 | 数据是异步写入的。对于分布式表的INSERT,数据块只写入本地文件系统。数据尽快发送到后台的远程服务器。通过检查表目录中的文件列表(等待发送的数据):/ var / lib / clickhouse / data / database / table /来检查数据是否成功发送。 136 | 137 | 如果服务器在对分布式表进行INSERT后停止退出或暴力重启(例如,设备出现故障后),则插入的数据可能会丢失。如果在表目录中检测到损坏的数据部分,它将被转移到'broken'子目录并不再使用。 当启用max_parallel_replicas选项时,查询处理在单个分片中的所有副本上并行化。有关更多信息,请参阅“设置,max_parallel_replicas”部分。 138 | 139 | ### MergeTree 140 | 141 | MergeTree 是 ClickHouse 中最重要的引擎,并由 MergeTree 衍生出了一系列的引擎,统称 MergeTree 系引擎。MergeTree 引擎提供了工根据日期进行索引和根据主键进行索引。MergeTree 是ClickHouse最先进的表引擎,不要跟merge 引擎混淆。 142 | 143 | 使用这个引擎的形式如下: 144 | 145 | ``` 146 | MergeTree(EventDate, (CounterID, EventDate), 8192) 147 | eg. MergeTree(EventDate, intHash32(UserID), (CounterID, EventDate, intHash32(UserID)), 8192) 148 | ``` 149 | 150 | - `EventDate` 一个日期的列名。一个MergeTree 类型的表必须有一个包含date类型的列。必须是Date类型不允许是DateTime 151 | - `intHash32(UserID)` 采样表达式。来伪随机的对主键里的CounterID和EventDate进行打散。换句话说,当使用了SAMPLE子句时,您会为一部分用户获得均匀的伪随机数据样本。 152 | - `(CounterID, EventDate)` 主键组(里面除了列名,也支持表达式),也可以是一个表达式。 `8192` 主键索引的粒度。 这个表是由很多个part构成。每一个part 按照主键进行了排序,除此之外,每一个part含有一个最小日期和最大日期。当插入数据的时候,会创建一个新的sort part ,同时会在后台周期行的进行merge的过程,当merge的时候,很多个part会被选中,通常是一个最小的一些part,然后merge成为一个大的排序好的part。 简单来说,整个整个合并排序的过程是在数据插入表的时候进行的。这个merge会导致这个表总是由少量的排序好的part构成,而且这个merge 本身没有做特别多的工作。这些part 在进行合并的时候会有一个大小的阈值,所以不会有太长的merge过程。 对于每一个part会生成索引文件。这个索引文件储存了表里面每一个索引块数据的主键的value值,也就是这是这个part的小型索引 对于列来说,在每一个索引块的数据也写入了标记,从而让数据可以在明确的数值范围内被查找到 当读表里的数据时,select 查询会被转化为要使用那些索引。这些索引会被用在判断where 条件或者prewhere 条件中,来判断是否命中了这些索引区间。 因此能够快速查询一个或者多个主键范围的值,在下面的示例中,能够快速的查询一个明确的counter,执行范围的日期区间里的一个明确的counter,各种counter的集合等。 153 | 154 | 总结一下上面说的: 155 | 156 | ##### 特性 157 | 158 | - 支持主键索和日期索引。 159 | - 可以提供实时的数据更新。 160 | - MergeTree 类型的表必须有一个 Date 类型列。因为默认情况下数据是按时间进行分区存放的。 161 | 162 | ##### 分区 163 | 164 | - MergeTree 默认分区是以月为单位,同一个月的数据永远都不会被合并,从1.1.54310 版本以后,可以自定义。可以通过 system.parts 表查看表的分区情况 165 | - 同一个分区的数据会被切割到不同的文件夹中。 166 | - 当有新数据写入时,数据会被写入新的文件夹中,后台会有线程定时对这些文件夹进行合并。 167 | - 每个文件夹中包含当前文件夹范围内的数据,数据按照主码排序,并且每个文件夹中有一个针对该文件夹中数据的索引文件。 168 | 169 | ##### 索引 170 | 171 | - 每个数据分区的子文件夹都有一个独立索引。 对于日期索引,查询仅仅在包含这些数据的分区上执行。 172 | - 当 where 子句中在索引列及 Date 列上做了“等于、不等于、>=、<=、>、<、IN、bool 判断”操作,索引就会起作用。 173 | - Like 操作不会使用索引如下面的 SQL 将不会用到索引。eg.SELECT count()FROM table WHERE CounterID = 34 OR URL LIKE '%upyachka%'。 174 | - 查询时最好指定主码,因为在一个子分区中,数据按照主码存储。所以,当定位到某天的数据文件夹时,如果这一天数据量很大,查询不带主码就会导致大量的数据扫描。 175 | 176 | ``` 177 | create table mergeTree_test (gmt Date, id UInt16, name String, point UInt16) ENGINE=MergeTree(gmt, (id, name), 10); 178 | 179 | insert into mergeTree_test(gmt, id, name, point) values ('2017-04-01', 1, 'zys', 10); 180 | insert into mergeTree_test(gmt, id, name, point) values ('2017-06-01', 4, 'abc', 10); 181 | insert into mergeTree_test(gmt, id, name, point) values ('2017-04-03', 5, 'zys', 11); 182 | ``` 183 | 184 | 这个mergeTree_test 的gmt 只接受 yyyy-mm-dd 的格式 185 | 186 | 数据在磁盘中的结构 187 | 188 | ![MergeTree](https://note.youdao.com/yws/api/personal/file/29FF62B819B84F229B5D8A0DF65A9985?method=download&shareKey=928852fb1bfa26246fabfbf84250bf9b) 189 | 190 | 从上面看: 最外层的目录,是根据日期列的范围,做了切分的。目前看来,三条数据,并没有使系统执行 merge 操作(还是有三个目录),后面使用更多的数据看看表现。 最外层的目录:最小日期 - 最大日期 - 最小区块数量 - 最大块数 - 层级 这个是旧的结构不是最新的 detached :如果系统检测到有损坏的数据部分(文件大小错误)或无法识别的部分(部分写入文件系统但未记录在ZooKeeper中),它会将它们移动到“detached”子目录(它们不会被删除) 目录内, primary.idx 应该就是主键组索引了。 目录内其它的文件,看起来跟 Log 引擎的差不多,就是按列保存,额外的 mrk 文件保存一下块偏移量。 使用 optimize table mergeTree_test 触发 merge 行为,三个目录会被合成两个目录,变成 20170401_20170403_2_6_1 和 20170601_20170601_4_4_0 了) 191 | 192 | ### ReplacingMergeTree 193 | 194 | 这个引擎是在 MergeTree 的基础上,添加了“处理重复数据”(根据主键去重)的功能,特别适合是在多维数据加工流程中,为“最新值”,“实时数据”场景。 表引擎的最后一个可选参数是版本列。合并时,它将具有相同主键值的所有行缩减为一行。如果指定了版本列,则会保留最高版本的行;否则,保留最后一行。版本列数据类型必须是 UInt 系、Date或者Datetime 中的一个 ReplacingMergeTree(EventDate, (OrderID, EventDate, BannerID, ...), 8192, ver) 注意,数据仅在合并期间进行重复数据删除。合并在未知的时间发生在后台,所以你不能为此计划。部分数据可能保持未处理状态。虽然可以使用OPTIMIZE操作运行未计划的合并,但不要指望使用它,因为OPTIMIZE操作将读取和写入大量数据 因此,ReplacingMergeTree适用于清除后台中的重复数据以节省空间,但不能保证一定不存在重复。这个引擎不在Yandex.Metrica中使用,但它已被应用于其他Yandex项目 195 | 196 | ``` 197 | create table rmt_test (gmt Date, id UInt16, name String, point UInt16) ENGINE=ReplacingMergeTree(gmt, (name), 10, point); 198 | 199 | insert into rmt_test (gmt, id, name, point) values ('2017-07-10', 1, 'a', 20); 200 | insert into rmt_test (gmt, id, name, point) values ('2017-07-10', 1, 'a', 30); 201 | insert into rmt_test (gmt, id, name, point) values ('2017-07-11', 1, 'a', 20); 202 | insert into rmt_test (gmt, id, name, point) values ('2017-07-11', 1, 'a', 30); 203 | insert into rmt_test (gmt, id, name, point) values ('2017-07-11', 1, 'a', 10); 204 | ``` 205 | 206 | 数据在磁盘上的结构 207 | 208 | ![rmt_test](https://note.youdao.com/yws/api/personal/file/E0C429617986406E89F85408E0BD565D?method=download&shareKey=928852fb1bfa26246fabfbf84250bf9b) 209 | 210 | 结构和MergeTree是一样的 插入这些数据,用 optimize table rmt_test 手动触发一下 merge 行为,然后查询: 211 | 212 | ``` 213 | :) select * from rmt_test 214 | 215 | SELECT * 216 | FROM rmt_test 217 | 218 | ┌────────gmt─┬─id─┬─name─┬─point─┐ 219 | │ 2017-07-11 │ 1 │ a │ 30 │ 220 | └────────────┴────┴──────┴───────┘ 221 | 222 | 1 rows in set. Elapsed: 0.003 sec. 223 | ``` 224 | 225 | ### SummingMergeTree 226 | 227 | 此引擎与MergeTree的不同之处在于它在合并时汇总数据。 228 | 229 | ``` 230 | SummingMergeTree(EventDate, (OrderID, EventDate, BannerID, ...), 8192) 231 | 232 | SummingMergeTree(EventDate, (OrderID, EventDate, BannerID, ...), 8192, (Shows, Clicks, Cost, ...)) 233 | ``` 234 | 235 | 明确设置要总计的列(最后的几个参数 - Shows, Clicks, Cost, ...)。合并时,具有相同主键值的所有行将其值汇总在指定的列中。指定的列也必须是数字,并且不能是主键的一部分。可加列不能是主键中的列,并且如果某行数据可加列都是 `null` ,则这行会被删除。 对于不属于主键的其他部分,合并时会选择第一个出现的值。 另外,一个表格可以嵌套以特殊方式处理的数据结构。如果嵌套表的名称以'Map'结尾,并且它至少包含两列满足以下条件的列 236 | 237 | 1. 表的第一列是数字类(Uint ,Date ,DateTime),我们将它称之为key; 238 | 2. 其他列是支持运算的((U)IntN, Float32/64),我们将它称之为values 然后,这个嵌套表被解释为key =>(values ...)的映射,并且在merging时,两个数据集的元素通过'key'合并,相应的(值...) 合并求和。 Examples: [(1, 100)] + [(2, 150)] -> [(1, 100), (2, 150)] key 不相同 (1, 100)] + [(1, 150)] -> [(1, 250)] key 相同 [(1, 100)] + [(1, 150), (2, 150)] -> [(1, 250), (2, 150)] [(1, 100), (2, 150)] + [(1, -100)] -> [(2, 150)] 239 | 240 | 对于Map的聚合,使用函数sumMap(key,value) 对于嵌套数据结构,不需要将列指定为总计列的列表。 这个表引擎不是特别有用。请记住,只保存预先汇总的数据时,会损失系统的某些优势 241 | 242 | 数据在磁盘上的结构和MergeTree一样 243 | 244 | ### AggregatingMergeTree 245 | 246 | 此引擎与MergeTree的不同之处在于,具有相同主键值的行merge 通过表中存储的聚合函数的逻辑 为此,它使用AggregateFunction数据类型,以及用于集合函数的-State和-Merge修饰符。 让我们仔细研究一下,有一个AggregateFunction数据类型。它是一种参数数据类型。作为参数,传递聚集函数的名称,然后是其参数的类型。 Examples 247 | 248 | ``` 249 | CREATE TABLE t 250 | ( 251 | column1 AggregateFunction(uniq, UInt64), 252 | column2 AggregateFunction(anyIf, String, UInt8), 253 | column3 AggregateFunction(quantiles(0.5, 0.9), UInt64) 254 | ) ENGINE = ... 255 | ``` 256 | 257 | 这种类型的列存储聚合函数的逻辑。要获得这种类型的值,请使用具有状态后缀的聚合函数。 例如: uniqState(UserID), quantilesState(0.5, 0.9)(SendTiming) 与相应的uniq和quantiles函数相反,这些函数返回状态,而不是准备好的值。也就是说,他们返回一个AggregateFunction类型的值。(这里我理解的就像是Scala这种语言一样只是一些逻辑的声明,并没有去按照逻辑去计算,也可能放了一些中间值) AggregateFunction类型值不能以Pretty formats输出。在其他格式中,这些类型的值将作为特定于实现的二进制数据输出。 在一次转换中AggregateFunction类型值不用于输出或保存 使用AggregateFunction类型值可以做的唯一有用的事情是将状态组合起来并获得结果,这实际上意味着完成聚合。具有“Merge并”后缀的聚合函数用于此目的。 例如:uniqMerge(UserIDState),其中UserIDState具有AggregateFunction类型。 简单来说,具有“Merge并”后缀的聚合函数会采用一组状态,将它们组合起来并返回结果。举个例子,这两个查询返回相同的结果: 258 | 259 | ``` 260 | SELECT uniq(UserID) FROM table 261 | SELECT uniqMerge(state) FROM (SELECT uniqState(UserID) AS state FROM table GROUP BY RegionID) 262 | ``` 263 | 264 | AggregatingMergeTree引擎。它在合并期间的工作是 将来表中相同主键的行按照的聚集函数的逻辑组合在一起。 不能使用普通INSERT在包含AggregateFunction列的表中插入行,因为无法显式定义AggregateFunction值。只能使用INSERT SELECT和-State集合函数插入数据。 使用AggregatingMergeTree表中的SELECT,通过'-Merge'修饰符使用GROUP BY和聚合函数来完成数据聚合 您可以使用AggregatingMergeTree表进行增量数据聚合,包括聚合物化视图。 Example: 265 | 266 | ``` 267 | CREATE MATERIALIZED VIEW test.basic 268 | ENGINE = AggregatingMergeTree(StartDate, (CounterID, StartDate), 8192) 269 | AS SELECT 270 | CounterID, 271 | StartDate, 272 | sumState(Sign) AS Visits, 273 | uniqState(UserID) AS Users 274 | FROM test.visits 275 | GROUP BY CounterID, StartDate; 276 | ``` 277 | 278 | 在test.visits表中插入数据。数据也将被插入到视图中,并汇总在该视图中: INSERT INTO test.visits ... 使用GROUP BY从视图执行SELECT以完成数据聚合: 279 | 280 | ``` 281 | SELECT 282 | StartDate, 283 | sumMerge(Visits) AS Visits, 284 | uniqMerge(Users) AS Users 285 | FROM test.basic 286 | GROUP BY StartDate 287 | ORDER BY StartDate; 288 | ``` 289 | 290 | 你可以像这样创建一个物化视图并为其分配一个普通的视图来完成数据聚合。 请注意,在大多数情况下,使用AggregatingMergeTree是没有效果的,因为直接查询也很快。这个看的也是很迷糊 291 | 292 | ### CollapsingMergeTree 293 | 294 | 这个引擎的适用场景是实时查询当前应用上的实时数据,可以类比为变形版的窗口函数。 295 | 296 | 要搞明白它,以及什么场景下用它,为什么用它,需要先行了解一些背景。 297 | 298 | 首先,在 *clickhouse* 中,数据是不能改,更不能删的,其实在好多数仓的基础设施中都是这样。前面为了数据的“删除”,还专门有一个 *ReplacingMergeTree* 引擎嘛。在这个条件之下,想要处理“终态”类的数据,比如大部分的状态数据都是这类,就有些麻烦了。 299 | 300 | 试想,假设每隔 10 秒时间,你都能获取到一个当前在线人数的数据,把这些数据一条一条存下,大概就是这样: 301 | 302 | | 时间点 | 在线人数 | 303 | | ------ | -------- | 304 | | 10 | 123 | 305 | | 20 | 101 | 306 | | 30 | 98 | 307 | | 40 | 88 | 308 | | 50 | 180 | 309 | 310 | 现在问你,“当前有多少人在线?”,这么简单的问题,怎么回答? 311 | 312 | 在这种存数机制下,“当前在线人数”显然是不能把 *在线人数* 这一列聚合起来取数的嘛。也许,能想到的是,“取最大的时间”的那一行,即先 `order by` 再 `limit 1` ,这个办法,在这种简单场景下,好像可行。那我们再把维度加一点: 313 | 314 | | 时间点 | 频道 | 在线人数 | 315 | | ------ | ---- | -------- | 316 | | 10 | a | 123 | 317 | | 10 | b | 29 | 318 | | 10 | c | 290 | 319 | | 20 | a | 101 | 320 | | 20 | b | 181 | 321 | | 20 | c | 31 | 322 | | 30 | a | 98 | 323 | | 30 | b | 18 | 324 | | 30 | c | 56 | 325 | | 40 | a | 88 | 326 | | 40 | b | 9 | 327 | | 40 | c | 145 | 328 | 329 | 这时,如果想看每个频道的当前在线人数,查询就不像之前那么好写了,硬上的话,你可能需要套子查询。好了,我们目的不是讨论 SQL 语句怎么写。 330 | 331 | 回到开始的数据: 332 | 333 | | 时间点 | 在线人数 | 334 | | ------ | -------- | 335 | | 10 | 123 | 336 | | 20 | 101 | 337 | | 30 | 98 | 338 | | 40 | 88 | 339 | | 50 | 180 | 340 | 341 | 如果我们的数据,是在关心一个最终的状态,或者说最新的状态的话,考虑在业务型数据库中的作法,我们会不断地更新确定的一条数据, OLAP 环境我们不能改数据,但是,我们可以通过“运算”的方式,去抹掉旧数据的影响,把旧数据“减”去即可,比如: 342 | 343 | | 符号 | 时间点 | 在线人数 | 344 | | ---- | ------ | -------- | 345 | | + | 10 | 123 | 346 | | - | 10 | 123 | 347 | | + | 20 | 101 | 348 | 349 | 当我们在添加 20 时间点的数据前,首先把之前一条数据“减”去,以这种“以加代删”的增量方式,达到保存最新状态的目的。 350 | 351 | 当然,起初的数据存储,我们可以以 `+1` 和 `-1` 表示符号,以前面两个维度的数据的情况来看(我们把 “时间gmt,频道point” 作为主键): 352 | 353 | | sign | gmt | name | point | 354 | | ---- | ---- | ---- | ----- | 355 | | +1 | 10 | a | 123 | 356 | | +1 | 10 | b | 29 | 357 | | +1 | 10 | c | 290 | 358 | | -1 | 10 | a | 123 | 359 | | +1 | 20 | a | 101 | 360 | | -1 | 10 | b | 29 | 361 | | +1 | 20 | b | 181 | 362 | | -1 | 10 | c | 290 | 363 | | +1 | 20 | c | 31 | 364 | 365 | 如果想看每个频道的当前在线人数: 366 | 367 | ``` 368 | select name, sum(point * sign) from t group by name; 369 | ``` 370 | 371 | 就可以得到正确结果了: 372 | 373 | ``` 374 | ┌─name─┬─sum(multiply(point, sign))─┐ 375 | │ b │ 181 │ 376 | │ c │ 31 │ 377 | │ a │ 101 │ 378 | └──────┴────────────────────────────┘ 379 | ``` 380 | 381 | 神奇。考虑数据可能有错误的情况(`-1` 和 `+1` 不匹配),我们可以添加一个 `having` 来把错误的数据过滤掉,比如再多一条类似这样的数据: 382 | 383 | ``` 384 | insert into t (sign, gmt, name, point) values (-1, '2017-07-11', 'd', 10), 385 | ``` 386 | 387 | 再按原来的 SQL 查,结果是: 388 | 389 | ``` 390 | ┌─name─┬─sum(multiply(point, sign))─┐ 391 | │ b │ 181 │ 392 | │ c │ 31 │ 393 | │ d │ -10 │ 394 | │ a │ 101 │ 395 | └──────┴────────────────────────────┘ 396 | ``` 397 | 398 | 加一个 `having` : 399 | 400 | ``` 401 | select name, sum(point * sign) from t group by name having sum(sign) > 0; 402 | ``` 403 | 404 | 就可以得到正确的数据了: 405 | 406 | ``` 407 | ┌─name─┬─sum(multiply(point, sign))─┐ 408 | │ b │ 181 │ 409 | │ c │ 31 │ 410 | │ a │ 101 │ 411 | └──────┴────────────────────────────┘ 412 | ``` 413 | 414 | 这种增量方式更大的好处,是它与指标本身的性质无关的,不管是否是可加指标,或者是像 UV 这种的去重指标,都可以处理。 415 | 416 | 相较于其它一些变通的处理方式,比如对于可加指标,我们可以通过“差值”存储,来使最后的 `sum` 聚合正确工作,但是对于不可加指标就无能为力了。 417 | 418 | 上面的东西如果都明白了,我们也就很容易理解 *CollapsingMergeTree* 引擎的作用了。 419 | 420 | “以加代删”的增量存储方式,带来了聚合计算方便的好处,代价却是存储空间的翻倍,并且,对于只关心最新状态的场景,中间数据都是无用的。 *CollapsingMergeTree* 引擎的作用,就是针对主键,来帮你维护这些数据,它会在 *merge* 期,把中间数据删除掉。 421 | 422 | 前面的数据,如果我们存在 *MergeTree* 引擎的表中,那么通过 `select * from t` 查出来是: 423 | 424 | ``` 425 | ┌─sign─┬────────gmt─┬─name─┬─point─┐ 426 | │ 1 │ 2017-07-10 │ a │ 123 │ 427 | │ -1 │ 2017-07-10 │ a │ 123 │ 428 | │ 1 │ 2017-07-10 │ b │ 29 │ 429 | │ -1 │ 2017-07-10 │ b │ 29 │ 430 | │ 1 │ 2017-07-10 │ c │ 290 │ 431 | │ -1 │ 2017-07-10 │ c │ 290 │ 432 | │ 1 │ 2017-07-11 │ a │ 101 │ 433 | │ 1 │ 2017-07-11 │ b │ 181 │ 434 | │ 1 │ 2017-07-11 │ c │ 31 │ 435 | │ -1 │ 2017-07-11 │ d │ 10 │ 436 | └──────┴────────────┴──────┴───────┘ 437 | ``` 438 | 439 | 如果换作 *CollapsingMergeTree* ,那么直接就是: 440 | 441 | ``` 442 | ┌─sign─┬────────gmt─┬─name─┬─point─┐ 443 | │ 1 │ 2017-07-11 │ a │ 101 │ 444 | │ 1 │ 2017-07-11 │ b │ 181 │ 445 | │ 1 │ 2017-07-11 │ c │ 31 │ 446 | │ -1 │ 2017-07-11 │ d │ 10 │ 447 | └──────┴────────────┴──────┴───────┘ 448 | ``` 449 | 450 | *CollapsingMergeTree* 在创建时与 *MergeTree* 基本一样,除了最后多了一个参数,需要指定 *Sign* 位(必须是 `Int8` 类型): 451 | 452 | ``` 453 | create table t(sign Int8, gmt Date, name String, point UInt16) ENGINE=CollapsingMergeTree(gmt, (gmt, name), 8192, sign); 454 | ``` 455 | 456 | 讲明白了 *CollapsingMergeTree* 可能有人会问,如果只是要“最新状态”,用 *ReplacingMergeTree* 不就好了么? 457 | 458 | 这里,即使不论对“日期维度”的特殊处理( *ReplacingMergeTree* 不会对日期维度做特殊处理,但是 *CollapsingMergeTree* 看起来是最会保留最新的),更重要的,是要搞明白, 我们面对的数据的形态,不一定是 *merge* 操作后的“完美”形态,也可能是没有 *merge* 的中间形态,所以,即使你知道最后的结果对于每个主键只有一条数据,那也只是 *merge* 操作后的结果,你查数据时,聚合函数还是得用的,当你查询那一刻,可能还有很多数据没有做 *merge* 呢。 459 | 460 | 明白了一点,不难了解,对于 *ReplacingMergeTree* 来说,在这个场景下跟 *MergeTree* 其实没有太多区别的,如果不要 `sign` ,那么结果就是日期维度在那里,你仍然不能以通用方式聚合到最新状态数据。如果要 `sign` ,当它是主键的一部分时,结果就跟 *MergeTree* 一样了,多存很多数据。而当它不是主键的一部分,那旧的 `sign` 会丢失,就跟没有 `sign` 的 *MergeTree* 一样,不能以通用方式聚合到最新状态数据。结论就是, *ReplacingMergeTree* 的应用场景本来就跟 *CollapsingMergeTree* 是两回事。 461 | 462 | *ReplacingMergeTree* 的应用,大概都是一些 `order by limit 1` 这种。而 *CollapsingMergeTree* 则真的是 `group by` 了。 463 | 464 | 官方的例子: Yandex.Metrica有普通日志(如命中日志)和更改日志。更改日志用于增量计算不断变化的数据统计信息。例如会话更改的日志或用户历史更改的日志。Session连接在Yandex.Metrica中不断变化。例如,每个会话的点击次数增加。我们将任何对象的变化称为一对(old values, new values)。如果创建对象,旧值可能会丢失。如果对象被删除,新的值可能会丢失。如果对象已更改,但以前存在且未被删除。在更改日志中,每个更改都会创建一个或两个条目。每个条目都包含对象所具有的所有属性,以及用于区分旧值和新值的特殊属性。当对象发生变化时,只有新条目被添加到更改日志中,而现有的条目未被触及。 465 | 466 | 更改日志可以逐步计算几乎所有的统计数据。为此,我们需要考虑带有加号的“new”行和带有减号的“old”行。换句话说,所有统计量的代数结构都包含用于取元素反转的操作。大多数统计数据都是如此。我们还可以计算“幂等”统计信息,如独特访问者的数量,因为在更改会话时,唯一访问者不会被删除。 这是允许Yandex.Metrica实时工作的主要概念。CollapsingMergeTree接受一个附加参数 - 包含行的“Sign”的Int8类型列的名称。 Example CollapsingMergeTree(EventDate, (CounterID, EventDate, intHash32(UniqID), VisitID), 8192, Sign) 在这里,Sign是一个包含-1表示“old”值和1表示“new”值的列。 merge时,每组连续相同的主键值(用于排序数据的列)被减少为不超过一行,列值为'sign_column = -1'(“负行”),并且不超过一行列值'sign_column = 1'(“正行”)。换句话说,来自更改日志的条目已折叠。 如果正数行和负数行匹配,则写入第一个负行和最后一个正数行。如果正数行比负数行多一个,则只写入最后一个正数行。如果负行比正行多一个,则只写入第一个负行。否则,将会出现逻辑错误,并且不会写入任何行。如果日志的同一部分意外插入多次,则会发生逻辑错误,错误仅记录在服务器日志中,merge继续。) 467 | 468 | 因此,collapsing 不应改变计算统计的结果。随着collapsed的进行每个对象最后几乎只剩下最后的一个值。与MergeTree相比,CollapsingMergeTree引擎可以使数据量减少数倍 有几种方法可以从CollapsingMergeTree表中获取完全“collapsed”的数据: 1、用GROUP BY和聚合函数编写一个查询来解释sign。例如,要计算数量,请写'sum(Sign)'而不是'count()'。要计算某些东西的总和,请写'sum(Sign * x)'而不是'sum(x)',依此类推,并添加'HAVING sum(Sign)> 0'。并非所有的金额都可以这样计算。例如,集合函数'min'和'max'不能被重写。 2、如果您必须提取没有聚合的数据(例如,要检查是否存在最新值符合特定条件的行),则可以对FROM子句使用FINAL修饰符。这种方法效率显着较低· 469 | 470 | ### GraphiteMergeTree 471 | 472 | 该引擎专为汇总(细化和聚合/平均)Graphite data而设计。(Graphite 介绍)对于想要将ClickHouse用作Graphite的数据存储的开发人员可能会有帮助 没有细看 473 | 474 | ### Data replication 475 | 476 | 复制仅支持MergeTree系列中的表,下面穷举出来了: ReplicatedMergeTree ReplicatedSummingMergeTree ReplicatedReplacingMergeTree ReplicatedAggregatingMergeTree ReplicatedCollapsingMergeTree ReplicatedGraphiteMergeTree 477 | 478 | 复制适用于单个表的级别,而不是整个服务器。服务器可以同时存储复制表和非复制表 复制不依赖分片。每个分片都有自己的独立复制。 为INSERT和ALTER查询复制压缩数据(请参阅ALTER操作的说明)。 CREATE,DROP,ATTACH,DETACH和RENAME查询在单个服务器上执行,不会被复制: 479 | 480 | - CREATE TABLE 操作将在运行查询的服务器上创建一个新的可复制表。如果该表已经存在于其他服务器上,它将添加一个新的副本。 481 | - DROP TABLE 操作将删除位于运行查询的服务器上的副本。 482 | - RENAME 操作会重命名其中一个副本上的表。就是说,复制表可以在不同副本上具有不同的名称 483 | 484 | 要使用复制,请在配置文件中设置ZooKeeper群集的地址。例: 485 | 486 | ``` 487 | 488 | 489 | example1 490 | 2181 491 | 492 | 493 | example2 494 | 2181 495 | 496 | 497 | example3 498 | 2181 499 | 500 | 501 | ``` 502 | 503 | 使用ZooKeeper 3.4.5或更高版本。 您可以指定任何现有的ZooKeeper集群,并且系统将使用其上的目录作为自己的数据(该目录在创建可复制表时指定) 如果ZooKeeper未在配置文件中设置,则无法创建复制表,并且任何现有的复制表都将为只读 在SELECT查询中不使用ZooKeeper,因为复制不会影响SELECT的性能,查询的运行速度与对非复制表的速度一样快。查询分布式复制表时,ClickHouse行为由设置max_replica_delay_for_distributed_queries和fallback_to_stale_replicas_for_distributed_queries控制。 对于每个INSERT操作,通过多个事务向ZooKeeper添加约十个条目。(更确切地说,这是针对每个插入的数据块; 一个INSERT操作包含一个块或者每个max_insert_block_size = 1048576行)。与非复制表相比,INSERT的延迟时间稍长。但是,如果按照建议以每秒不超过一次批量的INSERT数据,则不会产生任何问题。使用ZooKeeper集群的协调整个ClickHouse集群每秒总共有数百个INSERT。数据插入的吞吐量(每秒的行数)与非复制数据一样高。 504 | 505 | 对于非常大的群集,可以针对不同的分片使用不同的ZooKeeper群集。但是,这在Yandex.Metrica集群(大约300台服务器)上证明是没有必要的。 506 | 507 | Replication 是异步和多主机的。INSERT操作(以及ALTER)可以发送到任何可用的服务器。将数据插入运行查询的服务器上,然后将其复制到其他服务器。由于它是异步的,因此最近插入的数据会在其他副本上出现一些延迟。如果部分副本不可用,则数据在可用时被写入。如果副本可用,则延迟是通过网络传输压缩数据块所需的时间。 508 | 509 | 默认情况下,INSERT操作只等待确认写入一个副本的数据。如果数据只写入一个副本,并且具有此副本的服务器不再存在,则存储的数据将会丢失。 Tp能够从多个副本获取数据写入的确认,请使用insert_quorum选项。 每个数据块都是以原子方式写入的。 INSERT操作被分成块,最多为max_insert_block_size = 1048576行。或者这样说,如果INSERT操作的行数小于1048576,则它是一个原子操作。 510 | 511 | 数据块中重复数被据删除。对于同一数据块的多次写入(相同大小的数据块包含相同顺序的相同行),该块只写入一次。这是因为客户端应用程序不知道数据是否写入数据库时发生网络故障,因此可以简单地重复INSERT查询。使用相同的数据发送哪些副本INSERT并不重要。 INSERT是幂等的。重复数据消除参数由merge_tree服务器设置控制。 512 | 513 | 在复制期间,只有要插入的源数据通过网络传输。进一步的数据转换(merging)是以相同的方式在所有副本上进行协调和执行的。这可以最大限度地减少网络使用量,这意味着当副本驻留在不同的数据中心时复制效果良好。 (请注意,复制不同数据中心中的数据是复制的主要目标。) 514 | 515 | 您可以拥有相同数据的任意数量的副本。 Yandex.Metrica在生产中使用双重复制。每个服务器在某些情况下使用RAID-5或RAID-6和RAID-10。这是一个相对可靠和方便的解决方案。 516 | 517 | 系统监控副本上的数据同步性,并能够在发生故障后进行恢复。故障转移是自动的(对于数据中的小差异)或半自动的(当数据差异太大时,这说明可能配置错误)。 518 | 519 | #### 创建复制表 520 | 521 | Replicated的前缀被添加到表引擎名称。例如:ReplicatedMergeTree。 参数列表的开始处还添加了两个参数 - ZooKeeper中的表的路径以及ZooKeeper中的副本名称 Example 522 | 523 | ``` 524 | ReplicatedMergeTree('/clickhouse/tables/{layer}-{shard}/hits', '{replica}', EventDate, intHash32(UserID), (CounterID, EventDate, intHash32(UserID), EventTime), 8192) 525 | ``` 526 | 527 | 如示例所示,这些参数可以在大括号中包含替换。替换值取自配置文件的“macros”部分(理解为变量值)。例 05 02 example05-02-1.yandex.ru ZooKeeper中表的路径对于每个复制表都应该是唯一的。不同碎片上的表应该有不同的路径。在这种情况下,路径由以下部分组成: /clickhouse/tables/ 是常见的前缀。官方建议使用这个。 {layer}-{shard} 是碎片标识符。在本例中它由两部分组成,因为Yandex.Metrica集群使用双层分片。对于大多数任务,可以只留下{shard}替换,该替换将扩展为碎片标识符。 hits 是ZooKeeper中表的节点名称。将它与表名称相同是一个好主意。它是明确定义的,因为与表名相反,它在RENAME操作后不会更改。 528 | 529 | 副本名称标识保持唯一,可以使用服务器名称作为示例。 530 | 531 | 应该明确定义参数,而不是使用替换。这可能对测试和配置小型集群很方便。但是,在这种情况下,在这种情况下不能使用分布式DDL查询(ON CLUSTER)。就是说不用前面提到的在macros标签中定义的变量 532 | 533 | 在处理大型集群时,我们建议使用替换,因为它们会降低出错的可能性。 在每个副本上运行CREATE TABLE操作。此操作将创建一个新的复制表,或者将新副本添加到现有复制表中。 534 | 535 | 如果在表已包含其他副本上的某些数据后添加新的副本,则在运行查询后,数据将从其他副本复制到新副本。换句话说,新副本与其他节点同步。 536 | 537 | 要删除副本,请运行DROP TABLE。但是,只有一个副本被删除 - 驻留在运行查询的服务器上的副本。 538 | 539 | #### Recovery after failures 540 | 541 | 如果ZooKeeper在服务器启动时不可用,则复制表将切换到只读模式。系统会定期尝试连接到ZooKeeper 如果ZooKeeper在INSERT期间不可用,或者在与ZooKeeper交互时发生错误,则会抛出异常。 连接到ZooKeeper后,系统会检查本地文件系统中的数据集是否与预期的数据集(ZooKeeper存储此信息)匹配。如果存在较小的不一致性,则系统通过与副本同步数据来解决这些问题。 如果系统检测到有损坏的数据部分(文件大小错误)或无法识别的部分(部分写入文件系统但未记录在ZooKeeper中),它会将它们移动到“detached”子目录(它们不会被删除)。任何缺少的部分都从副本中复制。 请注意,ClickHouse不会执行任何破坏性操作,例如:自动删除大量数据。 当服务器启动(或者与ZooKeeper建立一个新的会话)时,它只检查所有文件的数量和大小。如果文件大小匹配但字节在中间的某个位置发生了更改,则不会立即检测到这种情况,而只是在尝试读取SELECT查询的数据时才会检测到。该查询将引发有关非匹配校验或者压缩块大小的异常。在这种情况下,数据部分将添加到验证队列中,并在必要时从副本中复制。 542 | 543 | 如果本地数据集与预期数据差别太大,则会触发安全机制。服务器在日志中输入并拒绝启动。造成这种情况的原因是,这种情况表明可能存在配置错误,例如shard 上的副本被意外配置为不同分片上的副本。但是,此机制的阈值设置得相当低,并且正常故障恢复期间可能会发生这种情况。在这种情况下,通过“pushing a button”数据被半自动地恢复。 544 | 545 | 开始恢复,在zookeeper上使用任何内容创建节点/path_to_table/replica_name/flags/force_restore_data或者运行该命令以恢复所有复制表: sudo -u clickhouse touch /var/lib/clickhouse/flags/force_restore_data 然后重新启动服务器。在开始时,服务器删除这些标志并开始恢复。 546 | 547 | #### Recovery after complete data loss 548 | 549 | 如果所有数据和元数据都从其中一台服务器中消失,请按照以下步骤进行恢复: 1、在服务器上安装ClickHouse。如果有需要,则在包含碎片标识符和副本的配置文件中使用正确定义替换。(我理解的就是正确安装ClickHouse和正确的配置,最好就是复制一台正在良好运行的ClickHouse节点然后改配置) 2、如果您有必须在服务器上手动复制的未复制表,请从副本(在目录中)复制其数据, (in the directory /var/lib/clickhouse/data/db_name/table_name/). 3、复制位于/ var / lib / clickhouse / metadata /中副本的表定义。如果在表定义中明确定义了分片或副本标识符,请对其进行更正以使其与此副本相对应。 (或者,启动服务器并进行应该位于/ var / lib / clickhouse / metadata /中的.sql文件中的所有ATTACH TABLE操作。) 4、开始恢复,使用任何内容创建ZooKeeper节点/path_to_table/replica_name/flags/force_restore_data或运行该命令来恢复所有复制表 sudo -u clickhouse touch /var/lib/clickhouse/flags/force_restore_data 550 | 551 | 然后启动服务器(重新启动,如果它已经在运行)。数据将从副本下载。 552 | 553 | 备用恢复选项是从ZooKeeper(/ path_to_table / replica_name)删除有关已丢失副本的信息,然后再次创建副本,如"Creating replicatable tables".中所述 请注意,如果您要一次恢复多个副本,恢复期间网络带宽没有限制。 554 | 555 | #### 从MergeTree转换到ReplicatedMergeTree 556 | 557 | 我们用MergeTree来代指MergeTree系列中的所有表引擎,与ReplicatedMergeTree相同。 558 | 559 | 如果您有一个需要手动复制的MergeTree表,则可以将其转换为可复制表。如果您已经在MergeTree表中收集了大量数据并且现在想要启用复制,则可能需要执行此操作。 560 | 561 | 如果各种副本上的数据不同,请首先对其进行同步,或者保留一个副本。 562 | 563 | 重命名现有的MergeTree表,然后使用旧名称创建一个ReplicatedMergeTree表。使用新表数据(/ var / lib / clickhouse / data / db_name / table_name /)将旧表中的数据移动到目录内的“detached”子目录中。然后在其中一个副本上运行ALTER TABLE ATTACH PARTITION,将这些数据parts 添加到工作集中。 564 | 565 | 当ZooKeeper集群中的元数据丢失或损坏时恢复 566 | 567 | 使用不同的名称创建MergeTree表。将具有ReplicatedMergeTree表数据的目录中的所有数据移动到新表的数据目录。然后删除ReplicatedMergeTree表并重新启动服务器。 568 | 569 | 如果要在不启动服务器的情况下删除 replicatedmergetree 表, 请执行以下操作: 删除元数据目录(/ var / lib / clickhouse / metadata /)中相应的.sql文件。 删除ZooKeeper中的相应路径(/ path_to_table / replica_name)。 570 | 571 | 在此之后,您可以启动服务器,创建MergeTree表,将数据移动到其目录,然后重新启动服务器。 572 | 573 | 当ZooKeeper集群中的元数据丢失或损坏时恢复 如果ZooKeeper中的数据丢失或损坏,您可以通过将数据移动到未复制的表格来保存数据,如上所述。 如果其他副本上存在完全相同的部分,则会将它们添加到其工作集中。如果没有,则从具有它们的副本中进行下载。 574 | 575 | ### Buffer 576 | 577 | Buffer 引擎,像是 Memory 存储的一个上层应用似的(磁盘上也是没有相应目录的)。它的行为是一个缓冲区,写入的数据先被放在缓冲区,达到一个阈值后,这些数据会自动被写到指定的另一个表中。 Buffer 是接在其它表前面的一层,对它的读操作,也会自动应用到后面表,但是因为前面说到的限制的原因,一般我们读数据,就直接从源表读就好了,缓冲区的这点数据延迟,只要配置得当,影响不大的。 Buffer 后面也可以不接任何表,这样的话,当数据达到阈值,就会被丢弃掉。 578 | 579 | ``` 580 | Buffer(database, table, num_layers, min_time, max_time, min_rows, max_rows, min_bytes, max_bytes) 581 | -- 先创建源表 582 | create table source_table (gmt Date, id UInt16, name String, point UInt16) ENGINE=MergeTree(gmt, (id, name), 10); 583 | -- 在创建buffer表 584 | create table source_buffer as t ENGINE=Buffer(default, source_table, 16, 3, 20, 2, 10, 1, 10000) 585 | ``` 586 | 587 | 引擎参数: 588 | 589 | - database:数据库, 590 | - table:表 - 将数据刷新到的表。您可以使用一个返回string.num_layers - Parallelism图层的常量表达式来代替数据库名称。数据库和表名的单引号中设置空字符串。这代表没有目标表。在这种情况下,当达到数据刷新条件时,缓冲区将被清除。这对于在内存中保存数据窗口可能很有用。 591 | - `num_layers` 是类似“分区”的概念,每个分区的后面的 `min / max` 是独立计算的,官方推荐的值是 `16` 。为每个'num_layers'缓冲区分别计算刷新数据的条件。例如,如果num_layers = 16且max_bytes = 100000000,则最大RAM消耗为1.6 GB。 592 | - `min / max` 这组配置荐,就是设置阈值的,分别是 时间(秒),行数,空间(字节)。 593 | 594 | 阈值的规则,是“所有的 min 条件都满足, 或 至少一个 max 条件满足”。 如果按上面我们的建表来说,所有的 min 条件就是:过了 3秒,2条数据,1 Byte。一个 max 条件是:20秒,或 10 条数据,或有 10K 。 595 | 596 | 关于 Buffer 的其它一些点: 597 | 598 | - 如果一次写入的数据太大或太多,超过了 max 条件,则会直接写入源表。 599 | - 删源表或改源表的时候,建议 Buffer 表删了重建。 600 | - “友好重启”时, Buffer 数据会先落到源表,“暴力重启”, Buffer 表中的数据会丢失。 601 | - 即使使用了 Buffer ,多次的小数据写入,对比一次大数据写入,也 慢得多 (几千行与百万行的差距)。 602 | 603 | 从Buffer表读取数据时,Buffer和目标表(如果有的话)都会处理数据。请注意,Buffer表不支持索引。换句话说,Buffer中的数据已被完全扫描,对于大型Buffer可能会很慢。(对于从属表中的数据,将使用它支持的索引。) 604 | 605 | 如果Buffer表中的一组列与下属表中的一组列不匹配,则会插入两个表中存在的列的子集 606 | 607 | 如果这些类型与Buffer表和其下一个表中的一列不匹配,则会在服务器日志中输入错误消息,并清除缓冲区。如果在刷新缓冲区时从属表不存在,也会发生同样的情况。 608 | 609 | 如果需要为下级表和缓冲区表运行ALTER,我们建议首先删除Buffer表,为下级表运行ALTER,然后再次创建Buffer表。 610 | 611 | PREWHERE,FINAL和SAMPLE对缓冲区表无法正常工作。这些条件传递到目标表,但不用于处理缓冲区中的数据。因此,我们建议只使用Buffer表进行写入,同时从目标表读取数据。 612 | 613 | 将数据添加到Buffer时,其中一个Buffer被锁定。如果从表中同时执行读取操作,则会导致延迟 614 | 615 | 插入到Buffer表中的数据可能以不同的顺序在不同的块中结束于从属表。因此,Buffer表很难正确写入CollapsingMergeTree。为了避免问题,您可以将'num_layers'设置为1。 616 | 617 | 如果复制目标表,则写入缓冲区表时,复制表的某些预期特性会丢失。随机更改数据部分的行和大小顺序会导致重复数据删除操作退出,这意味着无法对复制表进行可靠的“精确一次”写入。 618 | 619 | 由于这些缺点,我们只能推荐在极少数情况下使用Buffer表。 620 | 621 | 请注意,一次插入一行数据是无意义的,即使对于缓冲区表也是如此。这只会产生每秒几千行的速度,而插入较大的数据块每秒可产生超过一百万行(请参见“Performance”一节)。 622 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | clickHouse的文档中文翻译 2 | ======================== 3 | ClickHouse是俄罗斯yandex公司开源的一款性能强大的列式数据库,开源时间不久,中文资料相当欠缺,特开一个翻译专题,把主要的文档翻译为中文,方便大家尝试使用。 4 | 5 | - [ClickHouse官方英文文档](https://clickhouse.yandex/reference_en.html) 6 | - [ClickHouse中文文档](https://github.com/sparkthu/clickhouse-doc-cn/blob/master/Reference.md) 7 | 8 | 维护:sparkthu@gmail.com 9 | 致谢:@hc132提供了引擎部分的内容 10 | 11 | -------------------------------------------------------------------------------- /Reference.md: -------------------------------------------------------------------------------- 1 | ClickHouse 中文参考文档 2 | ======================== 3 | 4 | # 目录 5 | 6 | ## 介绍 7 | 8 | - [什么是ClickHouse?](https://github.com/sparkthu/clickhouse-doc-cn/blob/master/01introduction/whatis.md) 9 | - [新手上路](https://github.com/sparkthu/clickhouse-doc-cn/blob/master/01introduction/getstart.md) 10 | 11 | ## 使用 12 | 13 | - [接口](https://github.com/sparkthu/clickhouse-doc-cn/blob/master/02use/interface.md) 14 | - [查询语言](https://github.com/sparkthu/clickhouse-doc-cn/blob/master/02use/query.md) 15 | - [外部数据](https://github.com/sparkthu/clickhouse-doc-cn/blob/master/02use/external.md) 16 | 17 | ## 表引擎(Table engines) 18 | 19 | - [引擎](https://github.com/sparkthu/clickhouse-doc-cn/blob/master/03tableengine/engine.md) 20 | 21 | ## 系统表 22 | 23 | ## 表方法(Table functions) 24 | 25 | ## 格式(Formats) 26 | 27 | ## 数据类型 28 | 29 | ## 操作(Operators) 30 | 31 | ## 方法(Functions) 32 | 33 | ## 聚合方法(Aggregate functions) 34 | 35 | ## 词典 36 | 37 | ## 设置 38 | 39 | 40 | --------------------------------------------------------------------------------