ByteHouse云数仓版的CnchMergeTree表引擎支持在建表时设置唯一键(UNIQUE KEY),创建一个唯一键表,本文为您介绍唯一键表的能力细节和使用示例。
唯一键表即指定唯一键(UNIQUE KEY)的 CnchMergeTree 表,具有以下特点:
对比项 | 唯一键表 | 非唯一键表 |
---|---|---|
唯一键约束 | 保证 | 不保证 |
支持 | 不支持同步更新 | |
支持 | 不支持同步删除 | |
支持 | 不支持 | |
UPSERT / INSERT THROW/ INSERT IGNORE | 支持 说明 当使用唯一键表进行 insert 时,默认会实现 upsert 语义,即保留每个唯一键的最新值。 | 不支持 |
常用 Unique Key 字段类型 | 其他 Unique key 的字段类型 |
---|---|
String, UInt8, UInt16, UInt32, UInt64, Int8, Int16, Int32, Int64 | Decimal32, Decimal64, Date, Date32, DateTime, DateTime64, Time |
如果希望使用不支持的字段类型用作 Unique key 字段,可以尝试使用 sipHash64()
将对应字段类型转化为 UInt64。
说明
sipHash64
是一种快速且低冲突率的哈希函数,实践中未遇到 hash 冲突的场景。
Bytehouse 唯一键表默认提供分区级唯一作为去重粒度,当 cluster by 所需列为 unique key 字段所需列子集时,去重粒度可以优化到 bucket 唯一。不同的去重粒度能够在元数据级别限制待去重的数据量,进而满足用户不同的写入诉求:
注意
唯一键表在写入时,需要通过唯一键,找到每条记录原来所在的位置,因此待去重的存量数据行数越多,写入 rps 越低;通常,在存量数据为 50,000,000 时,写入 rps 为 20,000。
对比项 | 分区级唯一+非bucket去重优化 | 分区级唯一+bucket去重优化 |
---|---|---|
去重说明 | 分区级别去重,新写入的数据会跟对应分区中所有的存量数据进行去重。 | 分区级别的 bucket 去重,新写入的数据会跟表中对应分区,相同 bucket 的存量数据去重。 |
去重存量数据示例 |
|
|
唯一键表使用了 Delete-and-Insert 策略,当更新数据到来时,通过唯一键,先找到每条记录原来所在的位置,将该条记录标记为删除,然后将最新的数据作为新记录写入到新的数据文件中。读取时,根据删除标记,将已删除的旧版本数据过滤掉,从而查询到唯一键的最新值。
例如,以下示例中表为分区级唯一,PK 为 partition key,UK 为 unique key,delete bitmap 为标记删除列。
Delete bitmap 列为 1 的数据被标记删除,在增量数据写入后,最新数据为:
2020-10-29,1,m 2020-10-29,2,n 2020-10-30,1,z 2020-10-30,2,k
唯一键表由于去重特性,为了进行高效的数据写入,对于数据量和 Unique Key 数量具有如下使用限制:
以下为您提供两个典型使用示例,更多示例可参见ByteHouse Unique 表最佳实践。
CREATE TABLE t1 ( `event_time` DateTime, `product_id` UInt64, `city` String, `category` String, `amount` UInt32, `revenue` UInt64 ) ENGINE = CnchMergeTree PARTITION BY toDate(event_time) ORDER BY (city, category) UNIQUE KEY product_id; INSERT INTO t1 VALUES ('2020-10-29 23:40:00', 10001, 'Beijing', '男装', 5, 500), ('2020-10-29 23:40:00', 10002, 'Beijing', '男装', 2, 200), ('2020-10-29 23:40:00', 10003, 'Beijing', '男装', 1, 100); -- 写入相同 key 的数据可以实现更新(upsert语义) INSERT INTO t1 VALUES ('2020-10-29 23:50:00', 10002, 'Beijing', '男装', 4, 400), ('2020-10-29 23:50:00', 10003, 'Beijing', '男装', 2, 200), ('2020-10-29 23:50:00', 10004, 'Beijing', '男装', 1, 100), ('2020-10-30 00:00:05', 10001, 'Beijing', '男装', 1, 100), ('2020-10-30 00:00:05', 10002, 'Beijing', '男装', 2, 200); -- 查询自动返回每个key最新的数据 select * from t1 order by toDate(event_time), product_id; ┌──────────event_time─┬─product_id─┬─city────┬─category─┬─amount─┬─revenue─┐ │ 2020-10-29 23:40:00 │ 10001 │ Beijing │ 男装 │ 5 │ 500 │ │ 2020-10-29 23:50:00 │ 10002 │ Beijing │ 男装 │ 4 │ 400 │ │ 2020-10-29 23:50:00 │ 10003 │ Beijing │ 男装 │ 2 │ 200 │ │ 2020-10-29 23:50:00 │ 10004 │ Beijing │ 男装 │ 1 │ 100 │ │ 2020-10-30 00:00:05 │ 10001 │ Beijing │ 男装 │ 1 │ 100 │ │ 2020-10-30 00:00:05 │ 10002 │ Beijing │ 男装 │ 2 │ 200 │ └─────────────────────┴────────────┴─────────┴──────────┴────────┴─────────┘
当唯一键表单分区数据量达到一定量级,会影响写入效率。此时可以使用 cluster by 将分区级的数据量打散到不同的分桶,当 cluster by 所需的列全部包含在 unique key 中时,可以达到分区级唯一的效果,同时享受 bucket 唯一优化。
CREATE TABLE t2 ( `event_time` DateTime, `product_id` UInt64, `city` String, `category` String, `amount` UInt32, `revenue` UInt64 ) ENGINE = CnchMergeTree PARTITION BY toDate(event_time) ORDER BY (city, category) CLUSTER BY product_id INTO 10 BUCKETS UNIQUE KEY product_id; INSERT INTO t2 VALUES ('2020-10-29 23:40:00', 10001, 'Beijing', '男装', 5, 500), ('2020-10-29 23:40:00', 10002, 'Beijing', '男装', 2, 200), ('2020-10-29 23:40:00', 10003, 'Beijing', '男装', 1, 100); -- 写入相同 key 的数据可以实现更新(upsert语义) INSERT INTO t2 VALUES ('2020-10-29 23:50:00', 10002, 'Beijing', '男装', 4, 400), ('2020-10-29 23:50:00', 10003, 'Beijing', '男装', 2, 200), ('2020-10-29 23:50:00', 10004, 'Beijing', '男装', 1, 100), ('2020-10-30 00:00:05', 10001, 'Beijing', '男装', 1, 100), ('2020-10-30 00:00:05', 10002, 'Beijing', '男装', 2, 200); -- 查询自动返回每个key最新的数据 select * from t2 order by toDate(event_time), product_id; ┌──────────event_time─┬─product_id─┬─city────┬─category─┬─amount─┬─revenue─┐ │ 2020-10-29 23:40:00 │ 10001 │ Beijing │ 男装 │ 5 │ 500 │ │ 2020-10-29 23:50:00 │ 10002 │ Beijing │ 男装 │ 4 │ 400 │ │ 2020-10-29 23:50:00 │ 10003 │ Beijing │ 男装 │ 2 │ 200 │ │ 2020-10-29 23:50:00 │ 10004 │ Beijing │ 男装 │ 1 │ 100 │ │ 2020-10-30 00:00:05 │ 10001 │ Beijing │ 男装 │ 1 │ 100 │ │ 2020-10-30 00:00:05 │ 10002 │ Beijing │ 男装 │ 2 │ 200 │ └─────────────────────┴────────────┴─────────┴──────────┴────────┴─────────┘