• ClickHouse 语法优化规则


    ClickHouse 的 SQL 优化规则是基于RBO(Rule Based Optimization),下面是一些优化规则

    准备测试用表

    1上传官方的数据集

    visits_v1.tarhits_v1.tar上传到虚拟机,解压到clickhouse数据路径下

    // 解压到clickhouse数据路径

    1. sudo tar -xvf hits_v1.tar -C /var/lib/clickhouse
    2. sudo tar -xvf visits_v1.tar -C /var/lib/clickhouse
    3. //修改所属用户
    4. sudo chown -R clickhouse:clickhouse /var/lib/clickhouse/data/datasets
    5. sudo chown -R clickhouse:clickhouse /var/lib/clickhouse/metadata/datasets

    2重启clickhouse-server

    sudo clickhouse restart

    3执行查询

    clickhouse-client --query "SELECT COUNT(*) FROM datasets.hits_v1"

    clickhouse-client --query "SELECT COUNT(*) FROM datasets.visits_v1"

    注意:官方的tar包,包含了建库、建表语句、数据内容,这种方式不需要手动建库、建表,最方便。

    hits_v1表有130多个字段,880多万条数据

    visits_v1表有180多个字段,160多万条数据

    COUNT 优化

    在调用 count 函数时如果使用的是 count() 或者 count(*),且没有 where 条件则会直接使用 system.tables  total_rows,例如:

    1. EXPLAIN SELECT count()FROM datasets.hits_v1;
    2. Union
    3.   Expression (Projection)
    4.     Expression (Before ORDER BY and SELECT)
    5.       MergingAggregated
    6.         ReadNothing (Optimized trivial count)

    注意 Optimized trivial count ,这是对 count 的优化。

    如果 count 具体的列字段,则不会使用此项优化:

    1. EXPLAIN SELECT count(CounterID) FROM datasets.hits_v1;
    2. Union
    3.   Expression (Projection)
    4.     Expression (Before ORDER BY and SELECT)
    5.       Aggregating
    6.         Expression (Before GROUP BY)
    7.           ReadFromStorage (Read from MergeTree)

    消除子查询重复字段

    下面语句子查询中有两个重复的 id 字段会被去重:

    1. EXPLAIN SYNTAX SELECT 
    2.    a.UserID,
    3.    b.VisitID,
    4.    a.URL,
    5.    b.UserID
    6.    FROM
    7.    hits_v1 AS a 
    8.    LEFT JOIN (
    9.     SELECT       
    10.       UserID,
    11.       UserID as HaHa,
    12.       VisitID   
    13.     FROM visits_v1AS b 
    14.    USING (UserID)
    15.    limit 3;
    16. //返回优化语句:
    17. SELECT 
    18.     UserID,
    19.     VisitID,
    20.     URL,
    21.     b.UserID
    22. FROM hits_v1 AS a
    23. ALL LEFT JOIN 
    24. (
    25.     SELECT 
    26.         UserID,
    27.         VisitID
    28.     FROM visits_v1
    29. AS b USING (UserID)
    30. LIMIT 3

    4 谓词下推

    当group by有having子句,但是没有with cube、with rollup 或者with totals修饰的时候,having过滤会下推到where提前过滤。例如下面的查询,HAVING name变成了WHERE name,在group by之前过滤:

    1. EXPLAIN SYNTAX SELECT UserID FROM hits_v1 GROUP BY UserID HAVING UserID = '8585742290196126178';
    2. //返回优化语句
    3. SELECT UserID
    4. FROM hits_v1
    5. WHERE UserID = \'8585742290196126178\'
    6. GROUP BY UserID

    子查询也支持谓词下推:

    1. EXPLAIN SYNTAX
    2. SELECT *
    3. FROM 
    4. (
    5.     SELECT UserID
    6.     FROM visits_v1
    7. )
    8. WHERE UserID = '8585742290196126178'
    9. //返回优化后的语句
    10. SELECT UserID
    11. FROM 
    12. (
    13.     SELECT UserID
    14.     FROM visits_v1
    15.     WHERE UserID = \'8585742290196126178\'
    16. )
    17. WHERE UserID = \'8585742290196126178\'

    再来一个复杂例子:

    1. //返回优化后的语句
    2. SELECT UserID
    3. FROM 
    4. (
    5.     SELECT UserID
    6.     FROM 
    7. (
    8.         SELECT UserID
    9.         FROM visits_v1
    10.         WHERE UserID = \'8585742290196126178\'
    11. )
    12.     WHERE UserID = \'8585742290196126178\'
    13.     UNION ALL
    14.     SELECT UserID
    15.     FROM 
    16. (
    17.         SELECT UserID
    18.         FROM visits_v1
    19.         WHERE UserID = \'8585742290196126178\'
    20. )
    21.     WHERE UserID = \'8585742290196126178\'
    22. )
    23. WHERE UserID = \'8585742290196126178\'

    5 聚合计算外推

    聚合函数内的计算会外推例如

    1. EXPLAIN SYNTAX
    2. SELECT sum(UserID * 2)
    3. FROM visits_v1
    4. //返回优化后的语句
    5. SELECT sum(UserID) * 2
    6. FROM visits_v1

    6 聚合函数消除

    如果对聚合键,也就是 group by key 使用 min、max、any 聚合函数,则将函数消除,例如:

    1. EXPLAIN SYNTAX
    2. SELECT
    3. sum(UserID * 2),
    4. max(VisitID),
    5. max(UserID)
    6. FROM visits_v1
    7. GROUP BY UserID
    8. //返回优化后的语句
    9. SELECT 
    10. sum(UserID) * 2,
    11. max(VisitID),
    12.     UserID
    13. FROM visits_v1
    14. GROUP BY UserID

    7 删除重复的 order by key

    例如下面的语句,重复的聚合键 id 字段会被去重:

    1. EXPLAIN SYNTAX
    2. SELECT *
    3. FROM visits_v1
    4. ORDER BY
    5.     UserID ASC,
    6.     UserID ASC,
    7.     VisitID ASC,
    8. VisitID ASC
    9. //返回优化后的语句:
    10. select
    11. ……
    12. FROM visits_v1
    13. ORDER BY 
    14.     UserID ASC,
    15. VisitID ASC

    删除重复的 limit by key

    例如下面的语句重复声明的 name 字段会被去重

    1. EXPLAIN SYNTAX
    2. SELECT *
    3. FROM visits_v1
    4. LIMIT 3 BY
    5.     VisitID,
    6.     VisitID
    7. LIMIT 10
    8. //返回优化后的语句:
    9. select
    10. ……
    11. FROM visits_v1
    12. LIMIT 3 BY VisitID
    13. LIMIT 10

    删除重复的 USING Key

    例如下面的语句,重复的关联键 id 字段会被去重:

    1. EXPLAIN SYNTAX
    2. SELECT
    3.     a.UserID,
    4.     a.UserID,
    5.     b.VisitID,
    6.     a.URL,
    7.     b.UserID
    8. FROM hits_v1 AS a
    9. LEFT JOIN visits_v1 AS b USING (UserID, UserID)
    10. //返回优化后的语句:
    11. SELECT 
    12.     UserID,
    13.     UserID,
    14.     VisitID,
    15.     URL,
    16.     b.UserID
    17. FROM hits_v1 AS a
    18. ALL LEFT JOIN visits_v1 AS b USING (UserID)

    10 标量替换

    如果子查询只返回一行数据在被引用的时候用标量替换例如下面语句中的 total_disk_usage 字段

    1. EXPLAIN SYNTAX
    2. WITH 
    3. (
    4.         SELECT sum(bytes)
    5.         FROM system.parts
    6.         WHERE active
    7. AS total_disk_usage
    8. SELECT
    9. (sum(bytes) / total_disk_usage) * 100 AS table_disk_usage,
    10.     table
    11. FROM system.parts
    12. GROUP BY table
    13. ORDER BY table_disk_usage DESC
    14. LIMIT 10;
    15. //返回优化后的语句:
    16. WITH CAST(0, \'UInt64\'AS total_disk_usage
    17. SELECT 
    18. (sum(bytes) / total_disk_usage) * 100 AS table_disk_usage,
    19.     table
    20. FROM system.parts
    21. GROUP BY table
    22. ORDER BY table_disk_usage DESC
    23. LIMIT 10

    11 三元运算优化

    如果开启了 optimize_if_chain_to_multiif 参数,三元运算符会被替换成 multiIf 函数,例如:

    1. EXPLAIN SYNTAX 
    2. SELECT number = 1 ? 'hello' : (number = 2 ? 'world' : 'atguigu')
    3. FROM numbers(10)
    4. settings optimize_if_chain_to_multiif = 1;
    5. // 返回优化后的语句:
    6. SELECT multiIf(number = 1, \'hello\'number = 2, \'world\', \'atguigu\')
    7. FROM numbers(10)
    8. SETTINGS optimize_if_chain_to_multiif = 1

  • 相关阅读:
    【好书推荐】探究构架设计的方法论 | 《架构整洁之道》
    Linux常用的指令(总结)
    美创科技入选“浙江省网络安全规模十强企业”
    华为OD机考:0023-磁盘容量排序
    【论文阅读】Mastering the game of Go with deep neural networks and tree search
    java学习笔记第一天
    Unity获取脚本的CustomEditor(自定义编辑)数据
    Jenkins pipline集成发布到K8s
    pgpool-II 4.3 中文手册 - 入门教程
    E044-服务漏洞利用及加固-利用redis未授权访问漏洞进行提权
  • 原文地址:https://blog.csdn.net/shangjg03/article/details/134478598