clickhouse存在很多引擎,下面的所有内容基于MergeTree引擎
首先看下官网的主键相关内容:
索引效用实例-以MergeTree 为例
MergeTree 系列的引擎,数据是由多组部分文件组成的,一般来说,每个月(译者注:CK目前最小分区单元是月)会有几个部分文件(这里的部分就是块)。
每一个部分的数据,是按照主键进行字典序排列。例如,如果你有一个主键是(CounterID,Date),数据行会首先按照CounterID排序,如果CounterID相同,按照日期排序。
主键的数据结构,看起来像是标记文件组成的矩阵,这个标记文件就是每间隔index_granularity(索引粒度)行的主键值。
MergeTree引擎中,的默认index_granularity设置的英文8192。
主键是(CounterID,Date)的存储示意图如下:
首先按照CounterID排序,如果CounterID相同,按照日期排序
关于主键还有以下几点需要说明
上述例子中,通过使用哈希函数,把特定的用户名对应的CounterID和EVENTDATE做了聚合,顺便,这种聚合方式,可以在样本这个功能中利用到。稀疏索引适用于海量数据表,并且,稀疏索引文件本身,放到内存是没有问题的
分区,原则是尽量把经常一起用到的数据放到相同区(也可以根据where条件来分区),如果一个区太大再放到多个区,
两种主键,第一种ORDER BY (industry, l1_name, l2_name, l3_name, job_city, job_area, row_id),第二种不包含row_id字段,即ORDER BY (industry, l1_name, l2_name, l3_name, job_city, job_area),其中row_id 是唯一的,在where条件中使用row_id来查询时,你会发现第二种会性能更好,即将row_id从主键中移除,查询效果更好
ck的索引优化
1索引结构是稀疏索引 不要拿mysql的二叉树来类比
建索引的正确方式
开始字段不应该是区分度很高的字段,如果是唯一的,那么索引效果非常差,也不能找区分度特别差的,应该找区分度中等,这就涉及到你的SETTINGS的值,如果比较大,可以找区分度稍差的列,如果比较小,找区分度稍大的列作为索引
boss_info表大概是1500万条数据,20个G数据量
CREATE TABLE bi.boss_info ( row_id String, user_id Int32, user_name String, gender String, title String, is_hr String, certification String, user_status String, user_extra_status String, completion String, lure_content String, email String, brand_id Int32, company_name String, com_id Int32, company_full_name String, website String, address String, brand_completion String, brand_certify String, industry String, scale String, stage String, add_time String, com_description String, com_date8 String, active_time String, unactive_days Int32, job_title String, l1_name String, l2_name String, l3_name String, city String, low_salary Int32, high_salary Int32, degree String, exp_description String, work_years String, job_address String, job_province String, job_city String, job_area String, job_status String, job_num Int32, online_props_buy String, all_item_num Int32, all_income String, online_vip_buy String, online_vip_time String, online_super_vip_buy String, online_super_vip_time String, offline_props_distribute String, offline_props_time String, offline_vip_distribute String, offline_vip_time String, pay_now String, data_dt Date) ENGINE = MergeTree() PARTITION BY data_dt ORDER BY (industry, l1_name, l2_name, l3_name, job_city, job_area, row_id) SETTINGS index_granularity = 8192
CREATE TABLE bi.boss_info2 ( row_id String, user_id Int32, user_name String, gender String, title String, is_hr String, certification String, user_status String, user_extra_status String, completion String, lure_content String, email String, brand_id Int32, company_name String, com_id Int32, company_full_name String, website String, address String, brand_completion String, brand_certify String, industry String, scale String, stage String, add_time String, com_description String, com_date8 String, active_time String, unactive_days Int32, job_title String, l1_name String, l2_name String, l3_name String, city String, low_salary Int32, high_salary Int32, degree String, exp_description String, work_years String, job_address String, job_province String, job_city String, job_area String, job_status String, job_num Int32, online_props_buy String, all_item_num Int32, all_income String, online_vip_buy String, online_vip_time String, online_super_vip_buy String, online_super_vip_time String, offline_props_distribute String, offline_props_time String, offline_vip_distribute String, offline_vip_time String, pay_now String, data_dt Date) ENGINE = MergeTree() PARTITION BY data_dt ORDER BY (industry, l1_name, l2_name, l3_name, job_city, job_area) SETTINGS index_granularity = 16384
建表语句区别:boss_info和boss_info2的区别是去掉了索引中的row_id
现象:
sql1:select row_id from boss_info order by row_id desc limit 3;----耗时较大
结果中第一条是:999997-1
3 rows in set. Elapsed: 0.342 sec. Processed 14.62 million rows, 279.07 MB (42.71 million rows/s., 815.10 MB/s.)
sql2:select row_id from boss_info2 order by row_id desc limit 3;
3 rows in set. Elapsed: 0.061 sec. Processed 14.62 million rows, 279.07 MB (240.21 million rows/s., 4.58 GB/s.)
sql3:select * from boss_info where row_id ='999998-1';----耗时较大,时间不太稳定,再次测平均是4-6s
1 rows in set. Elapsed: 20.228 sec. Processed 13.16 million rows, 24.10 GB (650.83 thousand rows/s., 1.19 GB/s.)
sql4:select * from boss_info2 where row_id ='999997-1';
1 rows in set. Elapsed: 2.195 sec. Processed 14.62 million rows, 279.08 MB (6.66 million rows/s., 127.16 MB/s.)
sql5:select row_id from boss_info order by row_id asc limit 3;
结果中第一条是:1000010-1
3 rows in set. Elapsed: 0.058 sec. Processed 14.62 million rows, 279.07 MB (251.10 million rows/s., 4.79 GB/s.)
sql6:select row_id from boss_info2 order by row_id asc limit 3;
3 rows in set. Elapsed: 0.061 sec. Processed 14.62 million rows, 279.07 MB (240.11 million rows/s., 4.58 GB/s.)
sql7:select * from boss_info where row_id ='1000010-1';
1 rows in set. Elapsed: 4.003 sec. Processed 13.16 million rows, 24.10 GB (3.29 million rows/s., 6.02 GB/s.)
sql8:select * from boss_info2 where row_id ='1000010-1';
1 rows in set. Elapsed: 2.711 sec. Processed 14.62 million rows, 279.07 MB (5.39 million rows/s., 102.95 MB/s.)
sql9:select count(*) from boss_info;
结果:14622978
上面所有的sql都是进行了全表扫描.都是扫描了1500万的数据
sql10:select * from boss_info where industry='咨询' and row_id ='999997-1';
1 rows in set. Elapsed: 0.172 sec. Processed 147.64 thousand rows, 24.10 65B (859.30 thousand rows/s., 1.54 GB/s.)
这句sql没有进行全表扫描,仅仅扫描了15万左右
sql11:select * from boss_info where l1_name='技术' and row_id ='999997-1';
1 rows in set. Elapsed: 1.728 sec. Processed 4.76 million rows, 8.46 GB (2.75 million rows/s., 4.89 GB/s.)
sql12:select * from boss_info where l3_name='C++' and row_id ='999997-1';
1 rows in set. Elapsed: 3.073 sec. Processed 10.06 million rows, 18.03 GB (3.27 million rows/s., 5.87 GB/s.)
select industry, l1_name, l2_name, l3_name, job_city, job_area, row_id from boss_info order by row_id desc limit 3 ;
3 rows in set. Elapsed: 0.264 sec. Processed 14.62 million rows, 1.91 GB (55.45 million rows/s., 7.24 GB/s.)
select * from boss_info order by row_id desc limit 3 ;
3 rows in set. Elapsed: 3.299 sec. Processed 14.62 million rows, 25.51 GB (4.43 million rows/s., 7.73 GB/s.)
select * from boss_info where row_id ='999988-9';
先说ck的索引结构:
典型的稀疏索引,即ck中数据的存储会按照order by的字段顺序存储,同时会根据你设置的setting(即索引粒度)来抽样数据,数据内容就是order by字段对应的真实值;
问题:(说明下面答案都是笔者猜测的,没有十足把握确认正确)
1为什么sql10索引生效,sql1和sql3中的row_id索引都没有生效,甚至拉慢了查询效率
索引结构问题,ck是稀疏索引,sql10中industry刚好是索引的第一部分,所以索引生效直接定位范围区间;但是sql1和sql3中row_id是索引的最后一部分,定位到的返回就会是全表范围,所以真正取值时要进行全表扫描。
拉慢查询效率原因:
首先会扫描所有的索引来定位取值范围,但是定位到的取值范围就是全表,所以此步完全是多做的
然后会进行全表扫描
2sql3和sql4同是全表扫描,为什么sql3扫描数据量24GB,而sql4只有279M,导致sql3慢了10倍
数据量近百倍差距--->row_id在索引中是一定加载了所有字段,不在索引中仅仅扫描了row_id字段
row_id是索引一部分的时候 因为用row_id定位的区间是全部(稀疏和在索引末尾部分导致),所以全部字段加载到内存,再找符合row_id条件的记录?没有索引的时候就先用row_id匹配,仅仅读取row_id列,然仅仅加载row_id所在的一个粒度区间所有内容
3sql1和sql2相比,为何sql1用了索引反而慢了5倍,为何sql1比sql2的处理速度要快
可以参考问题一,先走了索引,但实际白走了