Clickhouse在数据一致性上没有很好的表现,其提供的ReplacingMergeTree只能够保证数据的最终一致性,因此在使用ReplacingMergeTree、 SummingMergeTree 这类表引擎的时候,会出现短暂
数据不一致的情况。
相关案例:
(1) 创建表
CREATE TABLE test_a(
user_id UInt64,
score String,
deleted UInt8 DEFAULT 0,
create_time DateTime DEFAULT toDateTime(0)
)ENGINE= Replac ingMergeTree(create_time)
ORDER BY user_id
user_id 是数据去重更新的标识 ;
create_time 是版本号字段,每组数据中 create_time 最大的一行表示最新的数据 ;
deleted 是自定的一个标记位,比如 0 代表未删除, 1 代表删除数据。
(2) 写入 1000万 测试数据
INSERT INTO TABLE test_a(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)
(3) 修改前 50万 行数据,修改内容包括 name 字段和 create_time 版本号字段
INSERT INTO TABLE test_a(user_id,score,create_time)
WITH(
SELECT ['AA','BB','CC','DD','EE' EE','FF','
)AS dict
SELECT number AS user_id, dict[number%7+1], now() AS create_time FROM
numbers(500000)
(4)统计总数
SELECT COUNT() FROM test_a;
10500000
由于ReplacingMergeTree还未触发分区合并,所以数据还未去重。
可以使用OPTIMIZE TABLE test_a FINAL命令强制表进行合并,但在数据合并期间CK将不能提供服务,这在生产环境是禁止出现的。
(1)执行去重的查询
SELECT
user_id ,
argMax(score, create_time) AS score,
argMax(deleted, create_time) AS deleted,
max(create_time) AS ctime
FROM test_a
GROUP BY user_id
HAVING deleted = 0
◼ argMax(field1 field2):按照 field2 的最大值取 field1 的值。
当我们更新数据时,会写入一行新的数据,例如上面语句中, 通过查询最大的create_time 得到修改后的 score字段值 。
(2)创建视图,方便测试
CREATE VIEW view_test _a AS
SELECT
user_id ,
argMax(score, create_time) AS score,
argMax(deleted, create_time) AS deleted,
max(create_time) AS ctime
FROM test_a
GROUP BY user_id
HAVING deleted = 0
通过使用group by和筛选最大标记值的方法能够查询出最新的数据,使用视图对查询进行存储后能提高查询效率。
一些版本号小的数据并没有被真正的删除,而是被过滤掉了。在一些合适的场景下,可以结合
表级别的 TTL 最终将物理数据删除。
在查询语句后增加FINAL修饰符 这样在查询的过程中将会执行 Merge的特殊逻辑 例如数据去重 预聚合等 。
但是这种方法在早期版本 基本没有人使用,因为在增加 FINAL 之后,我们的查询将会变
成一个单线程的执行过程,查询速度非常慢。
在v20.5.2.7-stable版本中, FINAL查询 支持多线程 执行,并且可以通过 max_final_threads 参数 控制单个查询的线程数。 但是 目前读取 part部分的动作依然是串行的 。
FINAL查询最终的性能和很多因素相关,列字段的大小、分区的数量等等都会影响到最
终的查询时间,所以还要 结合实际场景取舍 。
普通查询测试
explain pipeline select * from visits v 1 WHERE StartDate = '201 4 0 3 1 7
limit 100 settings max_threads = 2
(Expression)
ExpressionTransform × 2
(SettingQuotaAndLimits)
(Limit)
Limit 2->2
(ReadFromMergeTree)
MergeTreeThread × 2 0->1
明显将由2个线程并行读取 part 查询。
FINAL查询测试
explain pipeline select * from visits v 1 final WHERE StartDate = '2014-03-17 limit 100 settings max_final_threads = 2;
(Expression)
Expre ssionTransform × 2
(SettingQuotaAndLimits)
(Limit)
Limit 2->2
(ReadFromMergeTree)
ExpressionTransform × 2
CollapsingSortedTra nsform × 2
Copy 1->2
AddingSelector
ExpressionTransform
MergeTree 0->1
从CollapsingSortedTransform这一步开始已经是多线程执行 但是 读取 part 部分的动作还是串行。
ClickHouse的物化视图是一种查询结果的持久化,它确实是给我们带来了查询效率的提升。用户查起来跟表没有区别,它就是一张表,它也像是一张时刻在预计算的表,创建的过程它是用了一个特殊引擎,加上后来 as select 就是 create一个 table as select的写法。
“查询结果集”的范围很宽泛,可以是基础表中部分数据的一份简单拷贝,也可以是多表 join之后产生的结果或其子集,或者原始数据的聚合指标等等。所以,物化视图不会随着基础表的变化而变化,所以它也称为快照( snapshot
普通视图不保存数据,保存的仅仅是查询语句,查询的时候还是从原表读取数据,可以将普通视图理解为是个子查询。 物化视图则是把查询的结果根据相应的引擎存入到了磁盘或内存中 ,对数据重新进行了组织,你可以理解物化视图是完全的一张新表。
优点:查询速度快 ,要是把物化视图这些规则全部写好,它比原数据查询快了很多,总的行数少了,因为都预计算好了。
缺点:它的本质是一个流式数据的使用场景,是累加式的技术,所以要用历史数据做去重、去核这样 的 分析,在物化视图里面是不太好用的。在某些场景的使用也是有限的。而且如果一张表加了好多物化视图,在写这张表的时候,就会消耗很多机器的资源, 比如 数据带宽占满、存储一下子增加了很多。
一张表如果是多张物理视图的数据来源,表数据更新后,多张物理视图在进行更改时会消耗大量资源。
在ClickHouse中,创建物化视图的语法如下:也是create语法,会 创建一个隐藏的目标表来保存视图数据。 也可以 TO 表名,保存到一张显式的表。 没有加 TO表名,表名默认就是 .inner.物化视图名
CREATE [MATERIALIZED] VIEW [IF NOT EXISTS] [db.]table_name [TO[db.]name]
[ENGINE = engine] [POPULATE] AS SELECT
创建物化视图的限制:
1.必须指定物化视图的 engine 用于数据存储
2.TO [db].[table]语法的时候,不得使用 POPULATE。
3.查询语句 (select)可以包含下面的子句 DISTINCT, GROUP BY, ORDER BY, LIMIT
4.物 化视图的 alter操作有些限制,操作起来不大方便。
5.若物化视图的定义使用了 TO [db.]name 子语句,则可以将目标表的视图 卸载
DETACH 再 装载 ATTACH
物化视图的数据更新:
(1 物化视图创建好之后,若源表被写入新数据则物化视图也会同步更新
(2 POPULATE 关键字决定了物化视图的更新策略:
◼ 若有 POPULATE 则在创建视图的过程会将源表已经存在的数据一并导入,类似于
create table … as
◼ 若无 POPULATE 则物化视图在创建之后没有数据,只会在创建只有同步之后写入
源表的数据
◼ clickhouse 官方并不推荐使用 POPULATE,因为在创建物化视图的过程中同时写入
的数据不能被插入物化视图。
(3 物化视图不支持同步删除,若源表的数据不存在(删除了)则物化视图的数据仍然保留
(4 物化视图是 一种 特殊的数据表,可以用 show tables 查看
(5 物化视图数据的删除:
(6 物化视图的删除:
对于一些确定的数据模型,可将统计指标通过物化视图的方式进行构建,这样可避免查
询时重复计算的过程,物化视图会在有新数据插入时进行更新。
MySQL 的用户群体很大 为了能够增强数据的实时性 很多解决方案会利用 binlog 将数据写入到 ClickHouse。为了能够监听 binlog 事件,我们需要用到类似 canal 这样的第三方中间件,这无疑增加了系统的复杂度。
ClickHouse20.8.2.3 版本新增加了 MaterializeMySQL 的 database 引擎,该 database 能映射到 MySQL 中的某个 database ,并自动在 ClickHouse 中创建对应的ReplacingMergeTree 。ClickHouse 服务做为 MySQL 副本,读取 Binlog 并执行 DDL 和 DML 请求,实现了基于 MySQL Binlog 机制的业务数据库实时同步功能。
(1) MaterializeMySQL 同时支持 全量 和 增量 同步 在 database 创建之初会全量同步MySQL 中的表和数据 之后则会通过 binlog 进行增量同步。
(2) MaterializeMySQL database 为其所创建的每张 ReplacingMergeTree 自动增加了_sign 和 _version 字段。
其中_version 用作 ReplacingMergeTree 的 ver 版本参数 每当监听到 insert、 update 和 delete 事件时 在 databse 内全局自增。而 _sign 则用于标记是否被删除,取值 1 或者 -1 。
目前MaterializeMySQL 支持如下几种 binlog 事件 :
➢ MYSQL_WRITE_ROWS_EVENT: _sign = 1,,_version ++
➢ MYSQL_DELETE_ROWS_EVENT: _sign = -1,,_version ++
➢ MYSQL_UPDATE_ROWS_EVENT: 新数据 _sign = 1
➢ MYSQL_QUERY_EVENT: 支持 CREATE TABLE 、 DROP TABLE 、 RENAME TABLE等。
(1) DDL查询
MySQL DDL查询被转换成相应的 ClickHouse DDL查询( ALTER, CREATE, DROP, RENAME)。
如果 ClickHouse不能解析某些 DDL查询,该查询将被忽略。
(2) 数据复制
MaterializeMySQL不支持直接插入、删除和更新查询,而是将 DDL语句进行相应转换:
MySQL INSERT查询被转换为 INSERT with _sign=1。
MySQL DELETE查询被转换为 INSERT with _sign=-1。
MySQL UPDATE查询被转换成 INSERT with _sign=1和 INSERT with _sign=-1
(3) SELECT查询
如果在SELECT查询中没有指定 _version,则使用 FINAL修饰符,返回 _version的最大值对应的数据,即最新版本的数据。如果在SELECT查询中没有指定 _sign,则默认使用 WHERE _sign=1,即返回未删除状态(_sign=1)的数据。
(4) 索引转换
ClickHouse数据库表会自动将 MySQL主键和索引子句转换为 ORDER BY元组。ClickHouse只有一个物理顺序,由 ORDER BY子句决定。如果需要创建新的物理顺序,请使用物化视图。
(1 问题: 使用分布式 ddl执行命令 create table on cluster xxxx 某个节点上没有创建
表,但是 client返回正常,查看日志有如下报错。
xxx xxx : Retrying createReplica(), because some other replicaswere created at the same time
(2 解决办法: 重启该不执行的节点。
CK的同一数据的副本之间能够自行同步数据。
(1 问题: 由于某个数据节点副本异常,导致两数据副本表不一致,某个数据副本缺少表,需要将两个数据副本调整一致。
(2 解决办法:
在缺少表的数据副本节点上创建缺少的表,创建为本地表,表结构可以在其他数据副本通过 show crete table xxxx获取。
表结构创建后, clickhouse会自动从其他副本同步该表数据,验证数据量是否一致即可。
(1)问题 某个数据副本异常无法启动,需要重新搭建副本。
(2)解决办法
清空异常副本节点的metadata和 data目录。从另一个正常副本将metadata目录拷贝过来(这一步之后可以启动数据库,但是只有表结构没有数据)。
执行sudo -u clickhouse touch /data/clickhouse/flags/force_restore_data
启动数据库。
(1)问题 某个数据副本表在 zk上丢失数据,或者不存在,但是 metadata元数据里
存在,导致启动异常,报错:
Can t get data for node /clickhouse/tables/0102/xxxxx/xxxxxxx/replicas/ xxx /metadata: node doesn t exist (No node):Cannot attach table xxxxxxx
(2)解决办法
metadata中移除该表的结构文件,如果多个表报错都移除mv metadata/xxxxxx/xxxxxxxx.sql /tmp/
启动数据库
手工创建缺少的表,表结构从其他节点show create table获取。
创建后会自动同步数据,验证数据是否一致。
(1)问题 重建表过程中,先使用 drop table xxx on cluster xxx ,各节点在 clickhouse上table已物理删除,但是 zk里面针对某个 clickhouse节点的 table meta信息未被删除(低概率事件),因 zk里仍存在该表的 meta信息,导致再次创建该表 create table xxx on cluster, 该节点无法创建表 (其他节点创建表成功 ),报错
Replica /clickhouse/tables/01-03/xxxxxx/xxx/replicas/xxx already exists…
(2)解决办法从其他数据副本cp该 table的 metadata sql过来 .重启节点。
(1)问题 模拟其中一个节点意外宕机,在大量 insert数据的情况下,关闭某个节点。
(2 现象: 数据写入不受影响、数据查询不受影响、建表 DDL执行到异常节点会卡住,
报错:
Code: 159. DB:: Exception: Received from localhost:9000. DB::Exception:Watching task /clickhouse/task_queue/ddl/query 0000565925 is executinglonger than distributed_ddl_task_timeout (=180) seconds. There are 1unfin ished hosts (0 of them are currently active), they are going toexecute the query in background.
(3)解决办法 启动异常节点,期间其他副本写入数据会自动同步过来,其他副本的
建表 DDL也会同步。