未记录事实核验 · 状态:维护中

记录仅说明核验时间与维护状态,不保证适用于所有环境;使用前请留意正文中的版本、适用条件与风险提示。

warning 适用范围与待维护事项

本文适用于已掌握SQL、MergeTree基础和分布式数据库概念的读者,医院人物及流程仅为教学类比,不是真实医疗案例。示例未指定或实测ClickHouse版本,JSON类型、系统表和文件布局需按目标版本确认;sales表未提供,database_name需替换。集群片段缺少协调服务、宏、认证及跨节点建表,普通MergeTree不会因配置replica自动复制。存储组织图仍有误导性连线,跳过索引需检查既有数据物化与实际命中;本文不构成可直接部署的生产方案。

🏗️ ClickHouse架构原理

📚 学习目标

通过本章节的学习,你将能够:

  • 理解ClickHouse的整体架构设计思想
  • 掌握列式存储引擎的实现原理
  • 了解查询处理和优化机制
  • 理解分布式架构的设计和实现
  • 掌握性能优化的底层原理

tip 💡 学习建议

架构原理是理解ClickHouse高性能的关键。不要被复杂的技术细节吓到,就像了解汽车引擎一样,先理解主要组件和工作流程,再深入具体机制。这些原理将帮助你在实际使用中做出更好的设计决策。

🎯 整体架构概览

1. 系统架构图

ClickHouse的系统架构采用了分层设计的思想,从上到下分为客户端层、服务器层、存储层和物理层四个主要层次。每一层都有明确的职责分工,协同工作以实现高性能的数据处理能力。客户端层提供多样化的访问接口,服务器层负责查询解析和执行,存储层管理不同的存储引擎,物理层则处理实际的数据存储和I/O操作。这种架构设计使得ClickHouse既能保持高性能,又能支持灵活的扩展和优化。

%%{init: {"theme":"base","themeVariables":{"fontFamily":"PingFang SC, -apple-system, Source Han Serif SC, serif","fontSize":"16px","background":"#FDFBF5","primaryColor":"#FDFBF5","primaryTextColor":"#2D2A26","primaryBorderColor":"#6E6A60","secondaryColor":"#E8EFE8","secondaryBorderColor":"#8FA890","secondaryTextColor":"#2D2A26","tertiaryColor":"#FDFBF5","tertiaryBorderColor":"#9A9488","tertiaryTextColor":"#6E6A60","lineColor":"#6E6A60","textColor":"#2D2A26","mainBkg":"#FDFBF5","nodeBorder":"#2D5A3D","clusterBkg":"#F3EEDF","clusterBorder":"#9A9488","edgeLabelBackground":"#FDFBF5"},"flowchart":{"htmlLabels":true,"curve":"basis","padding":20,"nodeSpacing":60,"rankSpacing":70,"useMaxWidth":false},"themeCSS":".node rect, .node polygon, .node circle { stroke-width: 1.5px; } .edgePath .path { stroke-width: 1.5px; } .cluster-label .nodeLabel, .cluster-label foreignObject { color: #2D2A26 !important; } .edgeLabel, .edgeLabel foreignObject { color: #2D2A26 !important; background-color: #FDFBF5; } .edgeLabel rect { fill: #FDFBF5; }"}}%%
flowchart TB
    classDef default fill:#FDFBF5,stroke:#6E6A60,color:#2D2A26,stroke-width:1.5px
    classDef startend fill:#2D2A26,stroke:#2D2A26,color:#FDFBF5,font-weight:bold
    classDef decision fill:#F6EAD8,stroke:#C08A1A,color:#2D2A26,font-weight:bold
    classDef hub fill:#3B5A80,stroke:#3B5A80,color:#FDFBF5,font-weight:bold
    classDef io fill:#E5EAF0,stroke:#3B5A80,color:#2D2A26
    classDef accent fill:#A6432C,stroke:#A6432C,color:#FDFBF5,font-weight:bold
    classDef storage fill:#F3EEDF,stroke:#9A9488,color:#2D2A26
    subgraph "Client Layer 客户端层"
        A1[HTTP Client]
        A2[Native Client]
        A3[JDBC/ODBC]
        A4[Third-party Tools]
    end
    subgraph "Server Layer 服务器层"
        B1[Query Interface 查询接口]
        B2[Query Parser 查询解析器]
        B3[Query Optimizer 查询优化器]
        B4[Query Executor 查询执行器]
    end
    subgraph "Storage Layer 存储层"
        C1[MergeTree Engine]
        C2[Memory Engine]
        C3[Distributed Engine]
        C4[External Engines]
    end
    subgraph "Infrastructure Layer 基础设施层"
        D1[File System]
        D2[Network]
        D3[Memory Management]
        D4[Thread Pool]
    end
    A1 --> B1
    A2 --> B1
    A3 --> B1
    A4 --> B1 --> B2 --> B3 --> B4 --> C1
    B4 --> C2
    B4 --> C3
    B4 --> C4
    C1 --> D1
    C2 --> D3
    C3 --> D2
    C4 --> D1
    class A1,A2,A3,A4,B1,B2,B3,B4,C1,C2,C3,C4,D1,D2,D3,D4 default

2. 核心架构特点

王医生继续解释:"我们智能医院的设计理念体现了现代医疗系统的四大核心原则,这也正是ClickHouse数据库的设计精髓。"

"第一是专科化诊疗设计,"他指向各个专科科室,"就像ClickHouse的列式存储一样,我们不是让一个医生看所有病,而是心内科专门看心脏、神经科专门看大脑、骨科专门看骨骼。每个科室都在自己的专业领域做到极致,这样诊断效率最高。"

"第二是批量化高效处理,"他走向体检中心,"我们的体检流程不是一个人一个人单独处理,而是把同类检查的人群集中起来批量处理,就像ClickHouse的向量化执行,一次性处理一批数据,充分利用设备和人力资源。"

"第三是独立运作避免冲突,"他指向各个楼层,"每个科室都有独立的诊疗设备和医护团队,相互之间不会争抢资源。需要扩展时,新增一个科室就能立即增强整体服务能力,这就是无共享架构的优势。"

"第四是灵活的诊疗模式,"他展示不同的医疗设备,"根据病情复杂程度,我们可以选择门诊、住院、手术等不同的诊疗方案,就像ClickHouse可以根据数据特点选择不同的表引擎,每种方案都有其最适合的应用场景。"

专业技术对应:

  • 面向列的设计:数据按列存储,优化分析查询性能,支持高效的数据压缩和编码
  • 向量化执行:批量处理数据,充分利用CPU缓存,SIMD指令加速计算过程
  • 无共享架构:每个节点独立处理,避免资源竞争,水平扩展能力强
  • 可插拔的表引擎:不同场景使用不同的存储引擎,支持自定义存储策略

🗄️ 存储引擎架构

1. MergeTree引擎族

王医生带大家来到医院的档案管理中心:"现在让我介绍我们医院最核心的病历管理系统,它就像ClickHouse的MergeTree存储引擎一样,是整个系统的基石。"

"MergeTree就像我们医院的智能病历管理系统,"他指着排列整齐的数字化病历库,"专门为大规模医疗数据分析设计。传统医院的病历管理混乱低效,而我们的智能系统让每一份病历都能快速找到、高效分析。"

-- MergeTree表结构示例
CREATE TABLE events (
    date Date,
    user_id UInt64,
    event_name String,
    event_time DateTime,
    properties String
) ENGINE = MergeTree()
PARTITION BY toYYYYMM(date)
ORDER BY (user_id, event_time)
PRIMARY KEY user_id
SETTINGS index_granularity = 8192;

MergeTree数据组织结构

%%{init: {"theme":"base","themeVariables":{"fontFamily":"PingFang SC, -apple-system, Source Han Serif SC, serif","fontSize":"16px","background":"#FDFBF5","primaryColor":"#FDFBF5","primaryTextColor":"#2D2A26","primaryBorderColor":"#6E6A60","secondaryColor":"#E8EFE8","secondaryBorderColor":"#8FA890","secondaryTextColor":"#2D2A26","tertiaryColor":"#FDFBF5","tertiaryBorderColor":"#9A9488","tertiaryTextColor":"#6E6A60","lineColor":"#6E6A60","textColor":"#2D2A26","mainBkg":"#FDFBF5","nodeBorder":"#2D5A3D","clusterBkg":"#F3EEDF","clusterBorder":"#9A9488","edgeLabelBackground":"#FDFBF5"},"flowchart":{"htmlLabels":true,"curve":"basis","padding":20,"nodeSpacing":60,"rankSpacing":70,"useMaxWidth":false},"themeCSS":".node rect, .node polygon, .node circle { stroke-width: 1.5px; } .edgePath .path { stroke-width: 1.5px; } .cluster-label .nodeLabel, .cluster-label foreignObject { color: #2D2A26 !important; } .edgeLabel, .edgeLabel foreignObject { color: #2D2A26 !important; background-color: #FDFBF5; } .edgeLabel rect { fill: #FDFBF5; }"}}%%
flowchart TD
    classDef default fill:#FDFBF5,stroke:#6E6A60,color:#2D2A26,stroke-width:1.5px
    classDef startend fill:#2D2A26,stroke:#2D2A26,color:#FDFBF5,font-weight:bold
    classDef decision fill:#F6EAD8,stroke:#C08A1A,color:#2D2A26,font-weight:bold
    classDef hub fill:#3B5A80,stroke:#3B5A80,color:#FDFBF5,font-weight:bold
    classDef io fill:#E5EAF0,stroke:#3B5A80,color:#2D2A26
    classDef accent fill:#A6432C,stroke:#A6432C,color:#FDFBF5,font-weight:bold
    classDef storage fill:#F3EEDF,stroke:#9A9488,color:#2D2A26
    A[MergeTree Table]
        B[Partition 1<br/>2024-01]
        A --> B
    A --> C[Partition 2<br/>2024-02]
    A --> D[Partition N<br/>2024-XX]
        C --> D
    B --> E[Part 1]
    B --> F[Part 2]
        E --> F
    B --> G[Part N]
    E --> H[Primary Index]
        G --> H
    E --> I[Data Files<br/>.bin]
    E --> J[Mark Files<br/>.mrk]
        I --> J
    E --> K[Checksum<br/>.checksums]
    subgraph "Data Block Structure"
        L[Block Header]
        M[Column 1 Data]
        N[Column 2 Data]
        O[Column N Data]
    end
    I --> L
    class A,B,C,D,E,F,G,H,I,J,K,L,M,N,O default

数据写入和合并过程

张护士长详细解释病历管理流程:"让我告诉大家我们医院是如何高效管理海量病历数据的,这个过程就像ClickHouse的数据写入和合并机制。"

"首先,当患者来医院就诊时,"她指着接诊台,"医生会把本批诊疗信息先整理在临时记录本上,这类比一次普通同步INSERT中的内存处理,并不表示MergeTree默认跨多次INSERT持续积攒内存缓存。"

"当本批记录整理完毕后,"她走向档案室,"我们的护士会把这些记录整理成正式的病历档案,按照科室和时间分类存储到专门的档案柜里,这类比普通同步INSERT将数据排序并写成一个或多个新的Part;Part是分区内的数据部件,不是集群分片。异步插入等缓冲模式需另行配置。"

"最精妙的是我们的智能合并系统,"她打开一个整理完毕的档案柜,"系统会自动把多个小的病历档案合并成大的综合档案,比如把一个月内同一患者在不同科室的就诊记录合并到一起,这样查找和分析效率更高。"

"当医生需要查看某个患者的完整病史时,系统会自动从所有相关档案中提取信息,就像ClickHouse查询MergeTree表时从相关Part读取数据一样;Part是分区内的数据部件,不是集群分片。"

专业技术对应:

-- 数据写入过程模拟
-- 1. 普通同步INSERT在内存中解析、组织和排序本次写入的数据
INSERT INTO events VALUES 
    ('2024-01-01', 1001, 'page_view', '2024-01-01 10:00:00', '{}'),
    ('2024-01-01', 1002, 'click', '2024-01-01 10:01:00', '{}');

-- 2. 本次写入形成一个或多个磁盘Part;不默认等待跨INSERT的内存累积阈值
-- 3. 后台自动合并小的Part为大的Part
-- 4. 查询时自动从所有相关Part读取数据
%%{init: {"theme":"base","themeVariables":{"fontFamily":"PingFang SC, -apple-system, Source Han Serif SC, serif","fontSize":"16px","background":"#FDFBF5","primaryColor":"#FDFBF5","primaryTextColor":"#2D2A26","primaryBorderColor":"#6E6A60","secondaryColor":"#E8EFE8","secondaryBorderColor":"#8FA890","secondaryTextColor":"#2D2A26","tertiaryColor":"#FDFBF5","tertiaryBorderColor":"#9A9488","tertiaryTextColor":"#6E6A60","lineColor":"#6E6A60","textColor":"#2D2A26","mainBkg":"#FDFBF5","nodeBorder":"#2D5A3D","clusterBkg":"#F3EEDF","clusterBorder":"#9A9488","edgeLabelBackground":"#FDFBF5"},"flowchart":{"htmlLabels":true,"curve":"basis","padding":20,"nodeSpacing":60,"rankSpacing":70,"useMaxWidth":false},"themeCSS":".node rect, .node polygon, .node circle { stroke-width: 1.5px; } .edgePath .path { stroke-width: 1.5px; } .cluster-label .nodeLabel, .cluster-label foreignObject { color: #2D2A26 !important; } .edgeLabel, .edgeLabel foreignObject { color: #2D2A26 !important; background-color: #FDFBF5; } .edgeLabel rect { fill: #FDFBF5; }"}}%%
sequenceDiagram
    participant C as Client
    participant M as In-memory Processing
    participant D as Disk Storage
    participant BG as Background Merger
    
    C->>M: INSERT data
    M->>M: Process and sort this INSERT
    M->>D: Write new Part(s)
    BG->>D: Merge small Parts
    D->>D: Create larger Part
    BG->>D: Remove old Parts

2. 列式存储实现

数据压缩和编码

-- 不同的压缩算法选择
CREATE TABLE compressed_data (
    id UInt64,
    name String CODEC(ZSTD),        -- 字符串使用ZSTD压缩
    value Float64 CODEC(Gorilla, LZ4), -- 浮点数使用Gorilla编码再配合LZ4压缩,效果需实测
    timestamp DateTime CODEC(DoubleDelta) -- 时间戳使用DoubleDelta编码
) ENGINE = MergeTree()
ORDER BY id;

-- 查看压缩效果
SELECT 
    name,
    type,
    data_compressed_bytes,
    data_uncompressed_bytes,
    round(data_uncompressed_bytes / data_compressed_bytes, 2) as compression_ratio
FROM system.columns
WHERE table = 'compressed_data';

索引结构

-- 主键索引(稀疏索引)
CREATE TABLE user_events (
    user_id UInt64,
    event_time DateTime,
    event_name String,
    properties String
) ENGINE = MergeTree()
ORDER BY (user_id, event_time)
PRIMARY KEY user_id
SETTINGS index_granularity = 8192;

-- 二级索引
ALTER TABLE user_events ADD INDEX idx_event_name event_name TYPE bloom_filter GRANULARITY 1;
ALTER TABLE user_events ADD INDEX idx_properties properties TYPE ngrambf_v1(3, 512, 3, 0) GRANULARITY 1;

-- 查看索引使用情况
EXPLAIN indexes = 1 
SELECT * FROM user_events 
WHERE user_id = 1001 AND event_name = 'click';

⚡ 查询执行引擎

1. 查询处理流程

王医生带大家来到医院的智能诊断中心:"现在让我展示我们医院如何处理患者的诊疗请求,这个过程完美演示了ClickHouse的查询执行机制。"

"当患者带着症状来看病时,我们的诊疗流程是这样的,"他指着诊疗流程图解释道。

%%{init: {"theme":"base","themeVariables":{"fontFamily":"PingFang SC, -apple-system, Source Han Serif SC, serif","fontSize":"16px","background":"#FDFBF5","primaryColor":"#FDFBF5","primaryTextColor":"#2D2A26","primaryBorderColor":"#6E6A60","secondaryColor":"#E8EFE8","secondaryBorderColor":"#8FA890","secondaryTextColor":"#2D2A26","tertiaryColor":"#FDFBF5","tertiaryBorderColor":"#9A9488","tertiaryTextColor":"#6E6A60","lineColor":"#6E6A60","textColor":"#2D2A26","mainBkg":"#FDFBF5","nodeBorder":"#2D5A3D","clusterBkg":"#F3EEDF","clusterBorder":"#9A9488","edgeLabelBackground":"#FDFBF5"},"flowchart":{"htmlLabels":true,"curve":"basis","padding":20,"nodeSpacing":60,"rankSpacing":70,"useMaxWidth":false},"themeCSS":".node rect, .node polygon, .node circle { stroke-width: 1.5px; } .edgePath .path { stroke-width: 1.5px; } .cluster-label .nodeLabel, .cluster-label foreignObject { color: #2D2A26 !important; } .edgeLabel, .edgeLabel foreignObject { color: #2D2A26 !important; background-color: #FDFBF5; } .edgeLabel rect { fill: #FDFBF5; }"}}%%
flowchart TD
    classDef default fill:#FDFBF5,stroke:#6E6A60,color:#2D2A26,stroke-width:1.5px
    classDef startend fill:#2D2A26,stroke:#2D2A26,color:#FDFBF5,font-weight:bold
    classDef decision fill:#F6EAD8,stroke:#C08A1A,color:#2D2A26,font-weight:bold
    classDef hub fill:#3B5A80,stroke:#3B5A80,color:#FDFBF5,font-weight:bold
    classDef io fill:#E5EAF0,stroke:#3B5A80,color:#2D2A26
    classDef accent fill:#A6432C,stroke:#A6432C,color:#FDFBF5,font-weight:bold
    classDef storage fill:#F3EEDF,stroke:#9A9488,color:#2D2A26
    A[SQL Query<br/>SQL查询]
        B[Parser<br/>语法解析]
        A --> B --> C[Analyzer<br/>语义分析]
    C --> D[Optimizer<br/>查询优化]
        C --> D --> E[Planner<br/>执行计划]
    E --> F[Executor<br/>执行引擎]
        E --> F --> G[Result<br/>查询结果]
    subgraph "Optimizer Details"
        D1[Predicate Pushdown<br/>谓词下推]
        D2[Projection Pushdown<br/>投影下推]
        D3[Join Optimization<br/>连接优化]
        D4[Aggregation Optimization<br/>聚合优化]
    end
    D --> D1
    D --> D2
    D --> D3
    D --> D4
    class A,B,C,D,E,F,G,D1,D2,D3,D4 default

2. 向量化执行

王医生来到医院的大型体检中心:"让我给大家演示我们医院的批量检查流程,这就是ClickHouse向量化执行的完美体现。"

"传统医院是一个一个患者单独检查,效率很低,"他指着传统诊室,"而我们的智能体检中心采用流水线式的批量处理模式。"

"你们看,"他带大家参观体检流水线,"我们把需要做血常规的患者集中起来,一次性用自动化设备处理100个血样;把需要做心电图的患者排成队,用高速心电设备连续检测;需要做B超的患者也是批量安排。这样每台设备都能发挥最大效能,就像ClickHouse一次性处理一批数据,充分利用CPU和内存资源。"

-- 向量化执行示例
SELECT 
    user_id,
    count() as event_count
FROM events 
WHERE date >= '2024-01-01'
GROUP BY user_id;

-- 执行过程:
-- 1. 批量读取数据块(65536是常见max_block_size设置值,不保证每块固定为该行数;实际大小受设置、读取路径和数据影响)
-- 2. 向量化处理WHERE条件
-- 3. 向量化执行聚合计算
-- 4. 批量输出结果

执行计划分析

-- 查看详细执行计划
EXPLAIN PLAN 
SELECT 
    category,
    count() as cnt,
    sum(sales_amount) as total_sales
FROM sales 
WHERE date >= '2024-01-01'
GROUP BY category
ORDER BY total_sales DESC;

-- 查看执行流水线结构(不执行查询,也不提供实际运行统计)
EXPLAIN PIPELINE 
SELECT count() FROM events WHERE user_id = 1001;

-- 查看索引使用情况
EXPLAIN indexes = 1
SELECT * FROM events WHERE user_id = 1001 AND event_time >= '2024-01-01';

3. 内存管理

-- 查看已完成查询记录的内存用量(需启用查询日志;字段语义按目标版本确认)
SELECT 
    query,
    memory_usage,
    formatReadableSize(memory_usage) as memory_readable
FROM system.query_log 
WHERE type = 'QueryFinish'
ORDER BY memory_usage DESC
LIMIT 10;

-- 设置查询内存限制
SET max_memory_usage = 1000000000; -- 1GB限制

-- 查看系统内存指标
SELECT 
    metric,
    value,
    formatReadableSize(value) as readable_value
FROM system.asynchronous_metrics 
WHERE metric LIKE '%Memory%';

🌐 分布式架构

1. 集群架构设计

<!-- 集群配置示例 -->
<clickhouse>
    <remote_servers>
        <cluster_name>
            <shard>
                <replica>
                    <host>node1</host>
                    <port>9000</port>
                </replica>
                <replica>
                    <host>node2</host>
                    <port>9000</port>
                </replica>
            </shard>
            <shard>
                <replica>
                    <host>node3</host>
                    <port>9000</port>
                </replica>
                <replica>
                    <host>node4</host>
                    <port>9000</port>
                </replica>
            </shard>
        </cluster_name>
    </remote_servers>
</clickhouse>
%%{init: {"theme":"base","themeVariables":{"fontFamily":"PingFang SC, -apple-system, Source Han Serif SC, serif","fontSize":"16px","background":"#FDFBF5","primaryColor":"#FDFBF5","primaryTextColor":"#2D2A26","primaryBorderColor":"#6E6A60","secondaryColor":"#E8EFE8","secondaryBorderColor":"#8FA890","secondaryTextColor":"#2D2A26","tertiaryColor":"#FDFBF5","tertiaryBorderColor":"#9A9488","tertiaryTextColor":"#6E6A60","lineColor":"#6E6A60","textColor":"#2D2A26","mainBkg":"#FDFBF5","nodeBorder":"#2D5A3D","clusterBkg":"#F3EEDF","clusterBorder":"#9A9488","edgeLabelBackground":"#FDFBF5"},"flowchart":{"htmlLabels":true,"curve":"basis","padding":20,"nodeSpacing":60,"rankSpacing":70,"useMaxWidth":false},"themeCSS":".node rect, .node polygon, .node circle { stroke-width: 1.5px; } .edgePath .path { stroke-width: 1.5px; } .cluster-label .nodeLabel, .cluster-label foreignObject { color: #2D2A26 !important; } .edgeLabel, .edgeLabel foreignObject { color: #2D2A26 !important; background-color: #FDFBF5; } .edgeLabel rect { fill: #FDFBF5; }"}}%%
flowchart TB
    classDef default fill:#FDFBF5,stroke:#6E6A60,color:#2D2A26,stroke-width:1.5px
    classDef startend fill:#2D2A26,stroke:#2D2A26,color:#FDFBF5,font-weight:bold
    classDef decision fill:#F6EAD8,stroke:#C08A1A,color:#2D2A26,font-weight:bold
    classDef hub fill:#3B5A80,stroke:#3B5A80,color:#FDFBF5,font-weight:bold
    classDef io fill:#E5EAF0,stroke:#3B5A80,color:#2D2A26
    classDef accent fill:#A6432C,stroke:#A6432C,color:#FDFBF5,font-weight:bold
    classDef storage fill:#F3EEDF,stroke:#9A9488,color:#2D2A26
    subgraph "ClickHouse Cluster"
        subgraph "Shard 1"
            A1[Node1<br/>Replica 1]
            A2[Node2<br/>Replica 2]
        end
        subgraph "Shard 2"
            B1[Node3<br/>Replica 1]
            B2[Node4<br/>Replica 2]
        end
        subgraph "ZooKeeper Ensemble"
            Z1[ZK1]
            Z2[ZK2]
            Z3[ZK3]
        end
    A1 -.-> A2
    B1 -.-> B2
    A1 --> Z1
    A2 --> Z2
    B1 --> Z3
    B2 --> Z1

    end
    class A1,A2,B1,B2,Z1,Z2,Z3 default

2. 分布式表

-- 创建本地表
CREATE TABLE events_local (
    date Date,
    user_id UInt64,
    event_name String,
    event_time DateTime
) ENGINE = MergeTree()
PARTITION BY toYYYYMM(date)
ORDER BY (user_id, event_time);

-- 创建分布式表
CREATE TABLE events_distributed AS events_local
ENGINE = Distributed(cluster_name, database_name, events_local, rand());

-- 创建复制表
CREATE TABLE events_replicated (
    date Date,
    user_id UInt64,
    event_name String,
    event_time DateTime
) ENGINE = ReplicatedMergeTree('/clickhouse/tables/{shard}/events', '{replica}')
PARTITION BY toYYYYMM(date)
ORDER BY (user_id, event_time);

3. 数据分片和复制

-- 查看集群状态
SELECT * FROM system.clusters;

-- 查看分片分布
SELECT 
    shard_num,
    replica_num,
    host_name,
    port,
    is_local
FROM system.clusters 
WHERE cluster = 'cluster_name';

-- 查看表的分布情况
SELECT 
    partition,
    name,
    rows,
    bytes_on_disk,
    formatReadableSize(bytes_on_disk) as size_readable
FROM system.parts 
WHERE table = 'events_local'
ORDER BY partition, name;

🔧 性能优化机制

1. 查询优化技术

谓词下推

-- 子查询形式:过滤仍在数据库中执行,不是在应用层过滤
SELECT * FROM (
    SELECT user_id, event_name, event_time 
    FROM events
) WHERE user_id = 1001;

-- 直接表达过滤条件;优化器可能将上面的子查询形式转换为等价计划
SELECT user_id, event_name, event_time 
FROM events 
WHERE user_id = 1001;

-- ClickHouse自动进行谓词下推优化

投影下推

-- 只读取需要的列
SELECT user_id, count() 
FROM events 
GROUP BY user_id;

-- ClickHouse只会读取user_id列,大大减少I/O

2. 数据跳过索引

-- 创建跳过索引
CREATE TABLE events_with_skip_index (
    date Date,
    user_id UInt64,
    event_name String,
    properties String
) ENGINE = MergeTree()
ORDER BY date
SETTINGS index_granularity = 8192;

-- 添加不同类型的跳过索引
ALTER TABLE events_with_skip_index ADD INDEX idx_user_id user_id TYPE minmax GRANULARITY 1;
ALTER TABLE events_with_skip_index ADD INDEX idx_event_name event_name TYPE set(100) GRANULARITY 1;
ALTER TABLE events_with_skip_index ADD INDEX idx_properties properties TYPE bloom_filter GRANULARITY 1;

-- 查看索引效果
EXPLAIN indexes = 1
SELECT * FROM events_with_skip_index 
WHERE user_id BETWEEN 1000 AND 2000 
  AND event_name = 'click';

3. 并行处理机制

-- 查看查询并行度
SELECT 
    query,
    thread_ids,
    length(thread_ids) as thread_count
FROM system.query_log 
WHERE type = 'QueryFinish'
ORDER BY event_time DESC
LIMIT 5;

-- 控制查询并行度
SET max_threads = 8;

-- 查看系统线程池状态
SELECT 
    metric,
    value
FROM system.metrics 
WHERE metric LIKE '%Thread%';

📊 存储格式详解

1. 列文件格式

-- 查看各活动Part的列级存储信息;path是Part路径,不是逐个列文件路径
SELECT 
    column,
    column_type,
    path,
    formatReadableSize(column_bytes_on_disk) as size
FROM system.parts_columns 
WHERE table = 'events' AND active = 1
ORDER BY column, column_type;

-- 查看压缩信息
SELECT 
    name,
    type,
    data_compressed_bytes,
    data_uncompressed_bytes,
    round(data_uncompressed_bytes / data_compressed_bytes, 2) as compression_ratio
FROM system.columns 
WHERE table = 'events'
ORDER BY compression_ratio DESC;

2. 数据块结构

%%{init: {"theme":"base","themeVariables":{"fontFamily":"PingFang SC, -apple-system, Source Han Serif SC, serif","fontSize":"16px","background":"#FDFBF5","primaryColor":"#FDFBF5","primaryTextColor":"#2D2A26","primaryBorderColor":"#6E6A60","secondaryColor":"#E8EFE8","secondaryBorderColor":"#8FA890","secondaryTextColor":"#2D2A26","tertiaryColor":"#FDFBF5","tertiaryBorderColor":"#9A9488","tertiaryTextColor":"#6E6A60","lineColor":"#6E6A60","textColor":"#2D2A26","mainBkg":"#FDFBF5","nodeBorder":"#2D5A3D","clusterBkg":"#F3EEDF","clusterBorder":"#9A9488","edgeLabelBackground":"#FDFBF5"},"flowchart":{"htmlLabels":true,"curve":"basis","padding":20,"nodeSpacing":60,"rankSpacing":70,"useMaxWidth":false},"themeCSS":".node rect, .node polygon, .node circle { stroke-width: 1.5px; } .edgePath .path { stroke-width: 1.5px; } .cluster-label .nodeLabel, .cluster-label foreignObject { color: #2D2A26 !important; } .edgeLabel, .edgeLabel foreignObject { color: #2D2A26 !important; background-color: #FDFBF5; } .edgeLabel rect { fill: #FDFBF5; }"}}%%
flowchart TB
    classDef default fill:#FDFBF5,stroke:#6E6A60,color:#2D2A26,stroke-width:1.5px
    classDef startend fill:#2D2A26,stroke:#2D2A26,color:#FDFBF5,font-weight:bold
    classDef decision fill:#F6EAD8,stroke:#C08A1A,color:#2D2A26,font-weight:bold
    classDef hub fill:#3B5A80,stroke:#3B5A80,color:#FDFBF5,font-weight:bold
    classDef io fill:#E5EAF0,stroke:#3B5A80,color:#2D2A26
    classDef accent fill:#A6432C,stroke:#A6432C,color:#FDFBF5,font-weight:bold
    classDef storage fill:#F3EEDF,stroke:#9A9488,color:#2D2A26
    subgraph "Data Block"
        A[Block Header<br/>元数据信息]
        B[Column 1<br/>用户ID列]
        C[Column 2<br/>事件名称列]
        D[Column 3<br/>时间戳列]
        E[Column N<br/>其他列]
    end
    subgraph "Column Structure"
        F[Data Type Info<br/>数据类型信息]
        G[Compression Info<br/>压缩信息]
        H[Null Map<br/>空值位图]
        I[Actual Data<br/>实际数据]
    end
    B --> F
    B --> G
    B --> H
    B --> I
    class A,B,C,D,E,F,G,H,I default

3. 索引文件格式

-- 查看主键和排序键信息
SELECT 
    primary_key,
    sorting_key
FROM system.tables 
WHERE database = currentDatabase() AND name = 'events';

-- 从建表语句查看显式设置;未显式指定的设置需另行核对默认值
SHOW CREATE TABLE events;

-- 查看索引颗粒度效果
SELECT 
    rows,
    marks,
    round(rows / marks) as rows_per_mark
FROM system.parts 
WHERE table = 'events' AND active = 1;

🎯 架构设计最佳实践

1. 表设计建议

-- 好的表设计示例
CREATE TABLE user_behavior_optimized (
    -- 时间列,用于分区
    date Date,
    -- 高基数列,用于排序
    user_id UInt64,
    session_id String,
    -- 低基数列,使用LowCardinality
    event_type LowCardinality(String),
    page_category LowCardinality(String),
    -- 数值列
    duration UInt32,
    -- JSON数据
    properties JSON
) ENGINE = MergeTree()
PARTITION BY toYYYYMM(date)  -- 按月分区
ORDER BY (user_id, session_id, date)  -- 优化常用查询
SETTINGS index_granularity = 8192;

-- 添加合适的索引
ALTER TABLE user_behavior_optimized ADD INDEX idx_event_type event_type TYPE set(100) GRANULARITY 1;
ALTER TABLE user_behavior_optimized ADD INDEX idx_duration duration TYPE minmax GRANULARITY 1;

2. 查询优化建议

-- 高效的聚合查询
SELECT 
    toStartOfDay(date) as day,
    event_type,
    count() as event_count,
    uniq(user_id) as unique_users,
    avg(duration) as avg_duration
FROM user_behavior_optimized
WHERE date >= today() - 7
  AND event_type IN ('page_view', 'click')
GROUP BY day, event_type
ORDER BY day DESC, event_count DESC;

-- 使用物化视图预计算:保存可合并状态,避免累加各插入批次的去重结果
-- 此增量物化视图处理创建后的新插入数据,既有数据需另行回填
CREATE MATERIALIZED VIEW daily_stats_mv
ENGINE = AggregatingMergeTree()
ORDER BY (date, event_type)
AS SELECT 
    date,
    event_type,
    countState() as event_count,
    uniqState(user_id) as unique_users
FROM user_behavior_optimized
GROUP BY date, event_type;

-- 读取时合并聚合状态;uniq仍为近似去重
SELECT
    date,
    event_type,
    countMerge(event_count) as event_count,
    uniqMerge(unique_users) as unique_users
FROM daily_stats_mv
GROUP BY date, event_type;

🎯 架构理解重点

通过本章节的学习,我们深入了解了:

  1. 整体架构 - 分层设计、无共享架构、可插拔引擎
  2. 存储引擎 - MergeTree设计、列式存储、数据合并机制
  3. 查询处理 - 向量化执行、查询优化、内存管理
  4. 分布式设计 - 集群架构、数据分片、副本机制
  5. 性能优化 - 索引设计、并行处理、存储优化

abstract 📚 下一步学习

现在你已经理解了ClickHouse的架构原理,接下来我们将学习ClickHouse环境搭建,动手实践这些理论知识。

🔗 相关资源


💡 记住:理解架构原理是掌握ClickHouse的关键。这些知识将帮助你在实际使用中做出正确的设计决策,比如如何选择表引擎、如何设计分区策略、如何优化查询性能等。不要急于记住所有细节,重点是理解设计思想和工作原理!