跳至主要内容

·阅读时长 3 分钟

您在 MergeTree 表上达到了 parts_to_throw_insert 设置。您可以使用以下命令监视给定表的活动部分数量:

select count(*) from system.parts where table = '<table_name>' and active == 1

插入 Clickhouse 的主要要求:您永远不应该每秒发送太多 INSERT 语句。理想情况下 - 每秒/每几秒插入一次。

因此,您可以每秒插入 100K 行,但只能使用一个大的批量 INSERT 语句。当您每秒向 *MergeTree 表发送数百/数千个插入语句时,您将始终遇到一些错误,并且无法通过调整某些设置来更改它。

如果您无法将大量插入组合到 *MergeTree 表之外的一个大型批量插入语句中 - 那么您应该在 *MergeTree 表之前创建缓冲表。

  1. 每个插入在 /var/lib/clickhouse/.../table_name/ 中创建一个文件夹。在该文件夹中,每列有两个文件 - 一个包含数据(压缩),另一个包含索引。数据在这些文件内部按主键物理排序。这些文件夹称为“**部分**”。

  2. ClickHouse 在后台将这些较小的部分合并为较大的部分。它根据一些规则选择要合并的部分。合并两个(或多个)部分后,将创建一个较大的部分,旧部分将排队以供删除。您列出的设置允许微调合并部分的规则。合并过程的目标 - 是为每个分区留下一个大部分(或每个分区几个大部分,因为它们太大,不值得合并)。请查看 评论

  3. 如果您创建新部分的速度过快(例如,通过进行大量小插入),并且 ClickHouse 无法以适当的速度合并它们(因此新部分比 ClickHouse 可以合并它们的速度更快出现) - 那么您会收到异常“合并处理速度明显慢于插入”。您可以尝试增加限制,但您可能会遇到这种情况,然后您会遇到由过多的文件/目录(如 inode 限制)引起的系统问题。

  4. 如果您同时插入多个分区,则该问题会按受插入影响的分区数成倍增加。

  5. 您可以尝试使用列出的设置之一或使用 max_insert_block_size / max_block_size / insert_format_max_block_size / max_client_network_bandwidth 来调整 clickhouse 的行为。但是:更好的解决方案是按预期速度插入数据。预期速度是:**每 1-2 秒插入一次,每次插入包含 10K-500K 行数据**。

  6. 因此,解决“合并处理速度明显慢于插入”的正确方法是调整每秒的插入次数和每次插入的行数。如果数据是逐行出现的,则使用批量插入将小插入组合成一个更大的插入。如果您一次有太多数据要插入,请限制巨大的插入。不要更改 clickhouse 内部机制,除非您真正理解它们意味着什么。

  7. 如果您的数据传输速度超过每秒 500K 行 - 您很可能需要在集群中添加更多服务器来处理该流量,而不是调整设置。

  8. 后台合并的速度通常取决于存储速度、使用的压缩设置、MergeTree 选项(合并算法 - 简单合并/聚合/求和/折叠等)以及使用的排序键。

·阅读时长 1 分钟

ClickHouse 支持各种各样的 用于输入和输出的数据格式。它们之间有几种 JSON 变体,但数据导入中最常用的变体是 JSONEachRow。它期望每行一个 JSON 对象,每个对象用换行符分隔。

示例

使用 HTTP 接口

$ echo '{"foo":"bar"}' | curl 'https://127.0.0.1:8123/?query=INSERT%20INTO%20test%20FORMAT%20JSONEachRow' --data-binary @-

使用 CLI 接口

$ echo '{"foo":"bar"}'  | clickhouse-client --query="INSERT INTO test FORMAT JSONEachRow"

除了手动插入数据,您还可以考虑使用其中一个 客户端库 来代替。

有用设置

  • input_format_skip_unknown_fields 允许插入 JSON,即使表模式中没有其他字段(通过丢弃它们)。
  • input_format_import_nested_json 允许将嵌套 JSON 对象插入 嵌套 类型的列中。
注意

设置被指定为 HTTP 接口的 GET 参数,或作为 CLI 接口的以 -- 为前缀的附加命令行参数。

·阅读时长:4 分钟

以下是一些便于 ClickHouse 排错并找出问题根源的实用查询。我们还提供一篇关于 ClickHouse 监控所需的一些必要查询 的博文。

查看哪些设置已从默认值更改

SELECT
name,
value
FROM system.settings
WHERE changed

获取所有表的尺寸

SELECT table,
formatReadableSize(sum(bytes)) as size
FROM system.parts
WHERE active
GROUP BY table

响应如下所示

┌─table───────────┬─size──────┐
│ stat │ 38.89 MiB │
│ customers │ 525.00 B │
│ my_sparse_table │ 40.73 MiB │
│ crypto_prices │ 32.18 MiB │
│ hackernews │ 6.23 GiB │
└─────────────────┴───────────┘

表的行数和平均日大小

SELECT
table,
formatReadableSize(size) AS size,
rows,
days,
formatReadableSize(avgDaySize) AS avgDaySize
FROM
(
SELECT
table,
sum(bytes) AS size,
sum(rows) AS rows,
min(min_date) AS min_date,
max(max_date) AS max_date,
max_date - min_date AS days,
size / (max_date - min_date) AS avgDaySize
FROM system.parts
WHERE active
GROUP BY table
ORDER BY rows DESC
)

压缩列的百分比以及主索引在内存中的大小

您可以查看按列压缩数据的程度。此查询还返回主索引在内存中的大小,这对于了解主索引是否必须位于内存中很有用。

SELECT
parts.*,
columns.compressed_size,
columns.uncompressed_size,
columns.compression_ratio,
columns.compression_percentage
FROM
(
SELECT
table,
formatReadableSize(sum(data_uncompressed_bytes)) AS uncompressed_size,
formatReadableSize(sum(data_compressed_bytes)) AS compressed_size,
round(sum(data_compressed_bytes) / sum(data_uncompressed_bytes), 3) AS compression_ratio,
round(100 - ((sum(data_compressed_bytes) * 100) / sum(data_uncompressed_bytes)), 3) AS compression_percentage
FROM system.columns
GROUP BY table
) AS columns
RIGHT JOIN
(
SELECT
table,
sum(rows) AS rows,
max(modification_time) AS latest_modification,
formatReadableSize(sum(bytes)) AS disk_size,
formatReadableSize(sum(primary_key_bytes_in_memory)) AS primary_keys_size,
any(engine) AS engine,
sum(bytes) AS bytes_size
FROM system.parts
WHERE active
GROUP BY
database,
table
) AS parts ON columns.table = parts.table
ORDER BY parts.bytes_size DESC

客户端在过去 10 分钟内发送的查询数

请随意在 toIntervalMinute(10) 函数中增加或减少时间间隔

SELECT
client_name,
count(),
query_kind,
toStartOfMinute(event_time) AS event_time_m
FROM system.query_log
WHERE (type = 'QueryStart') AND (event_time > (now() - toIntervalMinute(10)))
GROUP BY
event_time_m,
client_name,
query_kind
ORDER BY
event_time_m DESC,
count() ASC

每个分区中的分区数

SELECT
concat(database, '.', table),
partition_id,
count()
FROM system.parts
WHERE active
GROUP BY
database,
table,
partition_id

查找运行时间长的查询

这有助于查找卡住的查询

SELECT
elapsed,
initial_user,
client_name,
hostname(),
query_id,
query
FROM clusterAllReplicas(default, system.processes)
ORDER BY elapsed DESC

使用运行时间最长的查询的查询 ID,我们可以获取一个堆栈跟踪,这在调试时很有用。

SET allow_introspection_functions=1;

SELECT
arrayStringConcat(
arrayMap(
x,
y -> concat(x, ': ', y),
arrayMap(x -> addressToLine(x), trace),
arrayMap(x -> demangle(addressToSymbol(x)), trace)
),
'\n'
) as trace
FROM
system.stack_trace
WHERE
query_id = '0bb6e88b-9b9a-4ffc-b612-5746c859e360';

查看最新的错误

SELECT *
FROM system.errors
ORDER BY last_error_time DESC

响应如下所示

┌─name──────────────────┬─code─┬─value─┬─────last_error_time─┬─last_error_message──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┬─last_error_trace─┬─remote─┐
│ UNKNOWN_TABLE │ 60 │ 3 │ 2023-03-14 01:02:35 │ Table system.stack_trace doesn't exist │ [] │ 0 │
│ BAD_GET │ 170 │ 1 │ 2023-03-14 00:58:55 │ Requested cluster 'default' not found │ [] │ 0 │
│ UNKNOWN_IDENTIFIER │ 47 │ 1 │ 2023-03-14 00:49:12 │ Missing columns: 'parts.table' 'table' while processing query: 'table = parts.table', required columns: 'table' 'parts.table' 'table' 'parts.table' │ [] │ 0 │
│ NO_ELEMENTS_IN_CONFIG │ 139 │ 2 │ 2023-03-14 00:42:11 │ Certificate file is not set. │ [] │ 0 │
└───────────────────────┴──────┴───────┴─────────────────────┴─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┴──────────────────┴────────┘

使用 CPU 和内存最多的前 10 个查询

SELECT
type,
event_time,
initial_query_id,
formatReadableSize(memory_usage) AS memory,
`ProfileEvents.Values`[indexOf(`ProfileEvents.Names`, 'UserTimeMicroseconds')] AS userCPU,
`ProfileEvents.Values`[indexOf(`ProfileEvents.Names`, 'SystemTimeMicroseconds')] AS systemCPU,
normalizedQueryHash(query) AS normalized_query_hash
FROM system.query_log
ORDER BY memory_usage DESC
LIMIT 10

我的投影使用了多少磁盘空间

SELECT
name,
parent_name,
formatReadableSize(bytes_on_disk) AS bytes,
formatReadableSize(parent_bytes_on_disk) AS parent_bytes,
bytes_on_disk / parent_bytes_on_disk AS ratio
FROM system.projection_parts

显示磁盘存储、分区数、system.parts 和标记中的行数跨数据库

SELECT
database,
table,
partition,
count() AS parts,
formatReadableSize(sum(bytes_on_disk)) AS bytes_on_disk,
formatReadableQuantity(sum(rows)) AS rows,
sum(marks) AS marks
FROM system.parts
WHERE (database != 'system') AND active
GROUP BY
database,
table,
partition
ORDER BY database ASC

列出最近写入的新分区的详细信息

详细信息包括它们何时创建、它们的大小、行数等等。

SELECT
modification_time,
rows,
formatReadableSize(bytes_on_disk),
*
FROM clusterAllReplicas(default, system.parts)
WHERE (database = 'default') AND active AND (level = 0)
ORDER BY modification_time DESC
LIMIT 100

·阅读时长 3 分钟

快乐圆周率日!我们认为使用 ClickHouse 中的 SQL 查询来计算圆周率会很有趣。以下是我们目前取得的结果...

  1. 此查询使用 ClickHouse numbers_mt 表函数返回 10 亿行,计算仅需 40 毫秒
SELECT 4 * sum(if(number % 2, -1, 1) / ((number * 2) + 1)) AS pi
FROM numbers_mt(1000000000.)

┌────────────────pi─┐
3.141592652589797
└───────────────────┘

1 row in set. Elapsed: 0.432 sec. Processed 1.00 billion rows, 8.00 GB (2.32 billion rows/s., 18.53 GB/s.)
  1. 以下示例也处理了 10 亿个数字,只是没有那么快
SELECT 3 + (4 * sum(if((number % 2) = 0, if((number % 4) = 0, -1 / ((number * (number + 1)) * (number + 2)), 1 / ((number * (number + 1)) * (number + 2))), 0))) AS pi
FROM numbers_mt(2, 10000000000)

┌─────────────────pi─┐
3.1415926525808087
└────────────────────┘

1 row in set. Elapsed: 9.825 sec. Processed 10.00 billion rows, 80.00 GB (1.02 billion rows/s., 8.14 GB/s.)
  1. 这个是我们最喜欢的 ClickHouse 查询(也是最准确的!)
SELECT pi()

┌──────────────pi()─┐
3.141592653589793
└───────────────────┘

1 row in set. Elapsed: 0.008 sec.
  1. 有人知道这个查询的三角函数知识
SELECT 2 * asin(1) AS pi

┌────────────────pi─┐
3.141592653589793
└───────────────────┘

1 row in set. Elapsed: 0.005 sec.
  1. 这是一个方便的 API,可以指定您想要的位数
SELECT *
FROM url('https://api.pi.delivery/v1/pi?start=0&numberOfDigits=100', 'JSONEachRow')

┌───────────────content─┐
3.1415926535897933e99 │
└───────────────────────┘

1 row in set. Elapsed: 0.556 sec.
  1. 这个很聪明 - 它使用 ClickHouse 距离函数
WITH random_points AS
(
SELECT (rand64(1) / pow(2, 64), rand64(2) / pow(2, 64)) AS point
FROM numbers(1000000000)
)
SELECT (4 * countIf(L2Norm(point) < 1)) / count() AS pi
FROM random_points


┌──────────pi─┐
3.141627208
└─────────────┘

1 row in set. Elapsed: 4.742 sec. Processed 1.00 billion rows, 8.00 GB (210.88 million rows/s., 1.69 GB/s.)
  1. 如果您是物理学家,您会对这个感到满意
SELECT 22 / 7

┌─────divide(22, 7)─┐
3.142857142857143
└───────────────────┘
  1. 另一个间接方法(这个来自 Alexey Milovidov),精确到小数点后 7 位 - 而且很快
WITH
10 AS length,
(number / 1000000000.) * length AS x
SELECT pow((2 * length) * avg(exp(-(x * x))), 2) AS pi
FROM numbers_mt(1000000000.)


┌─────────────────pi─┐
3.1415926890388595
└────────────────────┘

1 row in set. Elapsed: 1.245 sec. Processed 1.00 billion rows, 8.00 GB (803.25 million rows/s., 6.43 GB/s.)
注意

如果您还有其他方法,欢迎您分享。谢谢!

·阅读时长:2 分钟

问题:当源表中插入了新行时,这些新行也会发送到该源表的全部物化视图。物化视图的插入操作是同步执行的吗?也就是说,一旦服务器向客户端确认插入成功,是否就意味着所有物化视图都已完全更新并可供查询?

回答

  1. INSERT 成功时,数据会被插入到表和所有物化视图中。
  2. 插入操作相对于物化视图而言不是原子的。在 INSERT 正在执行的时刻,并发客户端可能会看到中间状态,此时数据已被插入到主表中,但尚未插入到物化视图中,反之亦然。
  3. 如果您使用的是 异步插入,它们会收集数据并在后台执行常规插入操作,向客户端返回与常规插入操作相同类型的答案。如果客户端从带有 wait_for_async_insert 选项(默认情况下)的异步插入操作中收到成功确认,则数据将被插入到表及其所有物化视图中。

问题:链式/级联物化视图呢?

回答:相同的规则适用 - INSERT 操作成功响应意味着数据已插入到链中的每个物化视图中。插入操作是非原子的。

·阅读时长:4 分钟

通常情况下,max_threads 设置 控制 并发读取线程和并发查询处理线程的数量

Untitled scene

数据按顺序(从磁盘)读取,依次读取每一列。

异步数据读取

新设置 allow_asynchronous_read_from_io_pool_for_merge_tree 允许读取线程(流)的数量超过查询执行管道中其余线程的数量,从而加快低 CPU ClickHouse 云服务上的冷查询速度,并提高 I/O 密集型查询的性能。启用此设置后,读取线程的数量由 max_streams_for_merge_tree_reading 设置控制。

Untitled scene

数据异步读取,从不同的列并行读取。

请注意,还有 max_streams_to_max_threads_ratio 设置用于配置读取线程(流)数量与查询执行管道中其余线程数量的比率。但是,在基准测试中,它并没有像 max_streams_for_merge_tree_reading 设置那样有效。

optimize_read_in_order 呢?

使用 optimize_read_in_order 优化,ClickHouse 可以 跳过 在内存中对数据进行排序,如果查询排序顺序反映了磁盘上数据的物理顺序,但这要求按顺序读取数据(与异步读取相反)

Untitled scene

optimize_read_in_order 优先于异步读取

当 ClickHouse 发现可以应用 optimize_read_in_order 优化 时,将忽略/禁用 allow_asynchronous_read_from_io_pool_for_merge_tree 设置。

演示所有上述内容的示例

  • 创建并加载 英国房价表

  • 检查 max_threads 的设置值(默认情况下,是 ClickHouse 在执行查询的节点上看到的 CPU 内核数量)。

SELECT getSetting('max_threads');


┌─getSetting('max_threads')─┐
│ 10 │
└───────────────────────────┘
  • 检查使用默认线程数进行读取和处理数据的查询管道
EXPLAIN PIPELINE
SELECT *
FROM uk_price_paid;

┌─explain──────────────────────┐
│ (Expression) │
│ ExpressionTransform × 10 │
│ (ReadFromMergeTree) │
│ MergeTreeThread × 10 0 → 1 │
└──────────────────────────────┘
  • 检查使用 60 个异步读取线程和默认线程数进行其余查询执行管道的查询管道
EXPLAIN PIPELINE
SELECT *
FROM uk_price_paid
SETTINGS
allow_asynchronous_read_from_io_pool_for_merge_tree = 1,
max_streams_for_merge_tree_reading = 60;


┌─explain────────────────────────┐
│ (Expression) │
│ ExpressionTransform × 10 │
│ (ReadFromMergeTree) │
│ Resize 60 → 10 │
│ MergeTreeThread × 60 0 → 1 │
└────────────────────────────────┘
  • 检查使用 20 个线程进行读取和处理数据的查询管道
EXPLAIN PIPELINE
SELECT *
FROM uk_price_paid
SETTINGS
max_threads = 20;


┌─explain──────────────────────┐
│ (Expression) │
│ ExpressionTransform × 20 │
│ (ReadFromMergeTree) │
│ MergeTreeThread × 20 0 → 1 │
└──────────────────────────────┘
  • 检查使用 60 个异步读取线程和 20 个线程进行其余查询执行管道的查询管道
EXPLAIN PIPELINE
SELECT *
FROM uk_price_paid
SETTINGS
max_threads = 20,
allow_asynchronous_read_from_io_pool_for_merge_tree = 1,
max_streams_for_merge_tree_reading = 60;


┌─explain────────────────────────┐
│ (Expression) │
│ ExpressionTransform × 20 │
│ (ReadFromMergeTree) │
│ Resize 60 → 20 │
│ MergeTreeThread × 60 0 → 1 │
└────────────────────────────────┘
  • 当可以应用 optimize_read_in_order 优化 时,检查使用 60 个异步读取线程和 20 个线程进行其余查询执行管道的查询管道
EXPLAIN PIPELINE
SELECT *
FROM uk_price_paid
ORDER BY postcode1, postcode2
SETTINGS
max_threads = 20,
allow_asynchronous_read_from_io_pool_for_merge_tree= 1,
max_streams_for_merge_tree_reading= 60;


┌─explain───────────────────────────┐
│ (Expression) │
│ ExpressionTransform │
│ (Sorting) │
│ MergingSortedTransform 20 → 1 │
│ (Expression) │
│ ExpressionTransform × 20 │
│ (ReadFromMergeTree) │
│ MergeTreeInOrder × 20 0 → 1 │
└───────────────────────────────────┘


-- note that this is equivalent to disabling allow_asynchronous_read_from_io_pool_for_merge_tree

EXPLAIN PIPELINE
SELECT *
FROM uk_price_paid
ORDER BY postcode1, postcode2
SETTINGS
max_threads = 20,
allow_asynchronous_read_from_io_pool_for_merge_tree = 0,
max_streams_for_merge_tree_reading = 0;


┌─explain───────────────────────────┐
│ (Expression) │
│ ExpressionTransform │
│ (Sorting) │
│ MergingSortedTransform 20 → 1 │
│ (Expression) │
│ ExpressionTransform × 20 │
│ (ReadFromMergeTree) │
│ MergeTreeInOrder × 20 0 → 1 │
└───────────────────────────────────┘

-- note that you can enforce allow_asynchronous_read_from_io_pool_for_merge_tree by disabling optimize_read_in_order

EXPLAIN PIPELINE
SELECT *
FROM uk_price_paid
ORDER BY
postcode1 ASC,
postcode2 ASC
SETTINGS
max_threads = 20,
allow_asynchronous_read_from_io_pool_for_merge_tree = 1,
max_streams_for_merge_tree_reading = 60,
optimize_read_in_order = 0;


┌─explain──────────────────────────────┐
│ (Expression) │
│ ExpressionTransform │
│ (Sorting) │
│ MergingSortedTransform 20 → 1 │
│ MergeSortingTransform × 20 │
│ (Expression) │
│ ExpressionTransform × 20 │
│ (ReadFromMergeTree) │
│ Resize 60 → 20 │
│ MergeTreeThread × 60 0 → 1 │
└──────────────────────────────────────┘


·阅读时长:2 分钟

在 ClickHouse 中,有几种方法可以为用户定义设置,具体取决于用例和您希望设置配置多长时间。让我们看看几个场景...

为单个查询配置设置

SELECT 查询可以包含一个 SETTINGS 子句,您可以在其中定义任意数量的设置。这些设置仅对该特定查询有效。例如

SELECT *
FROM my_table
SETTINGS max_threads = 8;

此特定查询的线程数上限为 8。

为会话配置设置

您可以使用 SET 子句为客户端会话的整个生命周期定义设置。这对于临时测试或您希望设置持续几个查询的生命周期(但不要更长时间)的情况非常有用。

SET max_threads = 8;

SELECT *
FROM my_table;

为特定用户配置设置

使用 ALTER USER 为单个用户定义设置。例如

ALTER USER my_user_name SETTINGS max_threads = 8;

您可以通过注销客户端,然后重新登录,并使用 getSetting 函数来验证它是否有效。

SELECT getSetting('max_threads');

·阅读时长 1 分钟

为了在 ClickHouse Cloud 服务的所有节点上执行相同的查询,我们可以使用 clusterAllReplicas

例如,为了从所有节点的(节点本地)系统表中获取条目,您可以使用

SELECT ... FROM clusterAllReplicas(default, system.TABLE) ...;

类似地,您可以使用 ON CLUSTER 子句,通过单个语句在所有节点上执行相同的 SYSTEM 语句

SYSTEM ... ON CLUSTER default;

例如,要从所有节点 删除文件系统缓存,您可以使用

SYSTEM DROP FILESYSTEM CACHE ON CLUSTER default;

·阅读时长 1 分钟

ClickHouse 提供了一种简单直观的方式来编写过滤聚合。例如,将标准 SQL 方式编写过滤聚合(在 ClickHouse 中运行良好)与使用 -If 聚合函数组合器 的简写语法进行比较,该组合器可以附加到任何聚合函数

--standard SQL
SELECT
avg(number)
FILTER (WHERE number > 50)
FROM numbers(100)

--ClickHouse using an aggregate combinator
SELECT
avgIf(number, number > 50)
FROM numbers(100)

类似地,存在一个 -Distinct 聚合组合器

--standard SQL
SELECT avg(DISTINCT number)

--ClickHouse using an aggregate combinator
SELECT avgDistinct(number)

为什么过滤聚合很重要?因为它们允许您在 Web 分析服务中实现“段比较”功能。例如

WITH
Region = 'us' AS segment1,
Browser = 'Chrome' AS segment2
SELECT
uniqIf(UserID, segment1),
uniqIf(UserID, segment2)
WHERE segment1 OR segment2

查看文档中的 聚合函数组合器 页面了解更多详情。

·阅读时长 1 分钟

ClickHouse 使用全局线程池中的线程来处理查询,并执行合并和变异等后台操作。如果没有空闲线程来处理查询,则会在池中创建一个新线程。

全局线程池的最大大小由 max_thread_pool_size 设置确定,默认值为 10,000。您可以在配置文件中修改此值 - 在这里我们将其设置为 20,000

<max_thread_pool_size>20000</max_thread_pool_size>

如果您修改了 max_thread_pool_size,我们建议将 thread_pool_queue_size 设置为相同的值。thread_pool_queue_size 设置是可以在全局线程池上调度的作业的最大数量

<thread_pool_queue_size>20000</thread_pool_queue_size>

如果您的服务器有大量空闲线程,您也可以释放资源 - 使用 max_thread_pool_free_size 设置。默认值为 1,000,这意味着您的全局线程池永远不会超过 1,000 个空闲线程。以下示例将值增加到 2,000

<max_thread_pool_free_size>2000</max_thread_pool_free_size>

查看 文档,了解有关上述设置以及影响全局线程池的其他设置的更多信息。