当前位置: 首页 > news >正文

ClickHouse多表join的性能优化:原理与源码详解

        本文将从底层原理和源代码的角度详细解释 ClickHouse 多表 JOIN 的处理过程,尽量用通俗移动的语言,让初学者也能理解。本文会分步骤推导,涵盖 JOIN 的原理、实现方式、关键代码逻辑以及优化机制,同时确保逻辑清晰、内容全面。最后给出具体的优化方法


1. 什么是 JOIN 以及 ClickHouse 中的 JOIN

1.1 JOIN 的基本概念

        在数据库中,JOIN 是一种操作,用于将多个表的数据根据某些条件(通常是键值匹配)合并在一起,形成一个结果集。例如:

  • 有两张表:users(存储用户ID和姓名)和 orders(存储订单ID和用户ID)。
  • 我们想查询每个用户的订单信息,就需要通过 user_id 将两张表“连接”起来。

常见的 JOIN 类型包括:

  • INNER JOIN:只返回两表中匹配的行。
  • LEFT JOIN:保留左表所有行,右表匹配不到的用 NULL 填充。
  • RIGHT JOIN:保留右表所有行,左表匹配不到的用 NULL 填充。
  • FULL JOIN:保留两表所有行,匹配不到的用 NULL 填充。
  • CROSS JOIN:两表行的笛卡尔积(每行与每行组合)。

        ClickHouse 支持以上 JOIN 类型,但它的 JOIN 实现有自己的特点,因为 ClickHouse 是一个列式存储的分析型数据库,优化目标是高性能、大数据量处理。

1.2 ClickHouse JOIN 的特点

ClickHouse 的 JOIN 有以下关键特性:

  1. 列式存储:ClickHouse 按列存储数据,JOIN 操作需要处理列之间的匹配,而不是传统的行式数据库那样逐行处理。
  2. 分布式处理:ClickHouse 通常运行在集群上,JOIN 可能涉及跨节点的分布式计算。
  3. 内存优化:ClickHouse 倾向于将 JOIN 的右表(通常是较小的表)加载到内存中,以加速匹配。
  4. 多种 JOIN 算法:ClickHouse 支持多种 JOIN 算法(如 Hash Join、Merge Join),根据数据大小和分布选择最优算法。
  5. 严格的语法限制:ClickHouse 的 JOIN 语法要求右表明确指定,且不支持复杂的子查询作为 JOIN 表。

2. ClickHouse 多表 JOIN 的底层原理

为了让初学者理解,我们先从概念入手,逐步深入到代码层面。

2.1 JOIN 的核心步骤

无论是什么类型的 JOIN,其核心步骤可以简化为:

  1. 读取数据:从左表和右表读取需要 JOIN 的列。
  2. 匹配键:根据 JOIN 条件(通常是 ON 子句中的键)找到匹配的行。
  3. 合并结果:根据 JOIN 类型(INNER、LEFT 等)构造结果集。
  4. 优化执行:通过索引、内存管理、并行处理等优化性能。

在 ClickHouse 中,这些步骤被分解为更细粒度的操作,结合列式存储和分布式架构。

2.2 列式存储对 JOIN 的影响

        传统行式数据库(如 MySQL)存储数据时,每行是一个完整的记录,JOIN 时直接比较整行。ClickHouse 是列式存储,数据按列组织,例如:

  • users 表可能有两列:user_id 和 name,分别存储在不同的文件中。
  • JOIN 时,ClickHouse 只加载 user_id 和 JOIN 所需的列,而不是整个表。

这带来两个优势:

  1. 减少 I/O:只需要读取 JOIN 相关的列,节省磁盘和内存开销。
  2. 向量化执行:ClickHouse 可以批量处理列数据,利用 CPU 的向量化指令(SIMD)加速计算。

2.3 JOIN 算法

ClickHouse 主要使用以下 JOIN 算法:

  1. Hash Join
    • 将右表(通常较小)加载到内存,构建一个哈希表,键是 JOIN 条件中的字段。
    • 遍历左表的数据,用哈希表快速查找匹配的行。
    • 适用于右表较小、内存足够的情况。
  2. Merge Join
    • 要求两表的 JOIN 键已排序
    • 类似“拉链”式合并,逐行比较两表的键。
    • 适合大数据量且键已排序的场景。
  3. Nested Loop Join
    • 对左表的每行,扫描右表的每一行,检查是否匹配。
    • 效率低,仅在特殊场景(如小表或无法使用哈希表)使用。

ClickHouse 默认优先选择 Hash Join,因为它在内存充足时性能最高。以下是 Hash Join 的伪代码:

// 构建右表的哈希表
HashMap<key, row> hash_table;
for each row in right_table:key = row[join_key];hash_table[key].append(row);// 遍历左表,查找匹配
for each row in left_table:key = row[join_key];if hash_table.contains(key):for each matched_row in hash_table[key]:output(row, matched_row);

3. ClickHouse 多表 JOIN 的执行流程

我们以一个具体的例子,详细推导 ClickHouse 如何处理多表 JOIN。假设有以下查询:

SELECT u.name, o.order_id
FROM users u
INNER JOIN orders o ON u.user_id = o.user_id
INNER JOIN payments p ON o.order_id = p.order_id;

这个查询涉及三表:users、orders 和 payments,通过 user_id 和 order_id 进行连接。

3.1 解析查询

        ClickHouse 首先解析 SQL,生成一个抽象语法树(AST)。在 AST 中,JOIN 被表示为一个操作节点,包含:

  • 左表和右表的引用。
  • JOIN 类型(这里是 INNER JOIN)。
  • JOIN 条件(u.user_id = o.user_id 和 o.order_id = p.order_id)。

解析后的 AST 会交给查询优化器,优化器会:

  1. 确定 JOIN 的执行顺序(例如,先 users 和 orders 连接,再和 payments 连接)。
  2. 选择合适的 JOIN 算法(通常是 Hash Join)。
  3. 决定哪些列需要读取。

3.2 确定 JOIN 顺序

多表 JOIN 需要决定执行顺序。ClickHouse 的优化器会根据以下因素选择顺序:

  • 表的大小:优先将小表作为右表,加载到内存。
  • 统计信息:ClickHouse 可能利用表的统计数据(如行数、数据分布)估算代价。
  • JOIN 条件:确保 JOIN 键的高选择性(即匹配的行数较少)。

        在上面的例子中,假设 users 有 1000 万行,orders 有 5000 万行,payments 有 2000 万行。优化器可能选择:

  1. 先执行 users 和 orders 的 JOIN,因为 users 较小,可以作为右表加载到内存。
  2. 再将结果与 payments 连接。

优化后的执行计划可能是:

(users INNER JOIN orders) INNER JOIN payments

3.3 读取数据

ClickHouse 按列读取数据。对于查询:

SELECT u.name, o.order_id
FROM users u
INNER JOIN orders o ON u.user_id = o.user_id
INNER JOIN payments p ON o.order_id = p.order_id;

需要读取的列是:

  • users:user_id(JOIN 键)、name(输出)。
  • orders:user_id(JOIN 键)、order_id(JOIN 键和输出)。
  • payments:order_id(JOIN 键)。

ClickHouse 使用存储引擎(如 MergeTree)读取这些列。读取过程:

  1. 根据表的元数据,找到存储 user_id、name 等列的文件。
  2. 利用索引(如果有配置)跳过无关的数据块。
  3. 将数据加载到内存,准备 JOIN。

3.4 执行 JOIN

我们以 users 和 orders 的 JOIN 为例,假设使用 Hash Join:

  1. 构建哈希表

    • ClickHouse 读取 orders 表的 user_id 和 order_id 列。
    • 为每行计算 user_id 的哈希值,存储到哈希表中。
    • 哈希表的键是 user_id,值是对应的 order_id 列表。
    • 代码逻辑(简化伪):
      HashMap<UInt64, Vector<Row>> hash_table;
      for each row in orders:key = row["user_id"];hash_table[key].push_back(row);
      
  2. 探测阶段

    • 读取 users 表的 user_id 和 name 列。
    • 对每行的 user_id,在哈希表中查找匹配的 orders 行。
    • 如果找到匹配(INNER JOIN),将 users 的 name 和 orders 的 order_id 合并到结果集中。
    • 代码逻辑(简化伪):
      for each row in users:key = row["user_id"];if hash_table.contains(key):for each matched_row in hash_table[key]:result.append({row["name"], matched_row["order_id"]});
      
  3. 处理下一个 JOIN

    • 上述 JOIN 的结果(users 和 orders 的匹配行)作为左表。
    • 再与 payments 表执行类似的 Hash Join,匹配 order_id。

3.5 输出结果

        JOIN 完成后,ClickHouse 将结果集(包含 name 和 order_id)返回给客户端。结果集仍然按列存储,传输时会序列化为客户端请求的格式(如 JSON、CSV)。


4. 源代码层面的实现

        ClickHouse 是开源的,我们可以参考其源代码(基于 GitHub 上的 ClickHouse 仓库)来理解 JOIN 的实现。以下是关键代码路径和逻辑的概述:

4.1 JOIN 操作的核心类

ClickHouse 的 JOIN 实现在以下模块中:

  • src/Interpreters/Join.h 和 Join.cpp:定义了 JOIN 操作的接口和实现。
  • src/Interpreters/HashJoin.h 和 HashJoin.cpp:实现了 Hash Join 算法。
  • src/Storages/StorageJoin.h 和 StorageJoin.cpp:处理特殊的 JOIN 表(预加载到内存的表)。

核心类是 IJoin,它是一个抽象接口,定义了 JOIN 的行为:

class IJoin
{
public:virtual ~IJoin() = default;virtual bool addBlockToJoin(const Block & block, bool check_limits = true) = 0;virtual void joinBlock(Block & block) = 0;// 其他方法...
};

HashJoin 是 IJoin 的具体实现,用于执行 Hash Join。

4.2 Hash Join 的实现

HashJoin 类的核心逻辑:

  1. 构建哈希表

    • 方法:addBlockToJoin。
    • 功能:将右表的数据加载到内存,构建哈希表。
    • 关键代码(简化):
      void HashJoin::addBlockToJoin(const Block & block, bool /*check_limits*/)
      {for (const auto & column : block){// 根据 JOIN 键提取列数据auto key = column.column->getPtr();// 插入哈希表hash_table.insert(key, column);}
      }
      
  2. 探测阶段

    • 方法:joinBlock。
    • 功能:遍历左表的数据,在哈希表中查找匹配。
    • 关键代码(简化):
      void HashJoin::joinBlock(Block & block)
      {for (size_t i = 0; i < block.rows(); ++i){// 提取左表的 JOIN 键auto key = block.getByPosition(key_position).column->get64(i);// 查找哈希表if (auto matched = hash_table.find(key)){// 构造结果行appendMatchedRows(block, matched);}}
      }
      

4.3 多表 JOIN 的处理

多表 JOIN 在 InterpreterSelectQuery 类中被分解为多个双表 JOIN。代码路径:

  • src/Interpreters/InterpreterSelectQuery.cpp:解析查询并生成执行计划。
  • src/Interpreters/ExpressionAnalyzer.cpp:优化 JOIN 顺序。

优化器会将多表 JOIN 转换为一系列双表 JOIN,例如:

// 伪代码
auto join1 = executeJoin(users, orders, INNER, "user_id = user_id");
auto join2 = executeJoin(join1, payments, INNER, "order_id = order_id");

4.4 分布式 JOIN

如果数据分布在多个节点,ClickHouse 会:

  1. 将右表广播到所有节点(如果右表较小)。
  2. 在每个节点上并行执行 JOIN。
  3. 合并结果。

分布式 JOIN 的逻辑在 src/Interpreters/DistributedStages/PlanSegmentExecutor.cpp 中实现。


5. 优化机制

ClickHouse 在 JOIN 过程中使用了多种优化手段:

  1. 索引利用:如果表有主键或二级索引,ClickHouse 会用索引过滤数据,减少扫描量。
  2. 内存管理:哈希表使用高效的内存分配器(如 jemalloc),避免内存碎片。
  3. 并行处理:JOIN 操作可以分配到多个 CPU 核心并行执行。
  4. 数据压缩:列式存储的数据通常是压缩的,读取时解压,减少 I/O。
  5. 右表预加载:对于小表,ClickHouse 支持 StorageJoin 引擎,将右表预加载到内存,加速 JOIN。

6. 例子比喻的总结

用简单的比喻解释 ClickHouse 的多表 JOIN:

  • 想象你有三本账本:users(记录客户姓名和 ID)、orders(记录订单和客户 ID)、payments(记录付款和订单 ID)。
  • 你想找出每个客户的订单和付款信息:
    1. 先把 orders 账本整理成一本“索引簿”,按客户 ID 分类(这就是哈希表)。
    2. 拿着 users 账本,逐个客户 ID 去索引簿里找对应的订单,写下匹配的结果。
    3. 再把这个结果当作一本新账本,和 payments 账本做类似的匹配。
  • ClickHouse 就像一个超级聪明的会计,它只看需要的页面(列),用最快的索引方式(哈希表),还能让多个助手(CPU 核心)一起干活。

7. 注意事项和限制

  1. 右表大小:Hash Join 要求右表能装进内存。如果右表太大,可能导致内存溢出。
  2. JOIN 顺序:ClickHouse 的优化器可能不总是选择最优顺序,复杂查询需要手动调整。
  3. 分布式 JOIN 的开销:广播右表会增加网络开销,需权衡数据分布。
  4. 语法限制:ClickHouse 不支持动态右表(如子查询),需提前物化。

8. 结论

        ClickHouse 的多表 JOIN 通过列式存储、Hash Join 算法和分布式处理实现了高性能。其核心步骤包括解析查询、优化执行计划、读取列数据、构建哈希表、匹配和合并结果。源代码层面,HashJoin 和 InterpreterSelectQuery 类实现了核心逻辑,结合内存管理和并行优化,确保高效执行。


下面就讲述多表join的优化策略

        ClickHouse 的 JOIN 性能可能因数据规模、查询模式、硬件环境或配置不当而表现不佳。以下是从底层原理、配置优化、查询设计和硬件角度,详细讲解如何优化 ClickHouse JOIN 性能,达到最快速度。


1. 理解 JOIN 性能瓶颈

在优化之前,先识别 JOIN 性能差的可能原因:

  • 数据量大:右表过大导致内存溢出,或左表扫描量过多。
  • JOIN 算法选择不当:默认的 Hash Join 在某些场景(如右表过大)效率低。
  • I/O 开销:读取无关列或数据块过多。
  • 分布式开销:跨节点广播右表或数据 shuffle 耗时。
  • 硬件限制:内存不足、CPU 核心数少或磁盘 I/O 慢。

优化目标是:减少内存使用、降低 I/O、加速匹配、优化分布式执行


2. 优化 JOIN 性能的具体方法

以下是分步骤的优化策略,从查询设计到系统配置,逐一推导原因并提供实现方法。

2.1 查询设计优化

2.1.1 选择合适的 JOIN 类型
  • 原理:不同的 JOIN 类型(如 INNER、LEFT、RIGHT)影响结果集大小和计算复杂度。INNER JOIN 只保留匹配行,通常比 LEFT 或 FULL JOIN 更快。
  • 优化方法
    • 优先使用 INNER JOIN,避免不必要的 LEFT 或 FULL JOIN。
    • 如果必须用 LEFT JOIN,确保右表尽可能小。
  • 示例
    -- 低效:LEFT JOIN 保留所有左表行
    SELECT u.name, o.order_id
    FROM users u
    LEFT JOIN orders o ON u.user_id = o.user_id;-- 优化:如果只需要匹配的行,用 INNER JOIN
    SELECT u.name, o.order_id
    FROM users u
    INNER JOIN orders o ON u.user_id = o.user_id;
    
2.1.2 减少 JOIN 表的数据量
  • 原理:JOIN 前通过 WHERE 子句或子查询过滤数据,减少扫描和匹配的行数。
  • 优化方法
    • 在 JOIN 前对左表和右表应用 WHERE 条件,过滤无关行。
    • 使用子查询或物化视图预先聚合数据。
  • 示例
    -- 低效:JOIN 后再过滤
    SELECT u.name, o.order_id
    FROM users u
    INNER JOIN orders o ON u.user_id = o.user_id
    WHERE o.order_date = '2025-05-01';-- 优化:JOIN 前过滤
    SELECT u.name, o.order_id
    FROM (SELECT * FROM users WHERE active = 1) u
    INNER JOIN (SELECT * FROM orders WHERE order_date = '2025-05-01') o
    ON u.user_id = o.user_id;
    
2.1.3 优化 JOIN 键
  • 原理:JOIN 键的选择性(即唯一值的比例)影响匹配效率。高选择性的键(接近唯一)减少哈希表碰撞,加速查找。
  • 优化方法
    • 选择高选择性的 JOIN 键(如主键或唯一索引)。
    • 确保 JOIN 键类型简单(如整数而非字符串),减少比较开销。
  • 示例
    -- 低效:用字符串作为 JOIN 键
    SELECT u.name, o.order_id
    FROM users u
    INNER JOIN orders o ON u.email = o.customer_email;-- 优化:用整数 user_id
    SELECT u.name, o.order_id
    FROM users u
    INNER JOIN orders o ON u.user_id = o.user_id;
    
2.1.4 使用物化视图或 StorageJoin
  • 原理:ClickHouse 支持 StorageJoin 引擎,将右表预加载到内存,构建持久化的哈希表,适合频繁 JOIN 的小表。物化视图可预聚合数据,减少 JOIN 时的数据量。
  • 优化方法
    • 对于小表(几 MB 到几 GB),创建 StorageJoin 表。
    • 对于大表,创建物化视图预聚合。
  • 示例
    -- 创建 StorageJoin 表
    CREATE TABLE orders_join
    ENGINE = Join(ANY, INNER, user_id)
    AS SELECT user_id, order_id FROM orders;-- 查询时直接使用
    SELECT u.name, o.order_id
    FROM users u
    INNER JOIN orders_join o ON u.user_id = o.user_id;
    
2.1.5 控制 JOIN 顺序
  • 原理:ClickHouse 优化器可能无法选择最优的 JOIN 顺序。手动指定顺序可减少中间结果集大小。
  • 优化方法
    • 将小表放在 JOIN 的早期,减少后续 JOIN 的数据量。
    • 使用括号明确指定 JOIN 顺序。
  • 示例
    -- 低效:优化器可能先处理大表
    SELECT u.name, o.order_id, p.payment_id
    FROM users u
    INNER JOIN orders o ON u.user_id = o.user_id
    INNER JOIN payments p ON o.order_id = p.order_id;-- 优化:明确小表优先
    SELECT u.name, o.order_id, p.payment_id
    FROM (users u INNER JOIN orders o ON u.user_id = o.user_id)
    INNER JOIN payments p ON o.order_id = p.order_id;
    

2.2 配置优化

2.2.1 调整 JOIN 算法
  • 原理:ClickHouse 默认使用 Hash Join,但某些场景下其他算法(如 Merge Join)更优。可以通过配置强制指定算法。
  • 优化方法
    • 对于已排序的大表,启用 Merge Join(需设置 join_algorithm = 'merge')。
    • 对于内存不足的情况,启用部分内存 JOIN(partial_merge_join)。
  • 示例
    -- 强制使用 Merge Join
    SET join_algorithm = 'merge';
    SELECT u.name, o.order_id
    FROM users u
    INNER JOIN orders o ON u.user_id = o.user_id;
    
2.2.2 增加内存限制
  • 原理:Hash Join 需要将右表加载到内存,内存不足会导致溢出到磁盘,性能下降。
  • 优化方法
    • 增加 max_bytes_before_external_join 参数,允许更大内存用于 JOIN。
    • 确保服务器有足够物理内存。
  • 示例
    -- 增加 JOIN 内存限制
    SET max_bytes_before_external_join = 1000000000; -- 1GB
    
2.2.3 启用并行处理
  • 原理:ClickHouse 支持多线程执行 JOIN,增加线程数可加速处理。
  • 优化方法
    • 设置 max_threads 参数,分配更多 CPU 核心。
    • 确保 max_parallel_replicas 启用,允许分布式并行。
  • 示例
    SET max_threads = 16;
    SET max_parallel_replicas = 4;
    

2.3 表设计优化

2.3.1 添加索引
  • 原理:ClickHouse 的 MergeTree 引擎支持主键和二级索引,JOIN 前可通过索引快速过滤数据。
  • 优化方法
    • 在 JOIN 键上创建主键或 ORDER BY 键。
    • 添加 DATA SKIPPING INDEX 跳过无关数据块。
  • 示例
    -- 创建带主键的表
    CREATE TABLE orders (user_id UInt32,order_id UInt64,order_date Date
    ) ENGINE = MergeTree()
    ORDER BY (user_id, order_date);-- 添加跳跃索引
    ALTER TABLE orders ADD INDEX idx_user_id user_id TYPE minmax GRANULARITY 8192;
    
2.3.2 优化表分区
  • 原理:分区可以减少 JOIN 时扫描的数据量,尤其对时间序列数据有效。
  • 优化方法
    • 按 JOIN 键或时间字段分区。
    • 在查询中指定分区条件。
  • 示例
    -- 创建分区表
    CREATE TABLE orders (user_id UInt32,order_id UInt64,order_date Date
    ) ENGINE = MergeTree()
    PARTITION BY toYYYYMM(order_date)
    ORDER BY (user_id);-- 查询指定分区
    SELECT u.name, o.order_id
    FROM users u
    INNER JOIN orders o ON u.user_id = o.user_id
    WHERE o.order_date = '2025-05-01';
    

2.4 分布式优化

2.4.1 避免广播大表
  • 原理:分布式 JOIN 默认广播右表到所有节点,大表广播会导致网络瓶颈。
  • 优化方法
    • 确保右表较小(通过 WHERE 或子查询过滤)。
    • 使用 GLOBAL JOIN 控制分布式行为。
  • 示例
    -- 使用 GLOBAL JOIN
    SELECT u.name, o.order_id
    FROM users u
    GLOBAL INNER JOIN orders o ON u.user_id = o.user_id;
    
2.4.2 数据本地化
  • 原理:如果左表和右表的数据在同一节点上,JOIN 无需跨节点传输。
  • 优化方法
    • 在建表时按 JOIN 键分片(DISTRIBUTED BY)。
    • 使用 Distributed 引擎合理分布数据。
  • 示例
    -- 按 user_id 分片
    CREATE TABLE orders_dist (user_id UInt32,order_id UInt64
    ) ENGINE = Distributed(cluster, db, orders, user_id);
    

2.5 硬件优化

  • 原理:ClickHouse 的 JOIN 性能受硬件限制,优化硬件可直接提升速度。
  • 优化方法
    • 增加内存:确保右表能完全加载到内存(建议 64GB 或更高)。
    • 使用 SSD:NVMe SSD 比 HDD 提供更快的 I/O。
    • 更多 CPU 核心:支持更高并行度(建议 16 核以上)。
    • 网络优化:分布式集群使用高带宽网络(10GbE 或更高)。

3. 性能优化的实际案例

以下低效查询:

SELECT u.name, o.order_id, p.payment_id
FROM users u
LEFT JOIN orders o ON u.user_id = o.user_id
INNER JOIN payments p ON o.order_id = p.order_id
WHERE o.order_date = '2025-05-01';

优化步骤:

  1. 改用 INNER JOIN
    SELECT u.name, o.order_id, p.payment_id
    FROM users u
    INNER JOIN orders o ON u.user_id = o.user_id
    INNER JOIN payments p ON o.order_id = p.order_id
    WHERE o.order_date = '2025-05-01';
    
  2. 提前过滤
    SELECT u.name, o.order_id, p.payment_id
    FROM (SELECT * FROM users WHERE active = 1) u
    INNER JOIN (SELECT * FROM orders WHERE order_date = '2025-05-01') o
    ON u.user_id = o.user_id
    INNER JOIN payments p ON o.order_id = p.order_id;
    
  3. 使用 StorageJoin
    CREATE TABLE payments_join
    ENGINE = Join(ANY, INNER, order_id)
    AS SELECT order_id, payment_id FROM payments;SELECT u.name, o.order_id, p.payment_id
    FROM (SELECT * FROM users WHERE active = 1) u
    INNER JOIN (SELECT * FROM orders WHERE order_date = '2025-05-01') o
    ON u.user_id = o.user_id
    INNER JOIN payments_join p ON o.order_id = p.order_id;
    
  4. 配置并行和内存
    SET max_threads = 16;
    SET max_bytes_before_external_join = 2000000000; -- 2GB
    

4. 监控和调试

  • 使用 EXPLAIN:查看 JOIN 的执行计划,确认算法和扫描量。
    EXPLAIN PLAN
    SELECT u.name, o.order_id
    FROM users u
    INNER JOIN orders o ON u.user_id = o.user_id;
    
  • 检查系统表:分析查询性能瓶颈。
    SELECT query_id, query_duration_ms, memory_usage
    FROM system.query_log
    WHERE query LIKE '%JOIN%';
    
  • 启用日志:设置 log_queries = 1 记录详细日志,分析慢查询。

5 Patriarchal总结(比喻)

用比喻解释优化:

  • JOIN 就像在图书馆找书:左表是你的书单,右表是书架上的书。
  • 低效:你每次拿一本书(行),跑遍整个书架找匹配,累且慢。
  • 优化
    • 先把书单精简(WHERE 过滤)。
    • 把书架上的书编好索引(哈希表或 StorageJoin)。
    • 派多个助手一起找(多线程)。
    • 确保图书馆的路宽敞(高带宽网络)。

6. 注意事项

  • 测试优化效果:每次调整后用小数据集验证性能提升。
  • 避免过优化:如盲目增加内存可能导致其他查询受影响。
  • 监控资源:优化后检查 CPU、内存和磁盘使用率,避免超载。

7. 结论

        通过查询优化(过滤数据、选择 JOIN 类型、优化键)、配置调整(内存、线程、算法)、表设计(索引、分区)、分布式优化(本地化、避免广播)和硬件升级,ClickHouse 的 JOIN 性能可以显著提升。优先从查询设计入手(如提前过滤、使用 StorageJoin),再结合配置和硬件优化,逐步迭代直到达到最优性能。

相关文章:

ClickHouse多表join的性能优化:原理与源码详解

本文将从底层原理和源代码的角度详细解释 ClickHouse 多表 JOIN 的处理过程&#xff0c;尽量用通俗移动的语言&#xff0c;让初学者也能理解。本文会分步骤推导&#xff0c;涵盖 JOIN 的原理、实现方式、关键代码逻辑以及优化机制&#xff0c;同时确保逻辑清晰、内容全面。最后…...

国产Excel处理控件Spire.XLS系列教程:C# 将Excel文件转换为Markdown格式

Microsoft Excel 是一款强大的数据管理工具&#xff0c;广泛用于数据处理和分析。然而&#xff0c;其专有格式&#xff08;如 XLS 和 XLSX&#xff09;在不同平台之间共享时&#xff0c;常常面临兼容性问题。相比之下&#xff0c;Markdown 是一种轻量级的标记语言&#xff0c;因…...

C++线程库

1. 基本概念 1.1 线程&#xff08;Thread&#xff09; 线程是操作系统能够进行运算调度的最小单位。它被包含在进程之中&#xff0c;是进程中的实际运作单位。一个进程可以并发多个线程&#xff0c;每条线程并行执行不同的任务。 1.2 并发与并行 并发&#xff08;Concurren…...

Missashe计网复习笔记(随时更新)

Missashe计算机网络复习笔记 前言&#xff1a;这篇笔记用于博主对计网这门课所学进行记录和总结&#xff0c;也包括一些个人的理解。正在更新当中…… 第一章 计算机网络体系结构 考纲内容 (一) 计算机网络概述 计算机网络的概念、组成与功能;计算机网络的分类; 计算机网络…...

解决osx-arm64平台上conda默认源没有提供 python=3.7 的官方编译版本的问题

CONDA_SUBDIRosx-64 conda create -n py37_env python3.7 是一个用于创建特定架构环境的命令&#xff0c;主要针对 macOS 系统。下面为你详细解析它的功能和作用&#xff1a; 命令功能解析 这个命令的主要功能是创建一个名为 py37_env 的 Conda 环境&#xff0c;并且指定该环…...

Cjson格式解析与接入AI大模型

JSON格式的解析与构造 基本概念 JSON是JavaScript Object Notation的简称&#xff0c;中文含义为“JavaScript 对象表示法”&#xff0c;它是一种数据交换的文本格式&#xff0c;而不是一种编程语言。 JSON 是一种轻量级的数据交换格式&#xff0c;采用完全独立于编程语言的…...

RocketMQ 深度解析:架构设计与最佳实践

在分布式系统架构日益复杂的今天&#xff0c;消息中间件作为系统间通信的桥梁&#xff0c;扮演着至关重要的角色。RocketMQ 作为阿里开源的高性能分布式消息中间件&#xff0c;凭借其卓越的性能、丰富的功能以及高可用性&#xff0c;在电商、金融、互联网等众多领域得到广泛应用…...

【Light】帕多瓦大学超表面技术:开启矢量光束相位偏振定制新时代

01 前言 近年来&#xff0c;完美涡旋光束&#xff08;Perfect Vortex Beams&#xff09;因其独特的环形强度分布和与拓扑电荷无关的特性&#xff0c;成为研究热点。然而&#xff0c;传统方法在生成此类光束时面临相位和偏振控制的挑战&#xff0c;通常需要复杂的光学系统或多重…...

2021-11-15 C++下一个生日天数

缘由c今日日期到生日天数-编程语言-CSDN问答 int isLeap(int year)//判断闰年 {//缘由https://ask.csdn.net/questions/7567048?spm1005.2025.3001.5141return ((!(year % 4) && year % 100) || !(year % 400)); } int daysInMonth(int year, int month)//返回月天数…...

基于nodejs + Koa +Nuxt3的订单系统项目实战

以下是一个基于 Node.js Koa Nuxt3 的订单系统项目实战指南&#xff0c;包含关键实现步骤和代码示例&#xff1a; 一、项目架构设计 project/ ├── backend/ # Koa 后端 │ ├── config/ # 配置文件 │ ├── controllers/ # 控制器 │ ├──…...

# YOLOv2:目标检测的升级之作

YOLOv2&#xff1a;目标检测的升级之作 在目标检测领域&#xff0c;YOLO&#xff08;You Only Look Once&#xff09;系列算法以其高效的速度和创新的检测方式受到了广泛关注。今天&#xff0c;我们就来深入探讨一下 YOLOv2&#xff0c;看看它是如何在继承 YOLOv1 的基础上进行…...

国债收益率、需求与抛售行为的逻辑解析

国债收益率、需求与抛售行为的逻辑解析 1. 国债收益率的定义 国债收益率是衡量国债投资回报的核心指标&#xff0c;分为两种常见计算方式&#xff1a; 当前收益率&#xff08;Current Yield&#xff09;&#xff1a;年利息收入 债券当前市场价格 例如&#xff1a;面值100元、…...

使用Python和TensorFlow实现图像分类的人工智能应用

最近研学过程中发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击链接跳转到网站人工智能及编程语言学习教程。读者们可以通过里面的文章详细了解一下人工智能及其编程等教程和学习方法。下面开始对正文内容的…...

嵌入式培训之C语言学习完(十七)结构体、共用体、枚举、typedef关键字与位运算

目录 一、结构体&#xff08;struct关键字&#xff09; &#xff08;一&#xff09;声明一个结构体数据类型 &#xff08;二&#xff09;结构体的成员初始化与赋值 a、结构体变量赋值 b、结构体成员初始化 c、结构体的定义形式 &#xff08;三&#xff09;考点&#xff…...

cilium路由模式和aws-eni模式下的IPAM

来看Cilium路由的几种核心模式。 封装模式&#xff08;overlay&#xff09; 首先是最基础的封装模式。这是默认情况下&#xff0c;如果你没有特别配置&#xff0c;Cilium会自动运行的模式。它的最大特点就是对底层网络环境的要求非常低&#xff0c;可以说是开箱即用。具体怎么…...

深入理解 Java 代理模式:从基础到实战​

在软件开发的世界里&#xff0c;设计模式是程序员智慧的结晶&#xff0c;它们为解决常见问题提供了经过验证的最佳实践。代理模式作为一种常用的结构型设计模式&#xff0c;在 Java 开发中有着广泛的应用。本文将深入探讨 Java 代理模式&#xff0c;从基本概念、分类、实现原理…...

Python毕业设计219—基于python+Django+vue的房屋租赁系统(源代码+数据库+万字论文)

毕设所有选题&#xff1a; https://blog.csdn.net/2303_76227485/article/details/131104075 基于pythonDjangovue的房屋租赁系统(源代码数据库万字论文)219 一、系统介绍 本项目前后端分离&#xff0c;分为租客、房东、管理员三种角色 1、租客&#xff1a; 注册、登录、公…...

Spring Boot 框架概述

1. 简介 Spring Boot 是由 Pivotal 团队开发的一个用于简化 Spring 应用开发的框架。它通过提供默认配置、嵌入式服务器和自动配置等特性&#xff0c;让开发者能够更快速地构建独立的、生产级别的 Spring 应用。 Spring Boot 的主要特点包括&#xff1a; 快速创建独立的 Spri…...

MySQL 8.0(主从复制)

MySQL 8.0 的 主从复制&#xff08;Master-Slave Replication&#xff09; 是一种数据库高可用和数据备份的核心技术&#xff0c;下面用 一、什么是主从复制&#xff1f; 就像公司的「领导-秘书」分工&#xff1a; 主库&#xff08;Master&#xff09;&#xff1a;负责处理所…...

探索大型语言模型的 LLM 安全风险和 OWASP 十大漏洞

大型语言模型 (LLM) 引领着技术进步,推动着包括医疗保健在内的各个领域的自动化进程。在 Halodoc,我们通过 AI 驱动技术的运用直接见证了这一变化。然而,强大的功能伴随着同样重大的责任——保障这些系统的安全对于保护敏感信息和维护信任至关重要。本博客探讨了与 LLM 相关…...

算法与数据结构 - 二叉树结构入门

目录 1. 普通二叉树结构 1.1. 常见术语 1.2. 完全二叉树 (Complete Binary Tree) 1.3. 满二叉树 (Full Binary Tree) 2. 特殊二叉树结构 2.1. 二叉搜索树 (BST) 2.1.1. BST 基本操作 - 查找 2.1.2. BST 基本操作 - 插入 2.1.3. BST 基本操作 - 删除 2.2. 平衡二叉树…...

基于AQS实现Reentrantlcok

好久没有更新了 这次来补充上一次AQS还没有实现的可重入锁部分&#xff01; 我们知道ReentrantLock是可重入的锁&#xff0c;主要的核心是state&#xff0c;他是一个原子性的整数&#xff0c;我们只需要将获取锁的代码boolean由false到true变成0->1即可完成。在完成删除逻辑…...

TiDB预研-分页查询、连接查询、执行计划

目录 分页查询原理连接查询原理查询计划分析 https://docs.pingcap.com/zh/tidb/stable/dev-guide-join-tables/ https://cn.pingcap.com/blog/tidb-query-optimization-and-tuning-1/ https://github.com/pingcap/blog-cn/blob/master/how-to-use-tidb.md 分页查询 深分…...

五、【LLaMA-Factory实战】模型部署与监控:从实验室到生产的全链路实践

【LLaMA-Factory实战】模型部署与监控&#xff1a;从实验室到生产的全链路实践 一、引言 在大模型应用落地过程中&#xff0c;从实验室研究到生产环境部署存在巨大挑战。本文基于LLaMA-Factory框架&#xff0c;详细介绍大模型部署与监控的完整流程&#xff0c;包含推理优化、…...

Unity 点击按钮,打开 Windows 文件选择框,并加载图片

代码如下&#xff1a; using System; using System.Collections; using System.Runtime.InteropServices; using UnityEngine; using UnityEngine.Events; using UnityEngine.Networking; using UnityEngine.UI;/// <summary> /// 文件日志类 /// </summary> // […...

深入解析磁盘 I/O 与零拷贝技术:从传统读取到高效传输

深入解析磁盘 I/O 与零拷贝技术&#xff1a;从传统读取到高效传输 在现代计算机系统中&#xff0c;磁盘 I/O 操作是数据处理的核心环节之一。无论是读取文件、写入数据&#xff0c;还是进行网络传输&#xff0c;磁盘 I/O 的效率直接影响到系统的整体性能。本文将深入探讨磁盘 I…...

第十六章,网络型攻击防范技术

网络攻击介绍 网络攻击 --- 指的是入侵或破坏网络上的服务器 ( 主机 ) &#xff0c;盗取服务器的敏感数据或占用网络带宽。 网络攻击分类&#xff1a; 流量型攻击 网络层攻击 应用层攻击 单包攻击 畸形报文攻击 --- 向目标主机发送有缺陷的IP报文&#xff0c;使得目标在…...

线程的生命周期·

知识点详细说明 Java线程的生命周期由Thread.State枚举明确定义,包含以下6种状态: 1. 新建状态(NEW) 定义:线程对象被创建后,但未调用start()方法。特点: 未分配系统资源(如CPU时间片)。可通过Thread.getState()获取状态为NEW。示例:Thread t = new Thread(); // 状…...

kafka 面试总结

Kafka的幂等性是一种机制&#xff0c;确保生产者发送的每条消息在Broker端只被持久化一次&#xff0c;即使生产者因网络问题等原因重试发送&#xff0c;也不会导致消息重复。 实现原理 生产者ID&#xff08;PID&#xff09; 每个生产者实例在初始化时&#xff0c;会被分配一个…...

Webpack基本用法学习总结

Webpack 基本使用核心概念处理样式资源步骤&#xff1a; 处理图片资源修改图片输出文件目录 自动清空上次打包的内容EslintBabel处理HTML资源搭建开发服务器生产模式提取css文件为单独文件问题&#xff1a; Css压缩HTML压缩 小结1高级SourceMap开发模式生产模式 HMROneOfInclud…...

5月9日复盘-混合注意力机制

5月9日复盘 四、混合注意力 混合注意力机制&#xff08;Hybrid Attention Mechanism&#xff09;是一种结合空间和通道注意力的策略&#xff0c;旨在提高神经网络的特征提取能力。 1. CBAM Convolution Block Attention Module &#xff0c;卷积块注意力模块 论文地址&…...

YOLOv8 优化:基于 Damo-YOLO 与 DyHead 检测头融合的创新研究

文章目录 YOLOv8 的背景与发展Damo-YOLO 的优势与特点DyHead 检测头的创新之处融合 Damo-YOLO 与 DyHead 检测头的思路融合后的模型架构融合模型的代码实现导入必要的库定义 Damo-YOLO 的主干网络定义特征金字塔网络&#xff08;FPN&#xff09;定义 DyHead 检测头定义融合后的…...

【网安播报】Meta 推出 LlamaFirewall开源框架以阻止 AI 越狱、注入和不安全代码

1、Meta 推出 LlamaFirewall 框架以阻止 AI 越狱、注入和不安全代码 Meta 宣布推出 LlamaFirewall&#xff0c;这是一个开源框架&#xff0c;旨在保护人工智能 &#xff08;AI&#xff09; 系统免受新出现的网络风险&#xff0c;例如提示词注入、越狱和不安全代码等。除了 Llam…...

QT 解决msvc fatal error C1060: 编译器的堆空间不足

一.物理内存太小&#xff0c;代码又比较复杂&#xff0c;递归嵌套之类的。 1.修改虚拟内存的大小&#xff0c;一般设置为物理内存的1.5倍。 二.msvc工程的编译默认开启的是多线程编译&#xff0c;所以电脑内存确实不够&#xff0c;采用如下设置。 QMAKE_CXXFLAGS -j1 三.ms…...

PX4开始之旅(一)自动调参

核心知识点&#xff1a;无人机震动与滤波参数 1. 通俗易懂的解释 想象一下&#xff0c;你的无人机就像一个非常敏感的“听众”&#xff0c;它的“耳朵”就是陀螺仪和加速度计这些传感器&#xff0c;用来感知自己是如何移动和旋转的。理想情况下&#xff0c;它应该只“听”到你…...

C++ 中 lower_bound 与 upper_bound 函数详解

C 中 lower_bound 与 upper_bound 函数详解 文章目录 C 中 lower_bound 与 upper_bound 函数详解**一、核心定义与区别****二、使用条件与时间复杂度****三、实际应用场景****四、注意事项与常见误区****五、代码示例****六、总结** 一、核心定义与区别 lower_bound 作用&#…...

minio数据迁移(两台服务器没法相互通信)

场景描述: A服务器 无法访问 B服务器&#xff0c;B服务器 也无法访问 A&#xff08;即双方都不能通过公网或内网直连对方&#xff09; MinIO 官方提供了 mc&#xff08;MinIO Client&#xff09;命令行工具&#xff0c;可以直接实现 Bucket 之间的数据迁移&#xff1a; 安装 …...

O2OA(翱途)开发平台系统安全-用户登录IP限制

O2OA(翱途)开发平台[下称O2OA开发平台或者O2OA]支持对指定的用户设置可以连接的客户端计算机的IP地址&#xff0c;以避免用户在不安全的环境下访问系统。本篇主要介绍如何开启O2OA用户登录IP限制。 一、先决条件&#xff1a; 以拥有管理员权限的用户账号登录O2OA(翱途)开发平…...

CSS flex:1

在 CSS 中&#xff0c;flex: 1 是一个用于弹性布局&#xff08;Flexbox&#xff09;的简写属性&#xff0c;主要用于控制 flex 项目&#xff08;子元素&#xff09;如何分配父容器的剩余空间。以下是其核心作用和用法&#xff1a; 核心作用 等分剩余空间&#xff1a;让 flex …...

OpenHarmony平台驱动开发(十一),PIN

OpenHarmony平台驱动开发&#xff08;十一&#xff09; PIN 概述 功能简介 PIN即管脚控制器&#xff0c;用于统一管理各SoC的管脚资源&#xff0c;对外提供管脚复用功能。 基本概念 PIN是一个软件层面的概念&#xff0c;目的是为了统一对各SoC的PIN管脚进行管理&#xff0…...

.NET高频技术点(持续更新中)

1. .NET 框架概述 .NET 框架的发展历程.NET Core 与 .NET Framework 的区别.NET 5 及后续版本的统一平台 2. C# 语言特性 异步编程&#xff08;async/await&#xff09;LINQ&#xff08;Language Integrated Query&#xff09;泛型与集合委托与事件属性与索引器 3. ASP.NET…...

Spring Cloud - 2( 12000 字详解 Spring Cloud)

一&#xff1a;服务注册和服务发现 - Eureka 1.1 背景 在上一章节的例子中&#xff0c;我们可以看到远程调用时 URL 被硬编码&#xff0c;这导致在更换机器或新增机器时&#xff0c;相关的 URL 需要进行相应的变更。这就需要让所有相关服务去修改 URL&#xff0c;随之而来的就…...

解决Win11下MySQL服务无法开机自启动问题

问题描述 在win11系统中&#xff0c;明明将MySQL服务设置成了自动启动&#xff0c;但在重启电脑后MySQL服务还是无法自动启动&#xff0c;每次都要重新到计算机管理的服务中找到服务再手动启动。 解决方式 首先确保mysql服务的启动类型为自动。 设置方法&#xff1a;找到此电…...

RGB矩阵照明系统详解及WS2812配置指南

RGB矩阵照明系统详解及WS2812配置指南 一、RGB矩阵照明简介 RGB矩阵照明是一种强大的功能&#xff0c;允许使用外部驱动器驱动的RGB LED矩阵为键盘增添绚丽的灯光效果。该系统与RGBLIGHT功能无缝集成&#xff0c;因此您可以使用与RGBLIGHT相同的键码来控制它&#xff0c;操作…...

全球首款无限时长电影生成模型SkyReels-V2本地部署教程:视频时长无限制!

一、简介 SkyReels-V2 模型集成了多模态大语言模型&#xff08;MLLM&#xff09;、多阶段预训练、强化学习以及创新的扩散强迫&#xff08;Diffusion-forcing&#xff09;框架&#xff0c;实现了在提示词遵循、视觉质量、运动动态以及视频时长等方面的全面突破。通过扩散强迫框…...

代理ARP与传统ARP在网络通信中的应用及区别研究

一些问题 路由器隔离广播域&#xff0c;每个接口/网段都是独立的广播域ARP请求是二层广播包&#xff0c;广播包没法通过路由器ARP请求没法穿越互联网到达目标主服务器 一些思考 电脑访问互联网服务器的时候&#xff0c;ARP询问的内容&#xff0c;真的是访问服务器么&#xf…...

理解 Envoy 的架构

理解 Envoy 的架构对于深入理解 Istio 至关重要&#xff0c;因为 Envoy 是 Istio 数据平面的核心。Envoy 是一个高性能的 C 分布式代理&#xff0c;设计为云原生应用和大规模微服务架构的网络基础。 以下是 Envoy 架构的关键组成部分和核心理念&#xff1a; 核心设计理念&…...

使用Kotlin Flow实现Android应用的响应式编程

在Android应用中使用Kotlin Flow实现响应式编程可以分为以下步骤&#xff0c;结合最佳实践和生命周期管理&#xff1a; 1. 添加依赖 在build.gradle中确保包含协程和生命周期相关依赖&#xff1a; dependencies {implementation("org.jetbrains.kotlinx:kotlinx-corouti…...

【AI提示词】蝴蝶效应专家

提示说明 一位专注于分析和优化蝴蝶效应现象的专业人士&#xff0c;擅长将微小变化转化为系统级影响的研究者。 提示词 # Role: 蝴蝶效应专家## Profile - language: 中文 - description: 一位专注于分析和优化蝴蝶效应现象的专业人士&#xff0c;擅长将微小变化转化为系统级…...

StreamRL:弹性、可扩展、异构的RLHF架构

StreamRL&#xff1a;弹性、可扩展、异构的RLHF架构 大语言模型&#xff08;LLMs&#xff09;的强化学习&#xff08;RL&#xff09;训练正处于快速发展阶段&#xff0c;但现有架构存在诸多问题。本文介绍的StreamRL框架为解决这些难题而来&#xff0c;它通过独特设计提升了训…...