使用合并树引擎时,无论是ReplacingMergeTree还是SummingMergeTree,都只能保证数据的最终一致性,因为数据的去重、聚合等操作会在数据合并的期间进行,而合并会在后台以一个不确定的时间进行,因此无法预先计划;
数据准备
建表:
CREATE TABLE test_consistence(user_id UInt64,score String,deleted UInt8 DEFAULT 0,create_time DateTime DEFAULT toDateTime(0)
)ENGINE= ReplacingMergeTree(create_time)
ORDER BY user_id;
其中deleted
作为是否删除的标识;create_time
是版本号字段,每组数据中 create_time 最大的一行表示最新的数据
导入数据:
INSERT INTO TABLE test_consistence(user_id,score)
WITH(SELECT ['A','B','C','D','E','F','G']
)AS dict
SELECT number AS user_id, dict[number%7+1] FROM numbers(10000000);
数据结构如下:
通过修改create_time
的值,可以更新数据:
INSERT INTO TABLE test_consistence(user_id,score,create_time)
WITH(SELECT ['AA','BB','CC','DD','EE','FF','GG']
)AS dict
SELECT number AS user_id, dict[number%7+1], now() AS create_time FROM
numbers(500000);
now()
函数可以获取当前时间
查询发现,此时表中共有10500000
条数据,说明还未进行去重;
去重方案
手动 OPTIMIZE
在写入数据后,立刻执行 OPTIMIZE 强制触发新写入分区的合并动作
optimize table test_consistence;
耗费时间:
可以看到,optimize是一个相对比较耗时的操作(与select、insert相比),因为需要进行大量的数据读写
通过 Group by 去重
执行去重的查询
SELECTuser_id ,argMax(score, create_time) AS score, argMax(deleted, create_time) AS deleted,max(create_time) AS ctime
FROM test_consistence
GROUP BY user_id
HAVING deleted = 0;
函数argMax
:argMax(field1,field2)
:按照 field2 的最大值取 field1 的值
因此argMax(score, create_time)
的含义就是,按照create_time的最大值取score的值,因为create_time代表当前数据的更新时间,因此总能取到最新的数据;
同理argMax(deleted, create_time)
是取最新的deleted
标识,来判断数据是否被删除
创建视图
通过上面的操作我们可以实现数据的去重等操作,从而保证一致性,因此创建视图来保存数据查询的逻辑
CREATE VIEW view_test_consistence AS
SELECTuser_id ,argMax(score, create_time) AS score, argMax(deleted, create_time) AS deleted,max(create_time) AS ctime
FROM test_consistence
GROUP BY user_id
HAVING deleted = 0;
视图创建的格式:create view view_name as select......
注意:这里创建的视图仅仅保存了数据查询的逻辑,并不保存具体的数据;
测试去重效果
插入数据:
insert into test_consistence(user_id, score, create_time) values(0, 'AAAA', now());
然后查询数据:
select * from test_consistence where user_id = '0';
发现并未去重;
而从视图中查询:
select * from view_test_consistence where user_id = '0';
则仅有最新的数据:
通过 FINAL 查询
在查询语句后增加 FINAL 修饰符,这样在查询的过程中将会执行 Merge 的特殊逻辑(例如数据去重,预聚合等)
测试
① 不使用final查询:
select * from visits_v1 WHERE StartDate = '2014-03-17' limit 100;
②使用final查询:
select * from visits_v1 final WHERE StartDate = '2014-03-17' limit 100;
可以看到,查询速度并没有普通的查询快,因此使用final确保数据一致性也是以效率为代价的;
版本说明
早期版本增加 FINAL 之后,查询会变成单线程;但从v20.5.2.7-stable版本之后转为多线程执行,并可以通过max_final_threads
参数控制单个查询的线程数:
explain pipeline select * from visits_v1 final WHERE StartDate = '2014-03-17' limit 100;
可以看到从 CollapsingSortedTransform 这一步开始已经是多线程执行,但是读取 part 部分的动作还是串行