数据一致性

查询 CK 手册发现,即便对数据一致性支持最好的 Mergetree,也只是保证最终一致性

数据一致性 - 图1

在使用ReplacingMergeTree、SummingMergeTree这类表引擎的时候,会出现短暂数据不一致的情况 在某些对一致性非常敏感的场景,通常有以下几种解决方案

准备测试表和数据

1)准备表
  1. CREATE TABLE test_a(
  2. user_id UInt64,
  3. score String,
  4. deleted UInt8 DEFAULT 0,
  5. create_time DateTime DEFAULT toDateTime(0)
  6. )ENGINE= ReplacingMergeTree(create_time)
  7. ORDER BY user_id;
其中: user_id是数据去重更新的标识 create_time是版本号字段,每组数据中create_time最大的一行表示最新的数据 deleted是自定的一个标记位,比如0代表未删除,1代表删除数据 2)写入1000万测试数据
  1. INSERT INTO TABLE test_a(user_id,score)
  2. WITH(
  3. SELECT ['A','B','C','D','E','F','G']
  4. )AS dict
  5. SELECT number AS user_id, dict[number%7+1] FROM numbers(10000000);
3)修改前50万行数据,修改内容包括name字段和create_time版本号字段
  1. INSERT INTO TABLE test_a(user_id,score,create_time)
  2. WITH(
  3. SELECT ['AA','BB','CC','DD','EE','FF','GG']
  4. )AS dict
  5. SELECT number AS user_id, dict[number%7+1], now() AS create_time FROM
  6. numbers(500000);
4)统计总数
  1. select count() from test_a;
10500000条 还未触发分区合并,所以还未去重

手动OPTIMIZE(不推荐)

在写入数据后,立刻执行OPTIMIZE强制触发新写入分区的合并动作
  1. OPTIMIZE TABLE test_a FINAL;

通过group by去重

1)执行去重的数据
  1. select
  2. user_id ,
  3. argMax(score, create_time) as score,
  4. argMax(deleted, create_time) as deleted,
  5. max(create_time) as ctime
  6. from test_a
  7. group by user_id
  8. having deleted = 0;
argMax(field1,field2):按照field2的最大值取field1的值

当我们更新数据时,会写入一行新的数据,例如上面语句中,通过查询最大的create_time得到修改后的score字段值

2)创建视图,方便测试 把上面步骤记录下来
  1. CREATE VIEW view_test_a AS
  2. SELECT
  3. user_id ,
  4. argMax(score, create_time) AS score,
  5. argMax(deleted, create_time) AS deleted,
  6. max(create_time) AS ctime
  7. FROM test_a
  8. GROUP BY user_id
  9. HAVING deleted = 0;
3)插入重复数据,再次查询 再次插入一条数据
  1. insert into table test_a(user_id,score,create_time)
  2. values(0,'AAAA',now());
再次查询
  1. select *
  2. from view_test_a
  3. where user_id = 0;
4)删除数据测试 再次插入一条标记为删除的数据
  1. insert into table test_a(user_id,score,deleted,create_time)
  2. values(0,'AAAA',1,now());
再次查询,刚才那条数据看不到了
  1. select *
  2. from view_test_a
  3. where user_id = 0;
这行数据并没有被真正的删除,而是被过滤掉了。在一些合适的场景下,可以结合表级别的TTL最终将物理数据删除

通过final查询

在查询语句后增加FINAL修饰符,这样在查询的过程中将会执行Merge的特殊逻辑(例如数据去重、预聚合等)

但是这种方法在早期版本基本没有人使用,因为在增加FINAL之后,我们的查询将会变成一个单线程的执行过程,查询速度非常慢

在v20.5.2.7-stable版本中,FINAL查询支持多线程执行,并且可以通过max_final_threads参数控制单个查询的线程数。但是目前读取part部分的动作依然是串行的

FINAL查询最终的性能和很多因素相关,列字段的大小、分区的数量等等都会影响到最终的查询时间,所以还要结合实际场景取舍

  1. explain pipeline select * from datasets.visits_v1 final where StartDate = '2014-03-17' settings max_final_threads = 2;