一,ClickHouse 引擎Merge时机与问题
项目使用ClickHouse对外提供数据,表引擎选型ReplicatedReplacingMergeTree。
该引擎在插入数据时,针对相同主键(order by字段)的数据进行去重,删除重复的数据,保留最新的数据,最新数据的依据可自定义,如插入ClickHouse时间,在建表时指定。
CREATE TABLE dev_test7.ods_sp_finance_events_local ON cluster default_cluster
(
`index` Int32 COMMENT '',
ck_insert_time DateTime DEFAULT now() COMMENT '写入CK时间'
) engine = ReplicatedReplacingMergeTree(
'/clickhouse/tables/dev_te/{shard}/ods__events_local',
'{replica}', ck_insert_time -- 这里指定判断最新数据的依据是 ck_insert_time
)
primary key (company_id)
partition by (company_id, financial_event_group_id)
order by (company_id, financial_event_group_id,event_type,`index`)
settings replicated_deduplication_window = 0 ,parts_to_throw_insert = 1000, parts_to_delay_insert = 300;
项目数据更新采用全量覆盖方式,利用ReplicatedReplacingMergeTree合并新旧版本数据,但合并数据的时机不确定、不可控,目前有两种方式解决这个问题:
- 1,使用optimize语句触发合并
OPTIMIZE TABLE example FINAL DEDUPLICATE;
实际测试时发现这种方式的结果不可预测,会出现合并不完全、合并时间延迟大的问题。
- 2,查询时使用final关键字
select * from dev_test7.ods_sp_finance_events final
二,使用JDBC写入ClickHouse的空值问题
使用JDBC写入CH过程中,如果有空值,很容易发生如下错误:
ru.yandex.clickhouse.except.ClickHouseException: ClickHouse exception, code: 27, host: 10.49.0.46, port: 8123; Code: 27, e.displayText() = DB::Exception: Cannot parse input: expected \t before: \\N\t90136203343433728\tIT\t\\N\t\\N\t-2.78\tEUR\t\\N\t\\N\t\\N\t\\N\tRefundEventList\t\\N\t\\N\t\\N\tZ4Uoc3mzDsttLyXl4EONBA0OwGDc1sOLC4TDswprWuE\t\\N\tFBM\t\\N\t2\t\\N\tShipmentItemAdjustmentLi: (at row 1)
根本原因在ClickHousePreparedStatement
在处理空值时使用"\N"对空值进行编码:
对于字符串类型这样处理是没有问题的,但对于Double、Long、Integer、DateTime等类型,CH不会认为”\N“为空,而是尝试结合类型长度将”\N“及其之后的一定长度字符进行转换,通常,这种转换会失败,抛出前面提到的错误。
解决办法:对可能为空的非字符串类型进行判空、赋初值。
三,clickhouse空值问题2
如果字段使用了Nullable类型,jdbc写入时可以是空,但不能是空字符串,因为如果是空字符串会导致在序列化时丢失这个字段的位置,导致反序列化时字段错位