The unique key table in ByteHouse is primarily used to implement the upsert functionality. This capability is a proprietary feature developed in-house by the ByteHouse team that both maintains high query performance and supports unique key updates. The unique key table in ByteHouse mainly solves the pain point that open-source ClickHouse cannot support efficient update operations, helping businesses develop real-time analytics applications more easily. You can implement upsert semantics by specifying a unique key (UNIQUE KEY), and queries will automatically return the latest value for each unique key.
A unique key table is a CnchMergeTree table that uses a unique key index (UNIQUE KEY) and has the following characteristics:
For more information about the capabilities, functional logic, usage restrictions, and other details of unique key tables, please see MySQL Foreign Table.
It is recommended that the number of UNIQUE KEY fields does not exceed five. If a unique key is formed by a combination of multiple fields, you can use their toString(sipHash128()) hash as the unique key.
If deduplication is required, it is recommended to prioritize using partition-level deduplication. Then, based on data volume and write performance requirements (RPS, record per second), determine whether bucket deduplication optimization needs to be enabled.
When any of the following situations occurs, it is recommended to enable bucket deduplication optimization:
You can enable bucket deduplication optimization by setting the columns required by the CLUSTER BY clause to be a subset of those required by the UNIQUE KEY when creating a table.
When the deduplication granularity data volume (before splitting buckets) is m and the required peak RPS is n. Determine the number of buckets using max{⌈m / 20M⌉, ⌈n / 20K⌉}. If you need to reserve some margin for data volume or RPS, use 2 * max{⌈m /20M⌉, ⌈n / 20K⌉} as the number of buckets.
During a query, parts with the same number of buckets are sent to the same worker node for computation. Therefore, selecting an appropriate number of buckets has a significant impact on queries. The following principles are generally observed:
After meeting the requirements on the write side, set write_bucket_num to 2 * max{⌈m / 20M⌉, ⌈n / 20K⌉}. The number of buckets can be set based on write_bucket_num and the number of workers:
write_bucket_num is less than or equal to the number of workers, you can use the number of workers directly. If you consider long-term horizontal scaling, you can use number of workers * 2 as the final bucket count.write_bucket_num is greater than the number of workers, round up to the nearest integer multiple of the number of workers to determine the final bucket count.CLUSTER BY EXPRESSION xxHash64V2(cluster_column) % N INTO N BUCKETS clause. For specific usage instructions, refer to RENAME Statement.CLUSTER BY columns; otherwise, bucket data skew can easily occur, which can affect write and query performance.It is recommended to use the following SETTINGS parameters:
max_dedup_worker_number = [cnch_vw_write 的 pod 数], pick_dedup_worker_by_mod = 1, dedup_impl_version = 'dedup_in_txn_commit'
A complete example of a table creation command is as follows:
CREATE TABLE orders ( `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 EXPRESSION xxHash64V2(product_id) % 6 INTO 6 BUCKETS UNIQUE KEY product_id SETTINGS [..., ]max_dedup_worker_number = [cnch_vw_write 的 pod 数], pick_dedup_worker_by_mod = 1, dedup_impl_version = 'dedup_in_txn_commit';
Parameter name | Parameter types | Optional values | No default value configured | Parameter descriptions |
|---|---|---|---|---|
max_dedup_worker_number | UInt64 | Integer | 1 | It is recommended that the number of Dedup workers matches the number of pods in the compute group configured by cnch_vw_write. |
pick_dedup_worker_by_mod | Bool | true/false | false | Dedup worker distribution mode: When the configuration is set to true, the modulo operation based on the total number of nodes in the compute group ensures even distribution. By default, when set to false, distribution is based on hash values and may not be even. |
dedup_impl_version | DedupImplVersion | dedup_in_write_suffix/ | dedup_in_write_suffix | Deduplication implementation: dedup_in_write_suffix executes deduplication logic at the end of the write phase; dedup_in_txn_commit executes deduplication logic during the transaction pre-commit phase, which is an optimized version. |
ByteHouse is designed to handle large volumes of data, so it is recommended to prioritize batch aggregation upstream whenever possible.
warning
Non-deduplication mode is typically used during historical data migration. In formal production environments, use with caution, as it may introduce duplicate data issues.
When the data you need to import meets the following two criteria, you can enable import without deduplication at the query level:
Use the following parameters to enable non-deduplicated import at the query level:
dedup_key_mode='append', enable_unique_partial_update = 0
The complete usage example is as follows:
INSERT INTO target_table SELECT * FROM source_table SETTINGS dedup_key_mode='append', enable_unique_partial_update = 0;
Parameter description
Parameter name | Parameter types | Optional values | No default value configured | Parameter descriptions |
|---|---|---|---|---|
dedup_key_mode | DedupKeyMode | replace/throw/append/ignore | replace | Set the deduplication mode. Different parameters correspond to different deduplication modes:
|
enable_unique_partial_update | Bool | true/false | true | Whether to use partial column update mode. Must be used together with a table-level parameter of the same name; the partial column update mode is enabled if and only if both this parameter and the table-level parameter are enabled. |
If concurrent writes occur during data import, you may first consider aligning writes at the upper layer according to deduplication granularity. If achieving the previous method is difficult, you can enable concurrent write optimization when deduplication becomes a bottleneck. ByteHouse offers better large-scale deduplication performance by configuring table-level parameters to enable batch deduplication using background threads.
Table-level parameter settings are as follows:
cloud_enable_staging_area=1, cloud_staging_area_wait_mode='wait_txn_visible'
A complete example of a table creation command is as follows:
CREATE TABLE orders ( `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 EXPRESSION xxHash64V2(product_id) % 6 INTO 6 BUCKETS UNIQUE KEY product_id SETTINGS [..., ]cloud_enable_staging_area=1, cloud_staging_area_wait_mode='wait_txn_visible';
Parameter descriptions
Parameter name | Parameter types | Optional values | No default value configured | Parameter description |
|---|---|---|---|---|
cloud_enable_staging_area | Bool | true/false | false | Whether to enable the staging area can support optimization scenarios for asynchronous or concurrent writes. |
cloud_staging_area_wait_mode | StagingAreaWaitMode | no_wait/wait_txn_visible | no_wait | After enabling the staging area, the data write mode is enabled. no_wait indicates the asynchronous write mode, wait_txn_visible indicates the synchronous write mode. |
When a single INSERT SELECT/DELETE/UPDATE operation affects more than 100 million rows, this task can be considered a large ELT task. ByteHouse supports making full use of multiple worker resources and centralized scheduling of deduplication tasks by setting table-level and query-level parameters. An example of parameter settings is as follows:
# 表级参数设置 dedup_impl_version = 'dedup_in_txn_commit' # query 级别参数设置 optimize_unique_table_write = 1, max_insert_threads = 8
The complete usage example is as follows:
Table-level parameter settings
CREATE TABLE orders ( `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 EXPRESSION xxHash64V2(product_id) % 6 INTO 6 BUCKETS UNIQUE KEY product_id SETTINGS [..., ]dedup_impl_version = 'dedup_in_txn_commit';
Query-level parameter settings
INSERT INTO target_table SELECT * FROM source_table SETTINGS optimize_unique_table_write = 1, max_insert_threads = 8;
warning
min_insert_block_size_bytes and min_insert_block_size_rows at the same time; you can set them to twice their current values.min_insert_block_size_bytes and min_insert_block_size_rows; they can be set to half of their current values.Parameter descriptions
Parameter name | Parameter types | Optional values | No default value configured | Parameter descriptions |
|---|---|---|---|---|
dedup_impl_version | DedupImplVersion | dedup_in_write_suffix/ | dedup_in_write_suffix | Deduplication implementation: dedup_in_write_suffix executes the deduplication logic at the end of the write phase, while dedup_in_txn_commit executes the deduplication logic during the transaction pre-commit phase and is the optimized version. |
optimize_unique_table_write | Bool | true/false | false | It is recommended to enable insert select import optimization for unique key tables only when the staging area is not enabled and dedup_impl_version='dedup_in_txn_commit'. |
max_insert_threads | UInt64 | Positive integers | 0 | Size of the worker's write thread pool for executing insert select requests |
min_insert_block_size_bytes | UInt64 | Positive integers | 1073710080 |
|
min_insert_block_size_rows | UInt64 | Positive integers | 1048545 | The minimum number of rows for a |
warning
This feature is currently being gradually rolled out. If you encounter any issues while using it, you can submit a support ticket or contact the ByteHouse team.
When online and offline import tasks coexist, large-volume offline tasks can severely impact the write latency of real-time imports, resulting in significant performance fluctuations for the latter. In this scenario, you can enable optimization to resolve the issue. By configuring table-level parameters and relying on the staging area, deduplication bandwidth can be preferentially allocated to real-time tasks, thereby reducing the impact of large offline ELT tasks on real-time write latency.
Example settings for table-level parameters are as follows:
cloud_enable_staging_area=1, cloud_staging_area_wait_mode='wait_txn_visible',real_time_task_optimize_algo='strict_optimize'
The following is a complete example of a table creation command:
CREATE TABLE orders ( `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 EXPRESSION xxHash64V2(product_id) % 6 INTO 6 BUCKETS UNIQUE KEY product_id SETTINGS [..., ]cloud_enable_staging_area=1, cloud_staging_area_wait_mode='wait_txn_visible',real_time_task_optimize_algo='strict_optimize';
Parameter description
Parameter name | Parameter types | Optional values | No default configuration values | Parameter descriptions |
|---|---|---|---|---|
cloud_enable_staging_area | Bool | true/false | false | Enabling the staging area can support scenarios optimized for asynchronous or concurrent writes. |
cloud_staging_area_wait_mode | StagingAreaWaitMode | no_wait/wait_txn_visible | no_wait | After enabling the staging area, data write mode. no_wait indicates the asynchronous write mode, and wait_txn_visible indicates the synchronous write mode. |
real_time_task_optimize_algo | RealTimeTaskOptimizeAlgo | disabled/strict_optimize/ loose_optimize | disabled | Configure offline/online import optimization algorithms. The effects for different parameter values are as follows:
|
-- By default, the engine ensures the uniqueness of the unique key within a partition. -- Note: UNIQUE KEY does not support nullable columns 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); -- Writing data with the same key enables updates (upsert semantics) 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); -- The query automatically returns the latest data for each 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 │ └─────────────────────┴────────────┴─────────┴──────────┴────────┴─────────┘
-- UNIQUE KEY can include multiple columns and expressions -- sipHash64 is a fast hash function with a low collision rate; when using sipHash64 as a unique key, it is necessary to consider the possibility of hash collisions. -- sipHash64:https://clickhouse.com/docs/en/sql-reference/functions/hash-functions#siphash64 CREATE TABLE t1m ( `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, sipHash64(city)); INSERT INTO t1m 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), ('2020-10-29 23:50:00', 10002, 'Shanghai', '男装', 4, 400), ('2020-10-29 23:50:00', 10003, 'Beijing', '男装', 2, 200), ('2020-10-29 23:50:00', 10004, 'Beijing', '男装', 1, 100); select * from t1m; ┌──────────event_time─┬─product_id─┬─city─────┬─category─┬─amount─┬─revenue─┐ │ 2020-10-29 23:40:00 │ 10001 │ Beijing │ 男装 │ 5 │ 500 │ │ 2020-10-29 23:40:00 │ 10002 │ Beijing │ 男装 │ 2 │ 200 │ │ 2020-10-29 23:50:00 │ 10003 │ Beijing │ 男装 │ 2 │ 200 │ │ 2020-10-29 23:50:00 │ 10004 │ Beijing │ 男装 │ 1 │ 100 │ │ 2020-10-29 23:50:00 │ 10002 │ Shanghai │ 男装 │ 4 │ 400 │ └─────────────────────┴────────────┴──────────┴──────────┴────────┴─────────┘
tip
The following restrictions apply when using the version field:
By default, data written with the same unique key will overwrite the existing data. This may lead to the following issues
To address the issue mentioned above, ByteHouse unique key tables support designating a field in the table as the version field. The engine ensures that when writing data with the same key, overwriting occurs only if the data version is greater than or equal to the existing version. The version field supports all UInt types, as well as Data and DateTime, and cannot be nullable.
-- The parameters in parentheses for CnchMergeTree are optional version fields CREATE TABLE t3 ( `event_time` DateTime, `product_id` UInt64, `city` String, `category` String, `amount` UInt32, `revenue` UInt64 ) ENGINE = CnchMergeTree(event_time) PARTITION BY toDate(event_time) ORDER BY (city, category) UNIQUE KEY product_id; INSERT INTO t3 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:50:00', 10001, 'Beijing', '男装', 8, 800), ('2020-10-29 23:50:00', 10002, 'Beijing', '男装', 5, 500); -- When backtracking the previous two records, if the version is less than the existing version, the records are automatically skipped during the write process. INSERT INTO t3 VALUES ('2020-10-29 23:40:00', 10001, 'Beijing', '男装', 5, 500), ('2020-10-29 23:40:00', 10002, 'Beijing', '男装', 2, 200); -- Rollback is not available for versions 10001 and 10002 select * from t3 order by toDate(event_time), product_id; ┌──────────event_time─┬─product_id─┬─city────┬─category─┬─amount─┬─revenue─┐ │ 2020-10-29 23:50:00 │ 10001 │ Beijing │ 男装 │ 8 │ 800 │ │ 2020-10-29 23:50:00 │ 10002 │ Beijing │ 男装 │ 5 │ 500 │ └─────────────────────┴────────────┴─────────┴──────────┴────────┴─────────┘ -- Continue to trace back the last two data entries, and write two data entries of the new version INSERT INTO t3 VALUES ('2020-10-29 23:50:00', 10001, 'Beijing', '男装', 8, 800), ('2020-10-29 23:50:00', 10002, 'Beijing', '男装', 5, 500), ('2020-10-29 23:55:00', 10001, 'Beijing', '男装', 10, 1000), ('2020-10-29 23:55:00', 10002, 'Beijing', '男装', 7, 700); -- Query automatically returns the latest version's data select * from t3 order by toDate(event_time), product_id; ┌──────────event_time─┬─product_id─┬─city────┬─category─┬─amount─┬─revenue─┐ │ 2020-10-29 23:55:00 │ 10001 │ Beijing │ 男装 │ 10 │ 1000 │ │ 2020-10-29 23:55:00 │ 10002 │ Beijing │ 男装 │ 7 │ 700 │ └─────────────────────┴────────────┴─────────┴──────────┴────────┴─────────┘
In certain application scenarios, users want to add a field when performing an INSERT operation to indicate the deletion status of a record, thereby extending the semantics of INSERT.
In unique key tables in ByteHouse, each table includes a reserved field_delete_flag_ of type UInt8, where 0 indicates data being written and any nonzero value indicates data deletion. This field cannot be specified in CREATE TABLE statements or queried. It can only be specified during INSERT operations, including both INSERT and INSERT SELECT. In addition, the ByteHouse unique key tables support the DELETE FROM clause by using the reserved field_delete_flag_.
Usage examples are as follows:
-- By default, the engine ensures the uniqueness of a unique key within a partition. -- Note: UNIQUE KEY does not support Nullable CREATE TABLE t5 ( `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 t5 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); Specify the deletion field to perform data deletion. Setting the deletion field to a nonzero value indicates deletion, while setting it to zero indicates a normal upsert operation. INSERT INTO t5 (event_time, product_id, city, category, amount, revenue, _delete_flag_) VALUES ('2020-10-29 23:50:00', 10001, 'Beijing', '男装', 4, 400, 5), ('2020-10-29 23:50:00', 10002, 'Beijing', '男装', 2, 200, 1), ('2020-10-29 23:50:00', 10004, 'Beijing', '男装', 1, 100, 0); The query result includes a newly added row of data and no longer contains two old rows of data. select * from t5 order by toDate(event_time), product_id; ┌──────────event_time─┬─product_id─┬─city────┬─category─┬─amount─┬─revenue─┐ │ 2020-10-29 23:40:00 │ 10003 │ Beijing │ 男装 │ 1 │ 100 │ │ 2020-10-29 23:50:00 │ 10004 │ Beijing │ 男装 │ 1 │ 100 │ └─────────────────────┴────────────┴─────────┴──────────┴────────┴─────────┘
-- By default, the engine ensures the uniqueness of the unique key within a partition. Note: UNIQUE KEY does not support Nullable. -- Specify version number CREATE TABLE t5m ( `event_time` DateTime, `product_id` UInt64, `city` String, `category` String, `amount` UInt32, `revenue` UInt64, `version` UInt64 ) ENGINE = CnchMergeTree(version) PARTITION BY toDate(event_time) ORDER BY (city, category) UNIQUE KEY product_id; INSERT INTO t5m VALUES ('2020-10-29 23:40:00', 10001, 'Beijing', '男装', 5, 500, 10), ('2020-10-29 23:40:00', 10002, 'Beijing', '男装', 2, 200, 10), ('2020-10-29 23:40:00', 10003, 'Beijing', '男装', 1, 100, 10); If you specify a field to delete and a version number, and the version number is less than the version number of the corresponding row in the query result, the delete operation will not take effect. INSERT INTO t5m (event_time, product_id, city, category, amount, revenue, version, _delete_flag_) VALUES ('2020-10-29 23:40:00', 10001, 'Beijing', '男装', 4, 400, 5, 1), ('2020-10-29 23:40:00', 10002, 'Beijing', '男装', 2, 200, 5, 1), ('2020-10-29 23:40:00', 10003, 'Beijing', '男装', 1, 100, 5, 1); -- The query results remain unchanged; no data has been deleted select * from t5m order by toDate(event_time), product_id; ┌──────────event_time─┬─product_id─┬─city────┬─category─┬─amount─┬─revenue─┬─version─┐ │ 2020-10-29 23:40:00 │ 10001 │ Beijing │ 男装 │ 5 │ 500 │ 10 │ │ 2020-10-29 23:40:00 │ 10002 │ Beijing │ 男装 │ 2 │ 200 │ 10 │ │ 2020-10-29 23:40:00 │ 10003 │ Beijing │ 男装 │ 1 │ 100 │ 10 │ └─────────────────────┴────────────┴─────────┴──────────┴────────┴─────────┴─────────┘ -- Specify the deletion field to perform data deletion. If the version number is not specified or is set to 0, the deletion operation will skip version checking and execute directly. INSERT INTO t5m (event_time, product_id, city, category, amount, revenue, _delete_flag_) VALUES ('2020-10-29 23:40:00', 10001, 'Beijing', '男装', 4, 400, 1); INSERT INTO t5m (event_time, product_id, city, category, amount, revenue, version, _delete_flag_) VALUES ('2020-10-29 23:40:00', 10003, 'Beijing', '男装', 1, 100, 0, 1); -- The query deleted two rows of old data select * from t5m order by toDate(event_time), product_id; ┌──────────event_time─┬─product_id─┬─city────┬─category─┬─amount─┬─revenue─┬─version─┐ │ 2020-10-29 23:40:00 │ 10002 │ Beijing │ 男装 │ 2 │ 200 │ 10 │ └─────────────────────┴────────────┴─────────┴──────────┴────────┴─────────┴─────────┘
-- By default, the engine ensures the uniqueness of the unique key within a partition. -- Note: UNIQUE KEY does not support Nullable CREATE TABLE t5x ( `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 t5x 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); -- Delete data where revenue >= 200 by using INSERT SELECT INSERT INTO t5x (event_time, product_id, city, category, amount, revenue, _delete_flag_) SELECT *, 1 as _delete_flag_ from t5x where revenue >= 200; -- Data with revenue >= 200 has been deleted from the query results select * from t5x order by toDate(event_time), product_id; ┌──────────event_time─┬─product_id─┬─city────┬─category─┬─amount─┬─revenue─┐ │ 2020-10-29 23:40:00 │ 10003 │ Beijing │ 男装 │ 1 │ 100 │ └─────────────────────┴────────────┴─────────┴──────────┴────────┴─────────┘
-- By default, the engine ensures unique keys are unique within a partition -- Note: UNIQUE KEY does not support Nullable CREATE TABLE t5y ( `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 t5y 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); -- Use DELETE FROM to delete data where revenue equals 500 DELETE FROM t5y WHERE revenue = 500; -- Delete data where revenue is greater than or equal to 200 using DELETE FROM. DELETE FROM t5y WHERE revenue >= 200; -- Data with revenue greater than or equal to 200 has been deleted from the query results select * from t5y order by toDate(event_time), product_id; ┌──────────event_time─┬─product_id─┬─city────┬─category─┬─amount─┬─revenue─┐ │ 2020-10-29 23:40:00 │ 10003 │ Beijing │ 男装 │ 1 │ 100 │ └─────────────────────┴────────────┴─────────┴──────────┴────────┴─────────┘
-- By default, the engine ensures the uniqueness of the unique key within a partition Note: UNIQUE KEY does not support nullable columns. CREATE TABLE t6 ( `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 t6 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); -- Update using the UPDATE statement UPDATE t6 SET amount = 10, revenue = 1000 WHERE event_time = '2020-10-29 23:40:00' and product_id=10001; -- 10,001 rows in the query result were updated select * from t6 order by toDate(event_time), product_id; ┌──────────event_time─┬─product_id─┬─city────┬─category─┬─amount─┬─revenue─┐ │ 2020-10-29 23:40:00 │ 10001 │ Beijing │ 男装 │ 10 │ 1000 │ │ 2020-10-29 23:40:00 │ 10002 │ Beijing │ 男装 │ 2 │ 200 │ │ 2020-10-29 23:40:00 │ 10003 │ Beijing │ 男装 │ 1 │ 100 │ └─────────────────────┴────────────┴─────────┴──────────┴────────┴─────────┘ Update using the UPDATE statement and restrict the scope with limit. UPDATE t6 SET category = concat('新',category) WHERE event_time = '2020-10-29 23:40:00' order by product_id limit 1; -- Only 10,001 data rows in the query result were updated select * from t6 order by toDate(event_time), product_id; ┌──────────event_time─┬─product_id─┬─city────┬─category─┬─amount─┬─revenue─┐ │ 2020-10-29 23:40:00 │ 10001 │ Beijing │ 新男装 │ 10 │ 1000 │ │ 2020-10-29 23:40:00 │ 10002 │ Beijing │ 男装 │ 2 │ 200 │ │ 2020-10-29 23:40:00 │ 10003 │ Beijing │ 男装 │ 1 │ 100 │ └─────────────────────┴────────────┴─────────┴──────────┴────────┴─────────┘ -- Swap columns using the UPDATE statement UPDATE t6 SET amount=revenue, revenue=amount WHERE event_time = '2020-10-29 23:40:00'; -- The amount and revenue columns in the query result have been swapped select * from t6 order by toDate(event_time), product_id; ┌──────────event_time─┬─product_id─┬─city────┬─category─┬─amount─┬─revenue─┐ │ 2020-10-29 23:40:00 │ 10001 │ Beijing │ 新男装 │ 1000 │ 10 │ │ 2020-10-29 23:40:00 │ 10002 │ Beijing │ 男装 │ 200 │ 2 │ │ 2020-10-29 23:40:00 │ 10003 │ Beijing │ 男装 │ 100 │ 1 │ └─────────────────────┴────────────┴─────────┴──────────┴────────┴─────────┘
A Bucket table is a performance optimization option available when creating tables in ByteHouse. When a Bucket table is used in ByteHouse, the system organizes table data based on one or more columns or expressions specified in the table creation statement, clustering rows with the same value under the same bucket number. This approach enables better performance during query processing.
Not unique at the bucket level
When enable_bucket_level_unique_keys is not used to specify bucket-level uniqueness. ByteHouse unique key tables enforce uniqueness within the scope specified by the table engine. By default, uniqueness is enforced at the partition level; at this time, different buckets within the same partition do not contain duplicate data.
-- By default, the engine ensures the uniqueness of the unique key within a partition -- Note: UNIQUE KEY does not support nullable columns CREATE TABLE t7 ( `d` Date, `id` Int32, `s` String ) ENGINE = CnchMergeTree PARTITION BY d CLUSTER BY s INTO 10 BUCKETS ORDER BY s UNIQUE KEY id; INSERT INTO t7 VALUES ('2023-06-26', 1, '1a'), ('2023-06-26', 2, '2a'), ('2023-06-26', 3, '3a'), ('2023-06-26', 3, '3b'), ('2023-06-26', 3, '3c'); select * from t7 order by id; ┌──────────d─┬─id─┬─s──┐ │ 2023-06-26 │ 1 │ 1a │ │ 2023-06-26 │ 2 │ 2a │ │ 2023-06-26 │ 3 │ 3c │ └────────────┴────┴────┘
Unique at the bucket level
When all columns required by the cluster by clause are included in the unique key fields, data with the same unique key will always fall into the same bucket, enabling bucket-level deduplication. At this point, partition_level_unique_keys is used to specify the scope of deduplication for the bucket (partition-level or table-level).
The bucket feature is the only way to reduce the amount of data subject to deduplication, thereby improving write RPS and P99 latency; enable_bucket_level_unique_keys = 1 allows skipping part-level bucket validation, further enhancing write efficiency;
-- By default, the engine ensures the uniqueness of the unique key within a partition Note: UNIQUE KEY does not support Nullable. CREATE TABLE t7m ( `d` Date, `id` Int32, `s` String ) ENGINE = CnchMergeTree PARTITION BY d CLUSTER BY id INTO 10 BUCKETS ORDER BY s UNIQUE KEY id SETTINGS enable_bucket_level_unique_keys = 1; INSERT INTO t7m VALUES ('2023-06-26', 1, '1a'), ('2023-06-26', 2, '2a'), ('2023-06-26', 3, '3a'), ('2023-06-26', 3, '3b'), ('2023-06-26', 3, '3c'); select * from t7m order by id; ┌──────────d─┬─id─┬─s──┐ │ 2023-06-26 │ 1 │ 1a │ │ 2023-06-26 │ 2 │ 2a │ │ 2023-06-26 │ 3 │ 3c │ └────────────┴────┴────┘ INSERT INTO t7m VALUES ('2023-06-26', 3, '3d'); select * from t7m order by id; ┌──────────d─┬─id─┬─s──┐ │ 2023-06-26 │ 1 │ 1a │ │ 2023-06-26 │ 2 │ 2a │ │ 2023-06-26 │ 3 │ 3d │ └────────────┴────┴────┘
The ByteHouse Cloud Data Warehouse Edition supports partial column update mode.
In row update mode, columns not specified are filled with default values. In column update mode, if a default column has an existing value, it will be retained; otherwise, the default value will be used. For details, see Basic Data Types.
The unique key table in ByteHouse is designed to be compatible with MySQL and supports its different write modes.
MySQL DML semantics | MySQL | ByteHouse unique key tables |
|---|---|---|
DDL examples |
|
|
insert into |
|
|
insert ignore |
┌─a─┬─b─┐ |
┌─a─┬─b─┐ |
replace into |
┌─a─┬─b─┐
Query OK, 2 rows affected (0.11 sec)
┌─a─┬─b─┐ |
┌─a─┬─b─┐
┌─a─┬─b─┐ |