0.登录
clickhouse-client --host 127.0.0.1 --port 9000 --user default --password pw   //登录
clickhouse-client --host 127.0.0.1 --port 9000 --user svr --password pw       //登录
1.特点
  1. 属于列式存储数据库(一列数据存储在一起),更适合数据统计分析(聚合、计数、求和)、数据类型一致更好存储压缩。
    行式存储(mysql、oracle),适用于增删改查都有的场景(主要是顺序写)
  2. 多样化引擎: merge、日志、集成、其他 4大类20多种引擎.. 还有库引擎
  3. 和LSM tree思想一致: 追加写/后台合并 (conn.4.11)
  4. 多线程效率高吃cpu
2.数据类型
  1. 类型 官方文档

    Int8  Int16  Int32  Int64
    UInt8 UInt16 UInt32 UInt64
    Float32 Float64
    Decimal32(s)   //等价于mysql:decimal32(9,9-s)   //2^31 =10位数 //mysql: decimal(m,d) m总位数 d小数位数
    Decimal64(s)   //等价于mysql:decimal32(18,18-s) //2^63 =19位数
    Decimal128(s)  //等价于mysql:decimal32(38,38-s) //2^127=37位数
    
    Bool           //本质是用Uint(8)存储
    String         //类似于mysql:varchar(n) //mysql有长度限制 clickhouse没有
    FixedString(n) //等价于mysql:char(n)
    Enum8 Enum16   //枚举把string和Int8 Int16绑定
    
    Date           //[1970-01-01, 2149-06-06]
    Date32         //[1970-01-01, 2299-12-31]
    DateTime       //[1970-01-01 00:00:00, 2106-02-07 06:28:15]
    DateTime64     //[0000-01-01 00:00:00, 9999-12-31 23:59:59.999999999]
    Time           //[-999:59:59, 999:59:59]
    
    Array(T)       //数组
    Nullable       //NULL 一般用业务不会出现的值代替NULL
3.表引擎
1.集成
  1. 数据存储在其他app,例如mysql/kafka    clickhouse本地不存数据但可以直接去其他app查询和插入数据
2.日志
  1. TinyLog 不支持索引,不支持并发 用于存少量数据的小表,生产环境很少使用
3.merge
  1. MergeTree

    CREATE TABLE tbl_orders
    (
        `customer_id` UInt32,
        `good_id` String,
        `good_num` UInt32,
        `amount` Decimal(16, 2) TTL `d` + INTERVAL 10 SECOND,  --5.[TTL] 1.字段在数据到期以后(时间>`d`+10s) 下一次合并会被设置为默认值 2.统计数据一般会要求最近的数据,所以TTL应用比较广泛
        `d` Datetime,                                          --
        INDEX idx_name `amount` TYPE minmax GRANULARITY 5      --4.[二级索引] 1.主键(跳数)索引把数据分成了很多块 2.每5块记录`amount`的最大最小值 3.在对`amount`过滤的时候可以快速的知道这5块数据里面有没有符合查找条件的数据
    )
    ENGINE = MergeTree                                         --0.<表引擎>
    PARTITION BY toYYYYMMDD(`d`)                               --1.[分区] 1.一个分区(Partition)有多个Part目录 2.新插入的数据会在临时Part目录 定时/大小限制/数量 触发合并(合并和LSM思路一致 但是没有数据新旧版本的概念L0>L1>L2) 3.推荐使用日期分区
    PRIMARY KEY customer_id                                    --3.[主键] 1.可以重复 2.稀疏索引(跳数建立索引) 3.必须是order by的前缀字段 默认值是order by
    SETTINGS index_granularity = 8192                          --3.[主键] 设置跳数索引的间隔(index_granularity 默认值:8192) 在索引开销和过滤效果之间做权衡
    ORDER BY (customer_id, good_id)                            --2.<排序>
    TTL `d` + INTERVAL 1 MONTH DELETE,                         --6.[TTL] 行数据在到期以后 下一次合并会被删除
        `d` + INTERVAL 1 WEEK TO VOLUME 'aa',                  --6.[TTL] 行数据在到期以后 下一次合并被移动到卷aa
        `d` + INTERVAL 2 WEEK TO DISK 'bb';                    --6.[TTL] 行数据在到期以后 下一次合并被移动到磁盘bb
    
    OPTIMIZE TABLE tbl_orders FINAL;                           --手动执行合并
  2. ReplacingMergeTree(按排序键替换旧版本)

    CREATE TABLE tbl_orders
    (
        `customer_id` UInt32,
        `good_id` String,
        `good_num` UInt32,
        `amount` Decimal(16, 2) TTL `d` + INTERVAL 1 MONTH,
        `d` Datetime,
        INDEX idx_name `amount` TYPE minmax GRANULARITY 5
    )
    ENGINE = ReplacingMergeTree(`d`)     --1.<表引擎> 1.会根据order by在合并的时候去重 保留`d`最大行 `d`相同保留最新插入的行 2.只有同一分区去重 3.其他和MergeTree一致
    PARTITION BY toYYYYMMDD(`d`)         --          4.实现去重 ①.FINAL ②.GROUP BY + argMax(good_id,d)
    PRIMARY KEY customer_id
    ORDER BY (customer_id, good_id)      --2.<排序> 1.UNIQUE
    TTL `d` + INTERVAL 1 MONTH DELETE,
        `d` + INTERVAL 1 WEEK TO VOLUME 'aa',
        `d` + INTERVAL 2 WEEK TO DISK 'bb';
    SELECT                                --sql语义:查询用户最新的订单数据 即业务实现: 预去重/只是同一分区去重->所有分区去重
        customer_id,
        argMax(good_id, d) AS good_id,    --实现去重 2(推荐).保留d最大的那个good_id 如果最新的数据操作是删除 可以插入一条good_id=-1的哨兵值标识最新行数据已经删除
        argMax(good_num, d) AS good_num,
        max(d) AS d
    FROM tbl_orders                       --实现去重 1(大表性能差).在这里加FINAL 相当于把后台没有完成的merge提前展示出来
    GROUP BY customer_id
    HAVING good_id != -1;                 --实现去重 2.过滤掉已经删除了的数据
  3. SummingMergeTree(预聚合)

    create table tbl_orders(
     `customer_id` UInt32,
     `good_id` String,
     `good_num` UInt32,
     `amount` Decimal(16,2) ,
     `d` Datetime
    )                                                --
    engine =SummingMergeTree(`amount`, `good_num`)   --<表引擎> 1.会根据order by在合并的时候聚合`amount`(可以指定多列 需要为数值列) 其他字段保留最先插入的数据 2.只有同一分区聚合
    partition by toYYYYMMDD(d)
    primary key (customer_id)
    order by (customer_id,good_id);
4.特殊
  1. Memory
4.sql
  1. 增 INSERT同mysql

    insert into tbl_orders values
    (101,'sku_001',1,1000.00,'2020-06-01 12:00:00'),
    (102,'sku_002',2,2000.00,'2020-06-01 11:00:00'),
    (102,'sku_002',1,6000.00,'2020-06-02 12:00:00');
  2. 删 DELETE

    alter table tbl_orders delete where good_id ='sku_001';
  3. 改 UPDATE

    alter table tbl_orders update total_amount=toDecimal32(2000.00,2) where customer_id=102;
  4. 删改 DELETE UPDATE 属于重型操作,对事务的支持为实验性功能
    clickhouse每次修改或者删除的策略是放弃数据所在的原有文件,重建新的文件(rcu)。所以要批量更改,不要频繁小数据操作
    不设计成mysql一样找到数据位置修改或者删除的原因是
    clickhouse为列式存储 同时数据编码和压缩 某个值改变以后,重新编码、压缩得到的数据内容和长度都可能发生变化
    如果该字段在ORDER BY,还会导致稀疏主键索引、marks(标识各列数据在压缩文件的具体偏移位置,在主键索引使用后要查找具体位置使用)等相关结构变化。
    所以clickhouse干脆采用了rcu的模式
    clickhouse会限制了最大并发请求数 默认值100 可配置max_concurrent_queries

  5. 查 SELECT

a. 支持子查询 / 支持CTE (同mysql)

   SELECT *
   FROM
   (
       SELECT
           customer_id,
           sum(amount) AS sumamt
       FROM tbl_orders
       GROUP BY customer_id
   )
   WHERE sumamt > 10000;
   WITH tbl_sumamt AS           --上面的子查询改成CTE 就是把子查询出来的表加了一个备注名
   (
       SELECT
           customer_id,
           sum(amount) AS sumamt
       FROM tbl_orders
       GROUP BY customer_id
   )
   SELECT *
   FROM tbl_sumamt
   WHERE sumamt > 10000;

b. 支持多种JOIN算法

算法 原理 适用场景
hash (默认) 固定把右表导入内存构建Hash Table 右表<内存 快
direct 拿左表的每行数据直接去右表查 如果右表底层是Hash-based结构,单次查询时间复杂度为O(1)
grace_hash 将左右表按JOIN Key哈希分片(相同哈希函数左右表相同key会被分配到同一个分片) -> 左1分片和右1分片进行Hash Join -> 左2与右2 右表>内存 中快
full_sorting_merge 左右表先完整排序(排序内存不够会用到磁盘) -> merge(左右表一个指针匹配 匹配上都走一步 没匹配上数大的指针走一步) 速度中 内存需求高
partial_merge 右表分块->右表块内排序 -> 左表分块 -> 左表每一行去右表每一块匹配 左右表>内存 速度慢 内存需求低

c. GROUP BY (abc) 操作增加了with rollup (all/a/ab/abc n+1组合) \with cube (2^n组合 其中非左前缀ac/bc/b/c会消耗更多的资源)\with totals (all/abc 2组合) 用来计算小计和总计。
d. 提供的常用函数

   聚合:     count、sum、avg、min、max、uniq→近似去重计数、uniqExact→精确去重计数
   条件:     if、multiIf→类似于case
   条件聚合: countIf、sumIf
   日期:     today、toDate、toDateTime、toYear、toMonth、toStartOfDay()→返回这一天的开始时间、toStartOfMonth
   类型转换: toString、toInt64、toFloat64
   NULL:    isNull(x) isNotNull(x) ifNull(x, 0)→为空返回0 coalesce(x1,x2,x3,x4)→返回第一个不为NULL的参数
   字符串:   length(str) lower(str) upper(str) concat(str1, str2) substring(str, 1, 3) replaceAll(str, 'old', 'new') trim(str) splitByChar(',', str)
   数组:     length([1,2,3]) arraySum([1,2,3]) arrayMax([1,2,3]) arrayMin([1,2,3])
   数值计算:
   abs(-10)         → 10
   round(12.345, 2) → 12.35
   floor(12.8)      → 12
   ceil(12.1)       → 13
   sqrt(100)        → 10
   pow(2, 3)        → 8
5.集群&副本 todo:
6.explain
  1. PLAN: 逻辑执行步骤(默认值)
    PIPELINE: 具体执行步骤及数据流
    AST: clickhouse怎么分析这个sql (一般不用)
    SYNTAX: clickhouse对sql优化后的结果
  2. 关键字执行顺序 conn.2
  3. 实例
    --sql语义: 从datasets system库中,统计每张表有多少个Part,降序,每个数据库只留前2条数据。
    EXPLAIN PLAN
    SELECT                                        --4.选字段
        `database`,
        `table`,
        count(1) AS `cnt`
    FROM system.parts                             --1.查表 记录表的part信息
    WHERE `database` IN ('datasets', 'system')    --2.条件 字段database在('datasets', 'system')里面
    GROUP BY                                      --3.分组
        `database`,
        `table`
    ORDER BY                                      --5.排序
        `database` ASC,
        `cnt` DESC
    LIMIT 2 BY `database`                         --6.每个database保留2条数据
        ┌─explain───────────────────────────────────────────────────────────────┐
     1. │ Output: database, table, count()                                      │ --7.输出结果到终端
     2. │                                                                       │
     3. │ LimitBy                                                               │ --6.limit
     4. │ │  Columns: database                                                  │
     5. │ │  Length 2                                                           │
     6. │ │  Offset 0                                                           │
     7. │ └──Sorting (Sorting for ORDER BY)                                     │ --5.排序 这里会用min(机器核数,16)个线程数去排序
     8. │    │  Sort description: database ASC, count() DESC                    │
     9. │    │  Per-stream LIMIT BY columns: database                           │
    10. │    │  Per-stream LIMIT BY length 2                                    │
    11. │    └──Aggregating                                                     │ --3.聚合 4.选字段
    12. │       │  Keys: database, table                                        │
    13. │       │  Aggregates: count()                                          │
    14. │       │  Skip merging: 0                                              │
    15. │       └──Filter ((WHERE + Change column names to column identifiers)) │ --2.where过滤
    16. │          │  Filter column: database IN ('datasets', 'system')         │
    17. │          └──ReadFromSystemPartsBase                                   │ --1.查表
    18. │                Output: database, table                                │
        └───────────────────────────────────────────────────────────────────────┘
    EXPLAIN SYNTAX SELECT number = 1 ? 'hello' : (number = 2 ? 'world' : 'atguigu') FROM numbers(10); --优化sql
       ┌─explain──────────────────────────────────────────────────────────────────────────┐
    1. │ SELECT if(equals(number, 1), 'hello', if(equals(number, 2), 'world', 'atguigu')) │
    2. │ FROM numbers(10)                                                                 │
       └──────────────────────────────────────────────────────────────────────────────────┘
7. 建表优化
  1. 时间定义为DateTime类型
    CREATE TABLE tbl_orders
    (
        `id` UInt32,
        `d`  Int32                                --可以直接指定为Datetime类型,在分区的时候就不用加toDate函数了
    )
    ENGINE = MergeTree
    PARTITION BY toYYYYMMDD(toDate(`d`))          --
    ORDER BY id;
  2. 尽量避免使用Nullable(T),用逻辑上的哨兵数值代替
    需要用额外的文件null_map用来标识数据是否为null
    在执行聚合操作的时候需要先读null_map,额外的判断数据是否为null(在几十亿数据量下这个成本会被放大)
    Nullable会给主键、排序键、skip index 等设计增加限制或额外处理(主要是指排序问题)
    同mysql NULL=NULL(结果是unknow) sql需要用IS NULL判断
  3. 基数: 一个字段有多少不同的值
    order by: 使用频率越高的字段放在越前面,基数大的字段放在order by里面会导致在做聚合的成本更高
8. 优化
  1. count()查统计数据直接返回 count(fld-)需要扫描列数据忽略NULL
  2. where 读取select列 -> 过滤不需要的行
    prewhere 先读取少量的过滤列(prewhere后面的列) -> 把大量行过滤掉 -> 再按行读取select列,从而减少磁盘读取、解压的数据量
    prewhere 的价值取决于过滤效果 0.比如一张表有1e行 100多个字段 select * from tbl prewhere uid=10001; 1.过滤后只有1000行 这个时候的收益就很高。2.如果过滤以后还有9000w行数据,反而会因为两次读表使得效率降低
  3. 数据采集
    //建表的时候指定抽样规则 eg:SAMPLE BY intHash64(UserID)
    //hash数据会平均分布在max(uint64)里面 抽取1%时就取0~0.01*max(uint64)
    //确保了每次抽样数据一致,以及单个用户的行为不会被分割 即全部采集或全部不采集
    //如果就是想混乱的随机采集 可以SAMPLE BY intHash64(RowID) RowID是每行唯一的字段
    SELECT Title,count(*) AS PageViews           --sql语义: 抽样 统计CounterID=57 不同Title出现的次数 保留前1000条
    FROM hits_v1
    SAMPLE 0.1                                   --代表采样 ≈10%的数据,也可以是具体的条数 都是≈,因为单用户数据全部被采集或者全部不被采集
    WHERE CounterID=57
    GROUP BY Title
    ORDER BY PageViews DESC LIMIT 1000
  4. 只select需要的字段 减少io/数据解压,where使用分区字段 有效过滤
  5. order by结合where(减少io) limit(减少继续处理/排序的数据量)使用,如果没有使用建表的order by左前缀字段,就会全表扫描
  6. 避免select使用表达式 比如: a,b,c为本来就有的字段 a+b b+1这种表达式应该放到业务层去处理 而不是在sql里面
  7. 业务允许的时候 用近视去重uniqCombined(fld-a) 代替count(distinct fld-a)精准去重
    SELECT COUNT(DISTINCT UserID) from hits_v1;  --对字段UserID去重 精确计数
    SELECT uniqCombined(UserID) from hits_v1;    --使用的是hyperloglog和redis一致
  8. 批量写入时先排序,类似于顺序写和无序写的效率差
  9. 物化视图: 保存一个查询语句的查询结果(放在一张表里面)
    插入数据视图结果也会改变,修改数据不会改变,如果需要改变可以用ReplacingMergeTree/AggregatingMergeTree 或者重新计算
    创建视图的时候可以指定旧的数据是否计算,可以选择不计算,后续手动把部分需要的旧数据计算查询一遍插入视图
8. MaterializeMySQL库引擎
  1. 作用: clickhouse做为mysql的副本,监控mysql的Binlog实现数据的物化副本
  2. clickhouse的MergeTree不适合频繁的删改,会用新增代替删改 每行多加2个对应字段 _sign:行是否删除 _version:行版本号
  3. 实操
    vim /etc/my.cnf
    server-id=1
    log-bin=mysql-bin           #开启binlog
    binlog_format=ROW           #记录具体发生变化的数据行
    gtid-mode=on                #开启gtid
    enforce-gtid-consistency=1  #满足gtid一致性: 解决故障恢复以后 节点知道事务从哪继续执行
    log-slave-updates=1         #记录本机binlog日志
    CREATE DATABASE ck_db ENGINE =
    MaterializeMySQL('127.0.0.1:3306','mysql_db','mysql_user','mysql_pw');
9. 监控
  1. 方案: clickhouse提供监控数据 -> Prometheus(普罗米修斯) 存数据、查数据 → Grafana 后台页面展示 (ps:都属于绿色安装比较简单)
  2. Grafana下载监控模板Json 官网 -> 导入模板 -> 设置告警
a. 备份
  1. 开源工具
  2. 命令
    clickhouse-backup help
    clickhouse-backup tables                             --查看当前 ClickHouse 中的表
    sudo clickhouse-backup create                        --创建备份 --schema:仅备份表结构 --data:仅备份数据 --table=dbname.*:指定表备份
    sudo clickhouse-backup list                          --查看已有备份
    sudo clickhouse-backup restore 2026-09-11T10-25-00   --恢复指定备份 --schema:仅恢复表结构 --data:仅恢复数据 --table=dbname.*:指定表恢复
    sudo clickhouse-backup delete  2026-09-11T10-25-00   --删除指定备份
    sudo clickhouse-backup upload  2026-09-11T10-25-00   --备份上传远程服务器
    sudo clickhouse-backup upload  2026-09-11T10-25-00   --从远程存储下载备份
文档更新时间: 2026-09-14 21:30   作者:morninglu
6. 改表 ALTER (同mysql)