惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

S
Secure Thoughts
月光博客
月光博客
Y
Y Combinator Blog
量子位
J
Java Code Geeks
The GitHub Blog
The GitHub Blog
MyScale Blog
MyScale Blog
aimingoo的专栏
aimingoo的专栏
Microsoft Azure Blog
Microsoft Azure Blog
Apple Machine Learning Research
Apple Machine Learning Research
博客园_首页
罗磊的独立博客
Google DeepMind News
Google DeepMind News
大猫的无限游戏
大猫的无限游戏
M
MIT News - Artificial intelligence
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
V
Visual Studio Blog
S
Schneier on Security
S
Security Affairs
Project Zero
Project Zero
L
LINUX DO - 热门话题
H
Hacker News: Front Page
Google Online Security Blog
Google Online Security Blog
L
Lohrmann on Cybersecurity
Latest news
Latest news
P
Palo Alto Networks Blog
Application and Cybersecurity Blog
Application and Cybersecurity Blog
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
www.infosecurity-magazine.com
www.infosecurity-magazine.com
MongoDB | Blog
MongoDB | Blog
Blog — PlanetScale
Blog — PlanetScale
The Last Watchdog
The Last Watchdog
Help Net Security
Help Net Security
I
Intezer
The Register - Security
The Register - Security
小众软件
小众软件
C
Check Point Blog
NISL@THU
NISL@THU
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
T
Tor Project blog
D
Docker
Hugging Face - Blog
Hugging Face - Blog
WordPress大学
WordPress大学
Forbes - Security
Forbes - Security
H
Hackread – Cybersecurity News, Data Breaches, AI and More
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
T
Tailwind CSS Blog
Security Latest
Security Latest
博客园 - 司徒正美
IT之家
IT之家

博客园 - everest33

计算机 图床 HTTP学习笔记 机器学习学习记录 squid学习笔记 VMware平时记录 摄影随记 IDEA vim配置(.vimrc文件) 分布式系统 杂记 Dockerfile、Docker镜像和Docker容器的关系 Java多线程学习笔记 正则表达式学习记录 Aspose 学习记录 ELK 学习笔记 Java泛型之通配符 Java中的类型擦除与桥方法 shell中 >/dev/null 2>&1 是什么意思 Visual Studio Code 学习记录
ClickHouse原理解析和应用实践学习笔记
everest33 · 2026-03-03 · via 博客园 - everest33

平时记录

💡 记住:ClickHouse 是为 大批量、低频写入 设计的,不是消息队列。写入频率越低、批次越大,系统越稳定

ClickHouse的存储结构为:/data/clickhouse/data/simulation/solarforecast_raw_data/{parts}

  • simulation为数据库;solarforecast_raw_data为数据表;数据表目录下为每次插入时生成的part。属于同一个Partition的不同part目录会自动进行合并(🚨最终一个Partition不一定只有一个part目录)。注意🚨:在物理存储目录结构下没有Partition目录,Partition是个逻辑概念。

ClickHouse原理解析与应用实践

朱凯 著 ISBN:978-7-111-65490-2 本书纸版由机械工业出版社于2020年出版。本书基于 ClickHouse 19.17.4.11 版本讲解。

※,1.6.1 顺理成章的MySQL时期

原文:(针对mysql)市面上一块典型的7200转SATA磁盘的IOPS(每秒能处理的请求数)仅为100左右,也就是说每秒只能执行100次随机读取。假设一次随机读取返回10行数据,那么查询100000行记录则需要至少100秒,这种响应时间显然是不可接受的。

问题:假设一次随机读取返回10行数据是否合理?为什么不假设读取100行,1000行呢?

这是一个非常敏锐的提问!你抓住了原文论证中的一个关键简化假设。我们来深入分析:


✅ 结论先行:

“一次随机读取返回10行数据”这个假设在特定上下文中是合理的,但它是一个有意为之的、偏保守(甚至略显悲观)的估算,目的是突出传统磁盘在 随机 I/O 场景下的致命瓶颈
不是对数据库实际工作方式的精确描述,而是一种用于说明性能问题的教学性简化模型


一、为什么说“10行”在技术上是可能的?

1. 磁盘 I/O 的基本单位是“扇区”或“块”

  • 传统机械硬盘(HDD)的最小物理读取单位是 512 字节(旧)或 4KB(先进格式化,AF)。
  • 操作系统和文件系统通常以 4KB 页(Page)为单位进行 I/O。
  • 数据库(如 MySQL InnoDB)默认的数据页大小也是 16KB(可配置为 4KB、8KB、32KB、64KB)。

2. 一行数据有多大

这决定了一页能存多少行。举例:

场景 单行大小估算 16KB 页可存行数
简单用户表(id + name) ~100 字节 ≈ 160 行
包含文本/JSON 的日志表 ~1KB ≈ 16 行
宽表(上百字段) ~2KB+ ≈ 8 行或更少

👉 所以,“10行/页”对应的是单行约 1.6KB 的中等偏大记录,这在实际业务中(如包含描述、地址、少量 JSON)完全合理

✅ 因此,从存储密度角度看,“一次 I/O 读 10 行”并非随意捏造,而是基于典型数据模型的合理估计。


二、那为什么不假设读 100 行、1000 行呢?

原因有三,核心在于 “随机读取” vs “顺序读取” 的本质区别:

❌ 误区:认为“一次 I/O 可以任意多读”

  • IOPS = Input/Output Operations Per Second,这里的 “Operation” 指的是一次寻址+读取动作
  • 7200 转 HDD 的瓶颈不在传输速度,而在“寻道时间”+“旋转延迟”
    • 平均寻道时间:~8.5 ms
    • 平均旋转延迟:~4.17 ms(7200 RPM)
    • 总随机访问延迟 ≈ 12.7 ms → 理论最大 IOPS ≈ 1000 / 12.7 ≈ 79(接近常说的 100)

🔑 关键点:无论你这次读 1 字节还是 1MB,只要它是随机位置,都要付出 ~12.7ms 的延迟成本!

📌 所以:

  • 如果你要读 分散在不同位置的 1000 行,即使每行只有 100 字节,也需要 1000 次随机 I/O → 1000 / 100 = 10 秒
  • 但如果这 1000 行物理上连续存储(顺序读),那么只需 1 次寻址 + 连续读取 → 传输时间可能仅需几毫秒
  • 【自注】从上面两段可以得出:一次IO可以读取任意多的数据,关键在于需要的数据是否一次IO就可以读取到(即是否是连续存储的);对于数据无序的情况,一次IO只能获取到其中一部分数据,下一部分数据需要再来一次IO寻址+数据读取

💡 原文的潜台词是:

“假设你要做的是全表扫描但数据无序(最坏情况),或者执行一个无法使用索引的查询,导致需要大量随机访问不同的数据页。”在这种场景下每次 I/O 只能有效获取一页的数据(比如 10 行),而不是批量读取上千行。


三、数据库的实际行为:为什么通常比“10行/IOPS”好得多?

原文故意忽略了数据库的优化机制,以凸显 HDD 的极限瓶颈。现实中,数据库会通过以下方式避免陷入这种最坏情况

1. 顺序 I/O 优先

  • 全表扫描时,数据库会按物理存储顺序读取数据页 → 变成顺序读,吞吐可达 100~200 MB/s(远高于 100 IOPS 的限制)。
  • 例如:16KB 页 × 1000 页 = 16MB,顺序读可能只需 0.1 秒。

2. 缓冲池(Buffer Pool)

  • 热点数据常驻内存(如 MySQL 的 innodb_buffer_pool),根本不需要磁盘 I/O

3. 预读(Read-Ahead)

  • 数据库预测你会读后续页面,提前批量加载到内存。

4. 索引覆盖 & 聚簇索引

  • 利用 B+ 树索引,将随机访问转化为少量 I/O(如 3 次)即可定位大量数据。

✅ 所以,100,000 行查询在真实系统中通常不会花 100 秒——除非你的查询写得极差(如 SELECT * 且无索引 + 数据全冷 + 强制随机读)。


四、原文为何要这样假设?—— 教学目的

作者的真正意图是:

  1. 量化 HDD 的随机 I/O 瓶颈:用具体数字(100 IOPS)说明为什么不能依赖 HDD 做高并发随机查询。
  2. 引出 SSD 的必要性:SSD 的随机 IOPS 可达 数万至数十万,彻底解决此问题。
  3. 强调架构设计的重要性:必须通过索引、缓存、数据局部性来避免随机 I/O

如果假设“一次读 1000 行”,就会掩盖 HDD 在非顺序访问场景下的致命缺陷,削弱论证力度。


✅ 总结

问题 回答
“10行/次”合理吗 合理,对应单行 ~1.6KB 的典型业务数据,在 16KB 页中可存约 10 行。
为什么不假设更多 因为随机 I/O 的成本在“寻址”而非“传输量”。读 1 行和读 1000 行(若分散)都需要多次寻址。
现实会这么慢吗 通常不会,因数据库会通过顺序读、缓存、索引等避免随机 I/O。但最坏情况下确实可能
作者为何这样写 教学简化,旨在突出传统 HDD 在 OLTP 场景下的性能天花板,推动技术选型(如用 SSD 或优化查询)。

📌 记住:性能分析中,“最坏情况假设”常用于揭示系统瓶颈,不代表日常表现。真正的工程优化,就是让系统永远不要走到这个最坏情况

第三章 安装与部署

※,3.1 clickhouse的安装

官方安装文档:https://clickhouse.com/docs/install

在192.168.67.27 Ubuntu20.04上使用apt安装

也可以从ClickHouse官方仓库下载deb包手动安装,注意页面最下面有个next按钮可以查找更多版本的deb包。需下载三个deb包:

1. 设置源

# Install prerequisite packages
sudo apt-get install -y apt-transport-https ca-certificates curl gnupg

# Download the ClickHouse GPG key and store it in the keyring
curl -fsSL 'https://packages.clickhouse.com/rpm/lts/repodata/repomd.xml.key' | sudo gpg --dearmor -o /usr/share/keyrings/clickhouse-keyring.gpg

# Get the system architecture
ARCH=$(dpkg --print-architecture)

# Add the ClickHouse repository to apt sources
echo "deb [signed-by=/usr/share/keyrings/clickhouse-keyring.gpg arch=${ARCH}] https://packages.clickhouse.com/deb stable main" | sudo tee /etc/apt/sources.list.d/clickhouse.list

# Update apt package lists
sudo apt-get update

2 查看可用版本

(base) [root@sungrow27 etc]$apt-cache madison clickhouse-server
clickhouse-server |   26.2.3.2 | https://packages.clickhouse.com/deb stable/main amd64 Packages
clickhouse-server |   26.2.2.9 | https://packages.clickhouse.com/deb stable/main amd64 Packages
clickhouse-server | 26.2.1.1139 | https://packages.clickhouse.com/deb stable/main amd64 Packages
...

3 安装最新版本。🔒 🔑 👮‍♂️ 安装过程中需要设置一个密码,本次设置为:Clickhouse@2026

(base) [root@sungrow27 etc]$apt install  clickhouse-server=26.2.3.2 clickhouse-client=26.2.3.2
正在读取软件包列表... 完成
正在分析软件包的依赖关系树       
正在读取状态信息... 完成       
将会同时安装下列软件:
  clickhouse-common-static
建议安装:
  clickhouse-common-static-dbg
下列【新】软件包将被安装:
  clickhouse-client clickhouse-common-static clickhouse-server
升级了 0 个软件包,新安装了 3 个软件包,要卸载 0 个软件包,有 71 个软件包未被升级。
需要下载 223 MB 的归档。
解压缩后会消耗 759 MB 的额外空间。
您希望继续执行吗? [Y/n] y
获取:1 https://packages.clickhouse.com/deb stable/main amd64 clickhouse-common-static amd64 26.2.3.2 [223 MB]
获取:2 https://packages.clickhouse.com/deb stable/main amd64 clickhouse-client amd64 26.2.3.2 [62.2 kB]                                                                             
获取:3 https://packages.clickhouse.com/deb stable/main amd64 clickhouse-server amd64 26.2.3.2 [92.0 kB]                                                                             
已下载 215 MB,耗时 33秒 (6,461 kB/s)                                                                                                                                               
正在选中未选择的软件包 clickhouse-common-static。
dpkg: 警告: 无法找到软件包 squid-langpack 的文件名列表文件,现假定该软件包目前没有任何文件被安装在系统里。
(正在读取数据库 ... 系统当前共安装有 193762 个文件和目录。)
准备解压 .../clickhouse-common-static_26.2.3.2_amd64.deb  ...
正在解压 clickhouse-common-static (26.2.3.2) ...
正在选中未选择的软件包 clickhouse-client。
准备解压 .../clickhouse-client_26.2.3.2_amd64.deb  ...
正在解压 clickhouse-client (26.2.3.2) ...
正在选中未选择的软件包 clickhouse-server。
准备解压 .../clickhouse-server_26.2.3.2_amd64.deb  ...
正在解压 clickhouse-server (26.2.3.2) ...
正在设置 clickhouse-common-static (26.2.3.2) ...
正在设置 clickhouse-server (26.2.3.2) ...
ClickHouse binary is already located at /usr/bin/clickhouse
Symlink /usr/bin/clickhouse-server already exists but it points to /clickhouse. Will replace the old symlink to /usr/bin/clickhouse.
Creating symlink /usr/bin/clickhouse-server to /usr/bin/clickhouse.
Symlink /usr/bin/clickhouse-client already exists but it points to /clickhouse. Will replace the old symlink to /usr/bin/clickhouse.
Creating symlink /usr/bin/clickhouse-client to /usr/bin/clickhouse.
Symlink /usr/bin/clickhouse-local already exists but it points to /clickhouse. Will replace the old symlink to /usr/bin/clickhouse.
Creating symlink /usr/bin/clickhouse-local to /usr/bin/clickhouse.
Symlink /usr/bin/clickhouse-benchmark already exists but it points to /clickhouse. Will replace the old symlink to /usr/bin/clickhouse.
Creating symlink /usr/bin/clickhouse-benchmark to /usr/bin/clickhouse.
Symlink /usr/bin/clickhouse-obfuscator already exists but it points to /clickhouse. Will replace the old symlink to /usr/bin/clickhouse.
Creating symlink /usr/bin/clickhouse-obfuscator to /usr/bin/clickhouse.
Symlink /usr/bin/clickhouse-compressor already exists but it points to /clickhouse. Will replace the old symlink to /usr/bin/clickhouse.
Creating symlink /usr/bin/clickhouse-compressor to /usr/bin/clickhouse.
Symlink /usr/bin/clickhouse-format already exists but it points to /clickhouse. Will replace the old symlink to /usr/bin/clickhouse.
Creating symlink /usr/bin/clickhouse-format to /usr/bin/clickhouse.
Symlink /usr/bin/clickhouse-extract-from-config already exists but it points to /clickhouse. Will replace the old symlink to /usr/bin/clickhouse.
Creating symlink /usr/bin/clickhouse-extract-from-config to /usr/bin/clickhouse.
Symlink /usr/bin/clickhouse-keeper already exists but it points to /clickhouse. Will replace the old symlink to /usr/bin/clickhouse.
Creating symlink /usr/bin/clickhouse-keeper to /usr/bin/clickhouse.
Symlink /usr/bin/clickhouse-keeper-converter already exists but it points to /clickhouse. Will replace the old symlink to /usr/bin/clickhouse.
Creating symlink /usr/bin/clickhouse-keeper-converter to /usr/bin/clickhouse.
Symlink /usr/bin/clickhouse-chdig already exists but it points to /clickhouse. Will replace the old symlink to /usr/bin/clickhouse.
Creating symlink /usr/bin/clickhouse-chdig to /usr/bin/clickhouse.
Symlink /usr/bin/chdig already exists. Will keep it.
Symlink /usr/bin/ch already exists. Will keep it.
Symlink /usr/bin/chl already exists. Will keep it.
Symlink /usr/bin/chc already exists. Will keep it.
Creating clickhouse group if it does not exist.
 groupadd -r clickhouse
groupadd:“clickhouse”组已存在
Creating clickhouse user if it does not exist.
 useradd -r --shell /bin/false --home-dir /nonexistent -g clickhouse clickhouse
useradd:用户“clickhouse”已存在
Will set ulimits for clickhouse user in /etc/security/limits.d/clickhouse.conf.
Creating config directory /etc/clickhouse-server/config.d that is used for tweaks of main server configuration.
Creating config directory /etc/clickhouse-server/users.d that is used for tweaks of users configuration.
Config file /etc/clickhouse-server/config.xml already exists, will keep it and extract path info from it.
/etc/clickhouse-server/config.xml has /var/lib/clickhouse/ as data path.
/etc/clickhouse-server/config.xml has /var/log/clickhouse-server/ as log path.
Users config file /etc/clickhouse-server/users.xml already exists, will keep it and extract users info from it.
Log directory /var/log/clickhouse-server/ already exists.
Data directory /var/lib/clickhouse/ already exists.
Pid directory /var/run/clickhouse-server already exists.
 chown -R clickhouse:clickhouse '/var/log/clickhouse-server/'
 chown -R clickhouse:clickhouse '/var/run/clickhouse-server'
 chown  clickhouse:clickhouse '/var/lib/clickhouse/'
Set up the password for the default user: 
Password for the default user is saved in file /etc/clickhouse-server/users.d/default-password.xml.
Setting capabilities for clickhouse binary. This is optional.
 chown -R clickhouse:clickhouse '/etc/clickhouse-server'

ClickHouse has been successfully installed.

Start clickhouse-server with:
 sudo clickhouse start

Start clickhouse-client with:
 clickhouse-client --password

Synchronizing state of clickhouse-server.service with SysV service script with /lib/systemd/systemd-sysv-install.
Executing: /lib/systemd/systemd-sysv-install enable clickhouse-server
Created symlink /etc/systemd/system/multi-user.target.wants/clickhouse-server.service → /lib/systemd/system/clickhouse-server.service.
正在设置 clickhouse-client (26.2.3.2) ...
正在处理用于 systemd (245.4-4ubuntu3.22) 的触发器 ...

4 修改配置文件 /etc/clickhouse-server/config.xml

  • <listen_host>0.0.0.0</listen_host> // 放开这条注释,让host监听0.0.0.0
  • 默认数据文件夹:/var/lib/clickhouse,可以修改为/data1/clickhouse
    • 注意修改新文件夹的属主chown -R clickhouse:clickhouse /data1/clickhouse

5. 启动clickhouse-server

(base) [root@sungrow27 clickhouse-server]$systemctl start clickhouse-server.service 
(base) [root@sungrow27 clickhouse-server]$systemctl status clickhouse-server.service 
● clickhouse-server.service - ClickHouse Server (analytic DBMS for big data)
     Loaded: loaded (/lib/systemd/system/clickhouse-server.service; enabled; vendor preset: enabled)
     Active: active (running) since Thu 2026-03-05 15:33:12 CST; 1s ago
   Main PID: 2367228 (clickhouse-serv)
      Tasks: 689 (limit: 154102)
     Memory: 199.0M
     CGroup: /system.slice/clickhouse-server.service
             ├─2367221 clickhouse-watchdog        --config=/etc/clickhouse-server/config.xml --pid-file=/run/clickhouse-server/clickhouse-server.pid
             └─2367228 /usr/bin/clickhouse-server --config=/etc/clickhouse-server/config.xml --pid-file=/run/clickhouse-server/clickhouse-server.pid

3月 05 15:33:11 sungrow27 systemd[1]: Starting ClickHouse Server (analytic DBMS for big data)...
3月 05 15:33:11 sungrow27 clickhouse-server[2367221]: Processing configuration file '/etc/clickhouse-server/config.xml'.
3月 05 15:33:11 sungrow27 clickhouse-server[2367221]: Logging trace to /var/log/clickhouse-server/clickhouse-server.log
3月 05 15:33:11 sungrow27 clickhouse-server[2367221]: Logging errors to /var/log/clickhouse-server/clickhouse-server.err.log
3月 05 15:33:11 sungrow27 systemd[1]: clickhouse-server.service: Supervising process 2367228 which is not our child. We'll most likely not notice when it exits.
3月 05 15:33:12 sungrow27 systemd[1]: Started ClickHouse Server (analytic DBMS for big data).

6 客户端连接

(base) [root@sungrow27 clickhouse-server]$clickhouse-client
ClickHouse client version 26.2.3.2 (official build).
Connecting to localhost:9000 as user default.
Password for user (default): [手动输入Clickhouse@2026]
Connecting to localhost:9000 as user default.
Connected to ClickHouse server version 26.2.3.

sungrow27 :)

※,3.2 客户端访问

ClickHouse的底层访问接口支持TCP和HTTP两种协议,其中,TCP协议拥有更好的性能,其默认端口为9000,主要用于集群间的内部通信及CLI客户端;而HTTP协议则拥有更好的兼容性,可以通过REST服务的形式被广泛用于JAVA、Python等编程语言的客户端,其默认端口为8123。通常而言,并不建议用户直接使用底层接口访问ClickHouse,更为推荐的方式是通过CLI和JDBC这些封装接口,因为它们更加简单易用。

ClickHouse的jar包有两类:

  • clickhouse-client:原生Java API实现的与ClickHouse通信的jar包,不依赖JDBC接口/规范。性能更好。
  • clickhouse-jdbc:实现了JDBC接口/规范的jar包,底层使用了clickhouse-client。性能相比clickhouse-client性能差点,因为jdbc抽象层有性能开销,但是通用型更好。

※,6.4.2 二级(跳数)索引的类型

★,关于ngrambf_v1(布隆过滤器)

问题:

你回答中提到:
对于表中的每一个 Granule(数据块),ClickHouse 会:
遍历该块中 message 列的所有字符串。
对每个字符串,提取所有的 N-Gram(N 由参数指定)。
将所有这些 N-Gram 全部添加到一个布隆过滤器(Bloom Filter)中。
将这个 BF 作为该 Granule 的索引元数据保存起来。
📦 最终,每个 Granule 对应一个小小的 BF。
我有点不理解,请问:ClickHouse遍历该块中 message 列的所有字符串,对每个字符串,提取所有的 N-Gram,将所有这些 N-Gram 全部添加到一个布隆过滤器(Bloom Filter)中,如果有一亿条数据,这个布隆过滤器为什么还是一个小小的BF?不应该很大吗?

通义千问Qwen3-max回答:

这是一个极其关键且深刻的问题!你准确地抓住了布隆过滤器(Bloom Filter, BF)最反直觉、也最精妙的地方:

为什么能用“很小的空间”表示“海量元素”的存在性?

答案在于:布隆过滤器不存储元素本身,只存储元素的“指纹”(哈希值),并且允许多个元素共享同一个“指纹位”。

下面我将一步步拆解这个“魔法”。


1. 布隆过滤器的核心结构:一个位数组

想象一个非常长的开关面板,上面有 M 个灯泡(位),初始状态全是 关(0)

  • M 是固定的,比如你创建索引时指定的 512
  • 这个面板就是整个布隆过滤器,它的大小就是 M 位 = M/8 字节
    • 例如 M=512 → 面板大小 = 64 字节
    • 无论你要存 1 个元素还是 1 亿个元素,这个面板的物理大小永远不变

2. 如何“添加”一个元素(N-Gram)?

假设我们要添加一个 N-Gram,比如 "hel"

  1. 计算多个哈希值

    • 使用 H 个不同的哈希函数(比如 H=2)。
    • hash1("hel") = 123
    • hash2("hel") = 456
  2. 将结果映射到位数组索引

    • 因为面板只有 M=512 个位置,所以要对结果取模。
    • index1 = 123 % 512 = 123
    • index2 = 456 % 512 = 456
  3. 打开对应的灯泡

    • 把第 123 号和第 456 号灯泡 打开(设为 1)

✅ 现在,"hel" 就被“添加”到 BF 中了。注意,我们并没有存储 "hel" 这个字符串!


3. 添加一亿个 N-Gram 会发生什么?

现在,我们开始处理一个 Granule(比如 8192 行数据)。每行日志可能产生 10~100 个 N-Gram。那么一个 Granule 总共可能有 几万到几十万个 N-Gram

当我们把这几十万个 N-Gram 逐个“添加”到同一个 BF(M=512)中时:

  • 每个 N-Gram 都会通过哈希函数,试图打开面板上的 2 个(H=2)灯泡。
  • 关键点来了:不同的 N-Gram 极有可能会打开相同的灯泡
    • 例如,"abc"hash1 结果可能是 123
    • "xyz"hash2 结果也可能是 123
    • 它们都会去打开第 123 号灯泡。

最终结果

  • 面板上的灯泡会从全 0,逐渐变成很多 1
  • 但是,面板的总大小始终是 512 位(64字节)
  • 它不会因为元素增多而变大,它只是变得“更亮”(1 的比例更高)。

📦 这就是为什么它“小小”的原因:它的容量是固定的,靠的是信息压缩概率,而不是存储


4. 查询时如何工作?(以及误报的来源)

现在,我们想查询 N-Gram "out" 是否可能存在。

  1. 计算哈希

    • hash1("out") = 200
    • hash2("out") = 300
  2. 检查灯泡

    • 查看第 200 号和 300 号灯泡是否都是 1
    • 情况 A:如果任何一个0(比如 3000)。
      • 结论"out" 绝对没有被添加过! ✅ 100% 准确
    • 情况 B:如果两个都是 1
      • 结论"out" 可能存在
      • 但是:这两个灯泡也可能是被其他 N-Gram(如 "pqr", "stu")打开的!
      • 这就是“误报(False Positive)”的来源。❌

误报率可控

  • 如果你把面板做得更大(增大 M),或者用更多的哈希函数(增大 H),就能降低不同元素“撞”到同一个灯泡的概率,从而降低误报率。
  • 这就是为什么创建索引时要调整 MH 参数。

5. 回到你的问题:一亿条数据 vs 一个小 BF

  • 误解澄清:BF 不是对整张表建一个,而是对每一个 Granule(数据块) 建一个。
  • 一个 Granule 默认只有 8192 行 数据。
  • 即使这 8192 行每行产生 100 个 N-Gram,总共也就 80 万个 N-Gram
  • 一个 M=4096 (512字节) 的 BF 来存储 80 万个 N-Gram 的指纹,虽然会有一定误报,但空间效率极高。

总结一下尺寸

项目 大小
一个 Granule 的原始数据 几 MB 到几十 MB
该 Granule 对应的 ngrambf_v1 索引 (BF) 几百字节 (M=4096 → 512字节)

💡 核心思想:BF 牺牲了一点点“精确性”(允许误报),换取了巨大的空间节省极快的查询速度。在“跳过索引”的场景下,这是完美的权衡——宁可多读一点(误报),也绝不能漏掉(漏报)。


最终结论

布隆过滤器之所以“小”,是因为:

  1. 它不存数据,只存哈希指纹
  2. 它的底层是一个固定大小的位数组,与插入元素的数量无关。
  3. 它利用哈希冲突来实现极致压缩,并通过数学方法将误报率控制在可接受范围内。

因此,即使一个 Granule 包含海量的 N-Gram,其对应的 ngrambf_v1 索引依然只是一个 KB 级别的微小结构,却能高效地完成“可能性预筛”的任务。

※,9 数据查询

问:ClickHouse如何输出查看执行计划?

DeepSeek回答:ClickHouse 提供了多种查看查询执行计划的方法。以下是主要的几种方式:

1. EXPLAIN 语句(最常用)

查看逻辑执行计划

-- 查看语法树(逻辑计划)
EXPLAIN SYNTAX
SELECT * FROM your_table WHERE id = 1;

-- 查看查询流水线(物理计划)
EXPLAIN PIPELINE
SELECT * FROM your_table WHERE id = 1;

-- 查看执行计划(最详细)
EXPLAIN PLAN
SELECT * FROM your_table WHERE id = 1;

查看带设置的执行计划

EXPLAIN
SELECT * FROM your_table WHERE id = 1
SETTINGS
    allow_experimental_analyzer = 1;  -- 使用新的分析器

2. EXPLAIN ESTIMATE(估算统计信息)

-- 估算查询将处理的数据量
EXPLAIN ESTIMATE
SELECT * FROM your_table WHERE date >= '2024-01-01';

3. 查看查询执行的详细步骤

启用详细日志

-- 在查询时启用trace日志
SELECT * FROM your_table
SETTINGS
    send_logs_level = 'trace',  -- 查看详细执行日志
    log_queries = 1;            -- 记录查询日志

或使用clickhouse-client -h localhost --password Clickhouse@2026 --send_logs_level=trace <<< 'SELECT * FROM simulation.load_raw_data' > /dev/null

(base) [root@sungrow27 20240509_1_1_0]$clickhouse-client --version
ClickHouse client version 26.2.3.2 (official build).
(base) [root@sungrow27 20240509_1_1_0]$clickhouse-client -h localhost --password Clickhouse@2026 --send_logs_level=trace <<< 'SELECT * FROM simulation.load_raw_data' > /dev/null
[sungrow27] 2026.03.12 13:45:13.486734 [ 2367241 ] {ffb2f54d-ef5e-4e81-bef0-14907ac8c707} <Debug> executeQuery: (from 127.0.0.1:56624) (query 1, line 1) SELECT * FROM simulation.load_raw_data  (stage: Complete)
[sungrow27] 2026.03.12 13:45:13.487487 [ 2367241 ] {ffb2f54d-ef5e-4e81-bef0-14907ac8c707} <Trace> Planner: Query to stage Complete
[sungrow27] 2026.03.12 13:45:13.487693 [ 2367241 ] {ffb2f54d-ef5e-4e81-bef0-14907ac8c707} <Trace> Planner: Query from stage FetchColumns to stage Complete
[sungrow27] 2026.03.12 13:45:13.488088 [ 2367241 ] {ffb2f54d-ef5e-4e81-bef0-14907ac8c707} <Debug> simulation.load_raw_data (fa3adb74-721c-48ef-8abd-8581fd90a9a2) (SelectExecutor): Key condition: unknown
[sungrow27] 2026.03.12 13:45:13.488806 [ 2367241 ] {ffb2f54d-ef5e-4e81-bef0-14907ac8c707} <Debug> simulation.load_raw_data (fa3adb74-721c-48ef-8abd-8581fd90a9a2) (SelectExecutor): MinMax index condition: unknown
[sungrow27] 2026.03.12 13:45:13.488890 [ 2367241 ] {ffb2f54d-ef5e-4e81-bef0-14907ac8c707} <Trace> simulation.load_raw_data (fa3adb74-721c-48ef-8abd-8581fd90a9a2) (SelectExecutor): Filtering marks by primary and secondary keys
[sungrow27] 2026.03.12 13:45:13.493081 [ 2367241 ] {ffb2f54d-ef5e-4e81-bef0-14907ac8c707} <Debug> simulation.load_raw_data (fa3adb74-721c-48ef-8abd-8581fd90a9a2) (SelectExecutor): PK index has dropped 0/282 granules, it took 0ms across 40 threads.
[sungrow27] 2026.03.12 13:45:13.493267 [ 2367241 ] {ffb2f54d-ef5e-4e81-bef0-14907ac8c707} <Debug> simulation.load_raw_data (fa3adb74-721c-48ef-8abd-8581fd90a9a2) (SelectExecutor): Selected 282/282 parts by partition key, 282 parts by primary key, 282/282 marks by primary key, 282 marks to read from 282 ranges
[sungrow27] 2026.03.12 13:45:13.493675 [ 2367241 ] {ffb2f54d-ef5e-4e81-bef0-14907ac8c707} <Trace> simulation.load_raw_data (fa3adb74-721c-48ef-8abd-8581fd90a9a2) (SelectExecutor): Spreading mark ranges among streams (default reading)
[sungrow27] 2026.03.12 13:45:13.501670 [ 2367241 ] {ffb2f54d-ef5e-4e81-bef0-14907ac8c707} <Debug> simulation.load_raw_data (fa3adb74-721c-48ef-8abd-8581fd90a9a2) (SelectExecutor): Reading approx. 81216 rows with 40 streams
[sungrow27] 2026.03.12 13:45:13.523991 [ 2367241 ] {ffb2f54d-ef5e-4e81-bef0-14907ac8c707} <Debug> executeQuery: Read 81216 rows, 3.87 MiB in 0.037162 sec., 2185458.2638178784 rows/sec., 104.20 MiB/sec.
[sungrow27] 2026.03.12 13:45:13.524191 [ 2367241 ] {ffb2f54d-ef5e-4e81-bef0-14907ac8c707} <Debug> MemoryTracker: Query peak memory usage: 4.89 MiB.
(base) [root@sungrow27 20240509_1_1_0]$clickhouse-client -h localhost --password Clickhouse@2026 --send_logs_level=trace <<< "SELECT * FROM simulation.load_raw_data where data_time='2024-05-09 00:00:00'" > /dev/null
[sungrow27] 2026.03.12 13:50:54.489445 [ 2367241 ] {128ca226-cfcc-415e-b9f5-6954b095fa8a} <Debug> executeQuery: (from 127.0.0.1:50512) (query 1, line 1) SELECT * FROM simulation.load_raw_data where data_time='2024-05-09 00:00:00'  (stage: Complete)
[sungrow27] 2026.03.12 13:50:54.491304 [ 2367241 ] {128ca226-cfcc-415e-b9f5-6954b095fa8a} <Trace> Planner: Query to stage Complete
[sungrow27] 2026.03.12 13:50:54.491624 [ 2367241 ] {128ca226-cfcc-415e-b9f5-6954b095fa8a} <Trace> Planner: Query from stage FetchColumns to stage Complete
[sungrow27] 2026.03.12 13:50:54.493018 [ 2367241 ] {128ca226-cfcc-415e-b9f5-6954b095fa8a} <Trace> QueryPlanOptimizePrewhere: The min valid primary key position for moving to the tail of PREWHERE is -1
[sungrow27] 2026.03.12 13:50:54.493075 [ 2367241 ] {128ca226-cfcc-415e-b9f5-6954b095fa8a} <Trace> QueryPlanOptimizePrewhere: Moved 1 conditions to PREWHERE
[sungrow27] 2026.03.12 13:50:54.493383 [ 2367241 ] {128ca226-cfcc-415e-b9f5-6954b095fa8a} <Debug> simulation.load_raw_data (fa3adb74-721c-48ef-8abd-8581fd90a9a2) (SelectExecutor): Key condition: (column 1 in [1715184000, 1715184000])
[sungrow27] 2026.03.12 13:50:54.493837 [ 2367241 ] {128ca226-cfcc-415e-b9f5-6954b095fa8a} <Debug> simulation.load_raw_data (fa3adb74-721c-48ef-8abd-8581fd90a9a2) (SelectExecutor): MinMax index condition: (column 0 in [1715184000, 1715184000])
[sungrow27] 2026.03.12 13:50:54.493951 [ 2367241 ] {128ca226-cfcc-415e-b9f5-6954b095fa8a} <Debug> simulation.load_raw_data (fa3adb74-721c-48ef-8abd-8581fd90a9a2) (SelectExecutor): Query condition cache has dropped 0/1 granules for PREWHERE condition equals(__table1.data_time, '2024-05-09 00:00:00'_String).
[sungrow27] 2026.03.12 13:50:54.494021 [ 2367241 ] {128ca226-cfcc-415e-b9f5-6954b095fa8a} <Debug> simulation.load_raw_data (fa3adb74-721c-48ef-8abd-8581fd90a9a2) (SelectExecutor): Query condition cache has dropped 0/1 granules for WHERE condition equals(data_time, '2024-05-09 00:00:00'_String).
[sungrow27] 2026.03.12 13:50:54.494079 [ 2367241 ] {128ca226-cfcc-415e-b9f5-6954b095fa8a} <Trace> simulation.load_raw_data (fa3adb74-721c-48ef-8abd-8581fd90a9a2) (SelectExecutor): Filtering marks by primary and secondary keys
[sungrow27] 2026.03.12 13:50:54.494164 [ 2367241 ] {128ca226-cfcc-415e-b9f5-6954b095fa8a} <Trace> simulation.load_raw_data (fa3adb74-721c-48ef-8abd-8581fd90a9a2) (SelectExecutor): Used generic exclusion search over index for part 20240509_1_283_4_284 with 1 steps
[sungrow27] 2026.03.12 13:50:54.494231 [ 2367241 ] {128ca226-cfcc-415e-b9f5-6954b095fa8a} <Debug> simulation.load_raw_data (fa3adb74-721c-48ef-8abd-8581fd90a9a2) (SelectExecutor): PK index has dropped 0/1 granules, it took 0ms across 1 threads.
[sungrow27] 2026.03.12 13:50:54.494311 [ 2367241 ] {128ca226-cfcc-415e-b9f5-6954b095fa8a} <Debug> simulation.load_raw_data (fa3adb74-721c-48ef-8abd-8581fd90a9a2) (SelectExecutor): Selected 1/282 parts by partition key, 1 parts by primary key, 1/1 marks by primary key, 1 marks to read from 1 ranges
[sungrow27] 2026.03.12 13:50:54.494401 [ 2367241 ] {128ca226-cfcc-415e-b9f5-6954b095fa8a} <Trace> simulation.load_raw_data (fa3adb74-721c-48ef-8abd-8581fd90a9a2) (SelectExecutor): Spreading mark ranges among streams (default reading)
[sungrow27] 2026.03.12 13:50:54.494609 [ 2367241 ] {128ca226-cfcc-415e-b9f5-6954b095fa8a} <Trace> simulation.load_raw_data (fa3adb74-721c-48ef-8abd-8581fd90a9a2) (SelectExecutor): Reading 1 ranges in order from part 20240509_1_283_4_284, approx. 288 rows starting from 0
[sungrow27] 2026.03.12 13:50:54.497771 [ 2367241 ] {128ca226-cfcc-415e-b9f5-6954b095fa8a} <Debug> executeQuery: Read 288 rows, 13.78 KiB in 0.008472 sec., 33994.33427762039 rows/sec., 1.59 MiB/sec.
[sungrow27] 2026.03.12 13:50:54.497847 [ 2367241 ] {128ca226-cfcc-415e-b9f5-6954b095fa8a} <Debug> MemoryTracker: Query peak memory usage: 268.32 KiB.
(base) [root@sungrow27 20240509_1_1_0]$

查看查询的详细执行信息

执行查询后,从系统表查看:

-- 查看最近的查询执行详情
SELECT
    query,
    query_id,
    read_rows,
    read_bytes,
    result_rows,
    result_bytes,
    memory_usage
FROM system.query_log
WHERE type = 'QueryFinish'
ORDER BY event_time DESC
LIMIT 5;

4. 使用特殊表函数

查看查询执行的步骤

-- 查看查询的执行步骤和耗时
SELECT *
FROM clusterAllReplicas(default, system.query_log)
WHERE query_id = 'your-query-id-here'
  AND type = 'QueryFinish';

5. 在客户端中查看

在clickhouse-client中启用详细输出

# 使用详细模式启动客户端
clickhouse-client --verbose

# 或者在查询时使用\G格式
clickhouse-client --query "EXPLAIN PLAN SELECT * FROM your_table" --format Vertical

6. 查看查询的性能剖析

启用性能剖析

-- 启用查询剖析
SET allow_introspection_functions = 1;

-- 查看查询的内存使用
EXPLAIN ESTIMATE
SELECT * FROM your_table
SETTINGS
    allow_introspection_functions = 1,
    log_comment = 'profile_query';

7. 查看实际执行统计

执行查询后,查看实际的执行统计:

-- 查看表的部分统计
SELECT
    table,
    sum(rows) as total_rows,
    formatReadableSize(sum(bytes)) as total_size,
    formatReadableSize(sum(data_uncompressed_bytes)) as uncompressed_size
FROM system.parts
WHERE active
GROUP BY table
ORDER BY sum(rows) DESC;

8. 使用 Grafana 或 ClickHouse 管理工具

如果你使用可视化工具,如:

  • Tabix

  • DBeaver(需要安装ClickHouse驱动)

  • DataGrip

  • Grafana​ 配合 ClickHouse 数据源

这些工具通常有可视化的执行计划查看器。

实际使用示例

示例1:查看完整执行计划

-- 查看完整的执行计划
EXPLAIN
SELECT
    database,
    formatReadableSize(sum(data_uncompressed_bytes)) as size
FROM system.columns
GROUP BY database
ORDER BY size DESC
SETTINGS
    send_logs_level = 'debug',
    log_queries = 1;

示例2:查看查询管道

-- 显示查询的物理执行管道
EXPLAIN PIPELINE
SELECT
    toYear(date) as year,
    count() as cnt
FROM hits
WHERE CounterID = 34
GROUP BY year
ORDER BY year;

示例3:在日志中查看详细执行

-- 执行查询并查看详细日志
SELECT *
FROM system.tables
WHERE name LIKE 'query%'
SETTINGS
    send_logs_level = 'trace',
    log_queries = 1;

执行后查看系统日志:

-- 查看查询执行日志
SELECT *
FROM system.query_log
WHERE event_time > now() - interval 5 minute
  AND query LIKE '%system.tables%'
ORDER BY event_time DESC
LIMIT 1\G

小贴士

  1. 对于复杂查询,建议使用 EXPLAIN PLANEXPLAIN PIPELINE结合

  2. 查看性能瓶颈时,使用 send_logs_level = 'trace'获取详细日志

  3. 长期监控查询性能,可以定期查询 system.query_log

  4. 在生产环境中谨慎使用 trace 级别日志,因为它会产生大量日志数据

选择哪种方法取决于你的具体需求:

  • 调试查询逻辑 → EXPLAIN SYNTAX

  • 优化查询性能 → EXPLAIN PIPELINE

  • 查看执行细节 → 启用 trace 日志

  • 监控生产查询 → 查询 system.query_log

★,9.8 having子句

HAVING子句需要与GROUP BY同时出现,不能单独使用。它能够在聚合计算之后实现二次过滤数据。例如下面的语句是一条普通的聚合查询,会按照table分组并计数:
SELECT COUNT() FROM system.parts GROUP BY table
--执行计划
Expression
Expression
Aggregating
Concat
Expression
One
现在增加HAVING子句后再次执行上述操作,则数据在按照table聚合之后,进一步截掉了table='query_v3'的部分。
SELECT COUNT() FROM system.parts GROUP BY table HAVING table = 'query_v3'
--执行计划
Expression
Expression
Filter
Aggregating
Concat
Expression
One
观察两次查询的执行计划,可以发现HAVING的本质是在聚合之后增加了Filter过滤动作。

对于类似上述的查询需求,除了使用HAVING之外,通过嵌套的WHERE也能达到相同的目的,例如下面的语句:
SELECT COUNT() FROM (SELECT table FROM system.parts WHERE table = 'query_v3') GROUP BY table
--执行计划
Expression
Expression
Aggregating
Concat
Expression
Expression
Expression
Filter
One
分析上述查询的执行计划,相比使用HAVING,嵌套WHERE的执行计划效率更高。因为WHERE等同于使用了谓词下推,在聚合之前就进行了数据过滤,从而减少了后续聚合时需要处理的数据量

既然如此,那是否意味着HAVING子句没有存在的意义了呢?其实不然,现在来看另外一种查询诉求。假设现在需要按照table分组聚合,并且返回均值bytes_on_disk大于10 000字节的数据表,在这种情形下需要使用HAVING子句:
SELECT table ,avg(bytes_on_disk) as avg_bytes
FROM system.parts GROUP BY table
HAVING avg_bytes > 10000
┌─table─────┬───avg_bytes───┐
│ hits_v1 │ 730190752 │
└─────────┴────────────┘
这是因为WHERE的执行优先级大于GROUP BY,所以如果需要按照聚合值进行过滤,就必须借助HAVING实现

★,9.12 select子句

SELECT子句决定了一次查询语句最终返回哪些列字段或表达式。与直观的感受不同,虽然SELECT位于SQL语句的起始位置,但它却是在上述一众子句之后执行的。在其他子句执行之后,SELECT会将选取的字段或表达式作用于每行数据之上。

在选择列字段时,ClickHouse还为特定场景提供了一种基于正则查询的形式。例如执行下面的语句后,查询会返回名称以字母n开头和包含字母p的列字段:

SELECT COLUMNS('^n'), COLUMNS('p') FROM system.databases
┌─name────┬─data_path────┬─metadata_path───┐
│ default │ /data/default/ │ /metadata/default/ │
│ system │ /data/system/ │ /metadata/system/ │

★,9.13 DISTINCT子句

DISTINCT子句能够去除重复数据,使用场景广泛。有时候,人们会拿它与GROUP BY子句进行比较。假设数据表query_v5的数据如下所示:

┌─name─┬─v1─┐
│ a │ 1 │
│ c │ 2 │
│ b │ 3 │
│ NULL │ 4 │
│ d │ 5 │
│ a │ 6 │
│ a │ 7 │
│ NULL │ 8 │
└────┴───┘

则下面两条SQL查询的返回结果相同:

-- DISTINCT查询
SELECT DISTINCT name FROM query_v5
-- DISTINCT查询执行计划
Expression
Distinct
Expression
Log
-- GROUP BY查询
SELECT name FROM query_v5 GROUP BY name
-- GROUP BY查询执行计划
Expression
Expression
Aggregating
Concat
Expression
Log

其中,第一条SQL语句使用了DISTINCT子句,第二条SQL语句使用了GROUP BY子句。但是观察它们执行计划不难发现,DISTINCT子句的执行计划更简单。与此同时,DISTINCT也能够与GROUP BY同时使用,所以它们是互补而不是互斥的关系

如果使用了LIMIT且没有ORDER BY子句,则DISTINCT在满足条件时能够迅速结束查询,这样可避免多余的处理逻辑;

而当DISTINCT与ORDER BY同时使用时,其执行的优先级是先DISTINCT后ORDER BY

第 11章 管理与运维

本章将会介绍ClickHouse的权限、熔断机制、数据备份和服务监控等知识。

※,11.1 用户配置

user.xml配置文件默认位于/etc/clickhouse-server/user.d/路径下,ClickHouse使用它来定义用户相关的配置项,包括系统参数的设定、用户的定义、权限以及熔断机制等。

★,11.1.3 用户定义

使用users标签可以配置自定义用户。如果打开user.xml配置文件,会发现已经默认配置了default用户,在此之前的所有示例中,一直使用的正是这个用户。定义一个新用户,必须包含以下几项属性。
1.username
username用于指定登录用户名,这是全局唯一属性。该属性比较简单,这里就不展开介绍了。

2.password
password用于设置登录密码,支持明文、SHA256加密和double_sha1加密三种形式,可以任选其中一种进行设置。现在分别介绍它们的使用方法。
(1)明文密码:在使用明文密码的时候,直接通过password标签定义,例如下面的代码:<password>123</password>;如果password为空,则表示免密码登录:<password></password>
(2)SHA256加密:在使用SHA256加密算法的时候,需要通过password_sha256_hex标签定义密码,例如下面的代码:<password_sha256_hex>a665a45920422f9d417e4867efdc4fb8a04a1f3fff1fa07e998e86f7f7a27ae3</password_sha256_hex>

可以执行下面的命令获得密码的加密串,例如对明文密码123进行加密:

# echo -n 123 | openssl dgst -sha256
(stdin)= a665a45920422f9d417e4867efdc4fb8a04a1f3fff1fa07e998e86f7f7a27ae3

(3)double_sha1加密:在使用double_sha1加密算法的时候,则需要通过password_double_sha1_hex标签定义密码,例如下面的代码:<password_double_sha1_hex>23ae809ddacaf96af0fd78ed04b6a265e05aa257</password_double_sha1_hex>
可以执行下面的命令获得密码的加密串,例如对明文密码123进行加密:

# echo -n 123 | openssl dgst -sha1 -binary | openssl dgst -sha1
(stdin)= 23ae809ddacaf96af0fd78ed04b6a265e05aa257

※,11.4 数据备份

导出文件备份

数据导出:如果数据的体量较小,可以通过dump的形式将数据导出为本地文件。例如执行下面的语句将test_backup的数据导出

  • clickhouse-client --password Clickhouse@2026 --query "select * from simulation.load_raw_data" > load_raw_data_backup.csv
  • OR
  • clickhouse-client --password Clickhouse@2026 <<< "select * from simulation.load_raw_data" > load_raw_data_backup.csv

数据导入:将备份数据再次导入,则可以执行下面的语句:

  • cat load_raw_data_backup.csv | clickhouse-client --query "INSERT INTO simulation.load_raw_data FORMAT TSV"

上述这种dump形式的优势在于,可以利用SELECT查询并筛选数据,然后按需备份

如果是备份整个表的数据,也可以直接复制它的整个目录文件,例如:

  • mkdir -p /chbase/backup/default/ & cp -r /chbase/data/default/test_backup  /chbase/backup/default/ #新版ClickHouse(26.2.3.2)数据表目录变成了一个软连接,和这个例子不太一样。

通过快照表备份

快照表实质上就是普通的数据表,它通常按照业务规定的备份频率创建,例如按天或者按周创建。所以首先需要建立一张与原表结构相同的数据表,然后再使用INSERT INTO SELECT句式,点对点地将数据从原表写入备份表。假设数据表test_backup需要按日进行备份,现在为它创建当天的备份表:CREATE TABLE test_backup_0206 AS test_backup

有了备份表之后,就可以点对点地备份数据了,例如:INSERT INTO TABLE test_backup_0206 SELECT * FROM test_backup

如果考虑到容灾问题,也可以将备份表放置在不同的ClickHouse节点上,此时需要将上述SQL语句改成远程查询的形式:

INSERT INTO TABLE test_backup_0206 SELECT * FROM remote('ch5.nauu.com:9000','default', 'test_backup', 'default')

按分区备份

基于数据分区的备份,ClickHouse目前提供了FREEZEFETCH两种方式。

1.使用FREEZE备份

FREEZE的完整语法如下所示:ALTER TABLE tb_name FREEZE PARTITION partition_expr

分区在被备份之后,会被统一保存到/path_to_clickhouse_data_root_dir/shadow/N子目录下。其中,N是一个自增长的整数,它的含义是备份的次数(FREEZE执行过多少次),具体次数由shadow子目录下的increment.txt文件记录。而分区备份实质上是对原始目录文件进行硬链接操作,所以并不会导致额外的存储空间。

  • 同一份数据的多个硬链接共享同一个inode(软连接则是各自的inode)。
  • 同一份数据的多个硬链接不会占用额外的数据存储空间,但会占用少量元数据空间。
  • 所有的硬链接都删除了,对应的数据才会被删除。

对于备份分区的还原操作,则需要借助ATTACH装载分区的方式来实现。这意味着如果要还原数据,首先需要主动将shadow子目录下的分区文件复制到相应数据表的detached目录下,然后再使用ATTACH语句装载。

2.使用FETCH备份

FETCH只支持ReplicatedMergeTree系列的表引擎,它的完整语法如下所示:ALTER TABLE tb_name FETCH PARTITION partition_id FROM zk_path

其工作原理与ReplicatedMergeTree同步数据的原理类似,FETCH通过指定的zk_path找到ReplicatedMergeTree的所有副本实例,然后从中选择一个最合适的副本,并下载相应的分区数据。例如执行下面的语句:ALTER TABLE test_fetch FETCH PARTITION 2019 FROM '/clickhouse/tables/01/test_fetch'表示指定将test_fetch的2019分区下载到本地,并保存到对应数据表的detached目录下,目录如下所示:data/default/test_fetch/detached/2019_0_0_0

与FREEZE一样,对于备份分区的还原操作,也需要借助ATTACH装载分区来实现。

FREEZE和FETCH虽然都能实现对分区文件的备份,但是它们并不会备份数据表的元数据。所以说如果想做到万无一失的备份,还需要对数据表的元数据进行备份,它们是/path_to_clickhouse_data_root_dir/metadata目录下的[table].sql文件。目前这些元数据需要用户通过复制的形式单独备份。

※,11.5 服务监控

基于原生功能对ClickHouse进行监控,可以从两方面着手——系统表和查询日志。

系统表

在众多的SYSTEM系统表中,主要由以下三张表支撑了对ClickHouse运行指标的查询,它们分别是metrics、events和asynchronous_metrics。

1.metrics

metrics表用于统计ClickHouse服务在运行时,当前正在执行的高层次的概要信息,包括正在执行的查询总次数、正在发生的合并操作总次数等。该系统表的查询方法如下所示

SELECT * FROM system.metrics LIMIT 5
┌─metric──────┬─value─┬─description─────────────────────┐
│ Query │ 1 │ Number of executing queries │
│ Merge │ 0 │ Number of executing background merges │
│ PartMutation │ 0 │ Number of mutations (ALTER DELETE/UPDATE) │
│ ReplicatedFetch │ 0 │ Number of data parts being fetched from replica │
│ ReplicatedSend │ 0 │ Number of data parts being sent to replicas
└──────────┴─────┴─────────────────────────────┘

2.events

events用于统计ClickHouse服务在运行过程中已经执行过的高层次的累积概要信息,包括总的查询次数、总的SELECT查询次数等,该系统表的查询方法如下所示:

SELECT event, value FROM system.events LIMIT 5
┌─event─────────────────────┬─value─┐
│ Query │ 165 │
│ SelectQuery │ 92 │
│ InsertQuery │ 14 │
│ FileOpen │ 3525 │
│ ReadBufferFromFileDescriptorRead │ 6311 │
└─────────────────────────┴─────┘

3.asynchronous_metrics

asynchronous_metrics用于统计ClickHouse服务运行过程时,当前正在后台异步运行的高层次的概要信息,包括当前分配的内存、执行队列中的任务数量等。该系统表的查询方法如下所示:

SELECT * FROM system.asynchronous_metrics LIMIT 5
┌─metric────────────────────────┬─value─┬─description────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
1. │ OSGuestTimeCPU1               │     0 │ The ratio of time spent running a virtual CPU for guest operating systems under the control of the Linux kernel (See `man procfs`). This is a system-wide metric, it includes all the processes on the host machine, not just clickhouse-server. This metric is irrelevant for ClickHouse, but still exists for completeness. The value for a single CPU core will be in the interval [0..1]. The value for all CPU cores is calculated as a sum across them [0..num cores]. │
2. │ ReplicasMaxQueueSize          │     0 │ Maximum queue size (in the number of operations like get, merge) across Replicated tables.                                                                                                                                                                 │
3. │ jemalloc.prof.active          │     1 │ An internal metric of the low-level memory allocator (jemalloc). See https://jemalloc.net/jemalloc.3.html                                                                                                                                                  │
4. │ NetworkSendErrors_veth5a6b48e │     0 │  Number of times error (e.g. TCP retransmit) happened while sending via the network interface. This is a system-wide metric, it includes all the processes on the host machine, not just clickhouse-server.                                                │
5. │ OSIOWaitTimeCPU3              │     0 │ The ratio of time the CPU core was not running the code but when the OS kernel did not run any other process on this CPU as the processes were waiting for IO. This is a system-wide metric, it includes all the processes on the host machine, not just clickhouse-server. The value for a single CPU core will be in the interval [0..1]. The value for all CPU cores is calculated as a sum across them [0..num cores]. │
   └───────────────────────────────┴───────┴────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘

查询日志

查询日志目前主要有6种类型,它们分别从不同角度记录了ClickHouse的操作行为。所有查询日志在默认配置下都是关闭状态(新版好像不是了),需要在config.xml配置中进行更改,接下来分别介绍它们的开启方法。在配置被开启之后,ClickHouse会为每种类型的查询日志自动生成相应的系统表以供查询。

1.query_log

query_log是最常用的查询日志,它记录了ClickHouse服务中所有已经执行的查询记录,它的全局定义方式如下所示:

<!-- config.xml -->
<clickhouse>
    <query_log>
        <database>system</database>
        <table>query_log</table>
        <partition_by>toYYYYMM(event_date)</partition_by>
        <!—刷新周期-->
        <flush_interval_milliseconds>7500</flush_interval_milliseconds>
    </query_log>
</clickhouse>

如果只需要为某些用户单独开启query_log,也可以在user.xml的profile配置中按照下面的方式定义:<log_queries> 1</log_queries>

system.query_log日志记录的信息十分完善,涵盖了查询语句、执行时间、执行用户返回的数据量和执行用户等。

2.query_thread_log

query_thread_log记录了所有线程的执行查询的信息,它的全局定义方式如下所示:

<!-- config.xml -->
<clickhouse>
    <query_thread_log>
        <database>system</database>
        <table>query_thread_log</table>
        <partition_by>toYYYYMM(event_date)</partition_by>
        <flush_interval_milliseconds>7500</flush_interval_milliseconds>
    </query_thread_log>
</clickhouse>

同样,如果只需要为某些用户单独开启该功能,可以在user.xml的profile配置中按照下面的方式定义:<log_query_threads> 1</log_query_threads>

system.query_thread_log日志记录的信息涵盖了线程名称、查询语句、执行时间和内存用量等。

3.part_log

part_log日志记录了MergeTree系列表引擎的分区操作日志,其全局定义方式如下所示:

<!-- config.xml -->
<clickhouse>
    <part_log>
        <database>system</database>
        <table>part_log</table>
        <flush_interval_milliseconds>7500</flush_interval_milliseconds>
    </part_log>
</clickhouse>

system.part_log日志记录的信息涵盖了操纵类型、表名称、分区信息和执行时间等。

4.text_log

text_log日志记录了ClickHouse运行过程中产生的一系列打印日志,包括INFO、DEBUG和Trace,它的全局定义方式如下所示:

<!-- config.xml -->
<clickhouse>
    <text_log>
        <database>system</database>
        <table>text_log</table>
        <flush_interval_milliseconds>7500</flush_interval_milliseconds>
    </text_log>
</clickhouse>

system.text_log日志记录的信息涵盖了线程名称、日志对象、日志信息和执行时间等。

5.metric_log

 metric_log日志用于将system.metrics和system.events中的数据汇聚到一起,它的全局定义方式如下所示:

<!-- config.xml -->
<clickhouse>
    <metric_log>
        <database>system</database>
        <table>metric_log</table>
        <flush_interval_milliseconds>7500</flush_interval_milliseconds>
        <collect_interval_milliseconds>1000</collect_interval_milliseconds>
    </metric_log>
</clickhouse>

 其中,collect_interval_milliseconds表示收集metrics和events数据的时间周期。

除了上面介绍的系统表和查询日志外,ClickHouse还能够与众多的第三方监控系统集成,限于篇幅这里就不再展开了。

================结束标记=================