导入数据时,Doris将表唯一值分配给自动递增列中没有指定值的行。
1、功能
对于包含自动递增列的表,在数据导入期间:
- 如果目标列不包括自动递增列,Doris将用生成的值填充自动递增列。
- 如果目标列包括自动递增列,则为该列导入的数据中的空值将被Doris生成的值替换,而非空值将保持不变。注意,非空值可能会破坏自动增量列值的唯一性。
1.1 唯一性
Doris确保在自动递增列上生成的值具有表范围内的唯一性。但是,需要注意的是,自动递增列的唯一性只保证由Doris自动填充的值的唯一性,而不考虑用户提供的值。如果用户通过指定自动递增列显式地为该表插入用户提供的值,则无法保证这种唯一性。
1.2 聚集性
Doris确保自动递增列上生成的值是密集的,但它不能保证导入期间自动递增列中自动生成的值是完全连续的。因此,在导入期间,自动增量列生成的值可能会出现一些跳跃。这是因为出于性能考虑,每个BE都会缓存一部分预分配的自动增量列值,并且这些缓存的值在不同的BE之间不会相交。此外,由于这种缓存机制,Doris不能保证以后在物理时间轴上导入的自动增量列上自动生成的值比以前导入的值大。因此,自动递增列分配的值不能用于确定导入的时间顺序。
2、语法
要使用自动递增列,需要在表创建期间(CREATE-TABLE)将AUTO_INCREMENT
属性添加到相应的列。要手动指定自动递增列的起始值,可以在创建表时使用AUTO_INCREMENT(start_value)
语句。如果不指定,则默认起始值为1。
2.1 Examples
- 创建一个 Duplicate 表,其中一个键列作为自动递增列:
sql
CREATE TABLE `demo`.`tbl` (
`id` BIGINT NOT NULL AUTO_INCREMENT,
`value` BIGINT NOT NULL
) ENGINE=OLAP
DUPLICATE KEY(`id`)
DISTRIBUTED BY HASH(`id`) BUCKETS 10
PROPERTIES (
"replication_allocation" = "tag.location.default: 3"
);
- 创建一个 Duplicate 表,其中一个键列作为自动递增列,并设置起始值为100:
sql
CREATE TABLE `demo`.`tbl` (
`id` BIGINT NOT NULL AUTO_INCREMENT(100),
`value` BIGINT NOT NULL
) ENGINE=OLAP
DUPLICATE KEY(`id`)
DISTRIBUTED BY HASH(`id`) BUCKETS 10
PROPERTIES (
"replication_allocation" = "tag.location.default: 3"
);
3.创建一个 Duplicate 表,其中一个值列作为自动递增列:
sql
CREATE TABLE `demo`.`tbl` (
`uid` BIGINT NOT NULL,
`name` BIGINT NOT NULL,
`id` BIGINT NOT NULL AUTO_INCREMENT,
`value` BIGINT NOT NULL
) ENGINE=OLAP
DUPLICATE KEY(`uid`, `name`)
DISTRIBUTED BY HASH(`uid`) BUCKETS 10
PROPERTIES (
"replication_allocation" = "tag.location.default: 3"
);
- 创建一个Unique 表,其中一个键列作为自增列:
sql
CREATE TABLE `demo`.`tbl` (
`id` BIGINT NOT NULL AUTO_INCREMENT,
`name` varchar(65533) NOT NULL,
`value` int(11) NOT NULL
) ENGINE=OLAP
UNIQUE KEY(`id`)
DISTRIBUTED BY HASH(`id`) BUCKETS 10
PROPERTIES (
"replication_allocation" = "tag.location.default: 3",
"enable_unique_key_merge_on_write" = "true"
);
- 创建一个Unique表,其中一个值列作为自动增量列:
sql
CREATE TABLE `demo`.`tbl` (
`text` varchar(65533) NOT NULL,
`id` BIGINT NOT NULL AUTO_INCREMENT,
) ENGINE=OLAP
UNIQUE KEY(`text`)
DISTRIBUTED BY HASH(`text`) BUCKETS 10
PROPERTIES (
"replication_allocation" = "tag.location.default: 3",
"enable_unique_key_merge_on_write" = "true"
);
2.2 制约和限制
- 只有Duplicate 模型表和Unique 模型表可以包含自动递增列。
- 一个表最多只能包含一个自动递增列。
- 自动递增列的类型必须为BIGINT 并且必须为
NOT NULL
。 - 手动指定的自动递增列的起始值必须大于或等于0。
3、用法
3.1 导入
考虑以下表:
sql
CREATE TABLE `demo`.`tbl` (
`id` BIGINT NOT NULL AUTO_INCREMENT,
`name` varchar(65533) NOT NULL,
`value` int(11) NOT NULL
) ENGINE=OLAP
UNIQUE KEY(`id`)
DISTRIBUTED BY HASH(`id`) BUCKETS 10
PROPERTIES (
"replication_allocation" = "tag.location.default: 3",
"enable_unique_key_merge_on_write" = "true"
);
当使用insert into语句导入数据而没有指定自动递增的列id
时,id
列将自动被生成的值填充。
sql
mysql> insert into tbl(name, value) values("Bob", 10), ("Alice", 20), ("Jack", 30);
Query OK, 3 rows affected (0.09 sec)
{'label':'label_183babcb84ad4023_a2d6266ab73fb5aa', 'status':'VISIBLE', 'txnId':'7'}
mysql> select * from tbl order by id;
+------+-------+-------+
| id | name | value |
+------+-------+-------+
| 1 | Bob | 10 |
| 2 | Alice | 20 |
| 3 | Jack | 30 |
+------+-------+-------+
3 rows in set (0.05 sec)
类似地,使用流加载导入文件test.csv
而不指定自动递增列id
将导致id
列被自动填充生成的值。
sql
Tom, 40
John, 50
bash
curl --location-trusted -u user:passwd -H "columns:name,value" -H "column_separator:," -T ./test1.csv http://{host}:{port}/api/{db}/tbl/_stream_load
bash
mysql> select * from tbl order by id;
+------+-------+-------+
| id | name | value |
+------+-------+-------+
| 1 | Bob | 10 |
| 2 | Alice | 20 |
| 3 | Jack | 30 |
| 4 | Tom | 40 |
| 5 | John | 50 |
+------+-------+-------+
5 rows in set (0.04 sec)
在指定自动递增列id
的同时使用insert into语句导入时,为该列导入的数据中的空值将被生成的值替换。
sql
mysql> insert into tbl(id, name, value) values(null, "Doris", 60), (null, "Nereids", 70);
Query OK, 2 rows affected (0.07 sec)
{'label':'label_9cb0c01db1a0402c_a2b8b44c11ce4703', 'status':'VISIBLE', 'txnId':'10'}
mysql> select * from tbl order by id;
+------+---------+-------+
| id | name | value |
+------+---------+-------+
| 1 | Bob | 10 |
| 2 | Alice | 20 |
| 3 | Jack | 30 |
| 4 | Tom | 40 |
| 5 | John | 50 |
| 6 | Doris | 60 |
| 7 | Nereids | 70 |
+------+---------+-------+
7 rows in set (0.04 sec)
3.2 部分更新
当对包含自动递增列的merge-on-write
Unique表执行部分更新时:
如果自动递增列是键列
,则在部分更新期间,由于用户必须显式指定键列,因此部分列更新的目标列必须包括自动递增列。在这个场景中,导入行为类似于常规的部分更新。
bash
mysql> CREATE TABLE `demo`.`tbl2` (
-> `id` BIGINT NOT NULL AUTO_INCREMENT,
-> `name` varchar(65533) NOT NULL,
-> `value` int(11) NOT NULL DEFAULT "0"
-> ) ENGINE=OLAP
-> UNIQUE KEY(`id`)
-> DISTRIBUTED BY HASH(`id`) BUCKETS 10
-> PROPERTIES (
-> "replication_allocation" = "tag.location.default: 3",
-> "enable_unique_key_merge_on_write" = "true"
-> );
Query OK, 0 rows affected (0.03 sec)
mysql> insert into tbl2(id, name, value) values(1, "Bob", 10), (2, "Alice", 20), (3, "Jack", 30);
Query OK, 3 rows affected (0.14 sec)
{'label':'label_5538549c866240b6_bce75ef323ac22a0', 'status':'VISIBLE', 'txnId':'1004'}
mysql> select * from tbl2 order by id;
+------+-------+-------+
| id | name | value |
+------+-------+-------+
| 1 | Bob | 10 |
| 2 | Alice | 20 |
| 3 | Jack | 30 |
+------+-------+-------+
3 rows in set (0.08 sec)
mysql> set enable_unique_key_partial_update=true;
Query OK, 0 rows affected (0.01 sec)
mysql> set enable_insert_strict=false;
Query OK, 0 rows affected (0.00 sec)
mysql> insert into tbl2(id, name) values(1, "modified"), (4, "added");
Query OK, 2 rows affected (0.06 sec)
{'label':'label_3e68324cfd87457d_a6166cc0a878cfdc', 'status':'VISIBLE', 'txnId':'1005'}
mysql> select * from tbl2 order by id;
+------+----------+-------+
| id | name | value |
+------+----------+-------+
| 1 | modified | 10 |
| 2 | Alice | 20 |
| 3 | Jack | 30 |
| 4 | added | 0 |
+------+----------+-------+
4 rows in set (0.04 sec)
当自动递增列是非键列
,并且用户没有为自动递增列指定值时 ,将从表中的现有数据行填充该值。如果用户指定自动递增列,则该列的导入数据中的空值将被生成的值替换,而非空值将保持不变,然后这些数据将加载部分更新的语义。
bash
mysql> CREATE TABLE `demo`.`tbl3` (
-> `id` BIGINT NOT NULL,
-> `name` varchar(100) NOT NULL,
-> `score` BIGINT NOT NULL,
-> `aid` BIGINT NOT NULL AUTO_INCREMENT
-> ) ENGINE=OLAP
-> UNIQUE KEY(`id`)
-> DISTRIBUTED BY HASH(`id`) BUCKETS 1
-> PROPERTIES (
-> "replication_allocation" = "tag.location.default: 3",
-> "enable_unique_key_merge_on_write" = "true"
-> );
Query OK, 0 rows affected (0.16 sec)
mysql> insert into tbl3(id, name, score) values(1, "Doris", 100), (2, "Nereids", 200), (3, "Bob", 300);
Query OK, 3 rows affected (0.28 sec)
{'label':'label_c52b2c246e244dda_9b91ee5e27a31f9b', 'status':'VISIBLE', 'txnId':'2003'}
mysql> select * from tbl3 order by id;
+------+---------+-------+------+
| id | name | score | aid |
+------+---------+-------+------+
| 1 | Doris | 100 | 0 |
| 2 | Nereids | 200 | 1 |
| 3 | Bob | 300 | 2 |
+------+---------+-------+------+
3 rows in set (0.13 sec)
mysql> set enable_unique_key_partial_update=true;
Query OK, 0 rows affected (0.00 sec)
mysql> set enable_insert_strict=false;
Query OK, 0 rows affected (0.00 sec)
mysql> insert into tbl3(id, score) values(1, 999), (2, 888);
Query OK, 2 rows affected (0.07 sec)
{'label':'label_dfec927d7a4343ca_9f9ade581391de97', 'status':'VISIBLE', 'txnId':'2004'}
mysql> select * from tbl3 order by id;
+------+---------+-------+------+
| id | name | score | aid |
+------+---------+-------+------+
| 1 | Doris | 999 | 0 |
| 2 | Nereids | 888 | 1 |
| 3 | Bob | 300 | 2 |
+------+---------+-------+------+
3 rows in set (0.06 sec)
mysql> insert into tbl3(id, aid) values(1, 1000), (3, 500);
Query OK, 2 rows affected (0.07 sec)
{'label':'label_b26012959f714f60_abe23c87a06aa0bf', 'status':'VISIBLE', 'txnId':'2005'}
mysql> select * from tbl3 order by id;
+------+---------+-------+------+
| id | name | score | aid |
+------+---------+-------+------+
| 1 | Doris | 999 | 1000 |
| 2 | Nereids | 888 | 1 |
| 3 | Bob | 300 | 500 |
+------+---------+-------+------+
3 rows in set (0.06 sec)
4、使用场景
4.1 字典编码
在用户配置文件中使用位图进行受众分析需要构建一个用户字典,其中每个用户对应一个唯一的整数字典值。聚合这些字典值可以提高位图的性能。
以线下UV、PV分析场景为例,假设有详细的用户行为表:
sql
CREATE TABLE `demo`.`dwd_dup_tbl` (
`user_id` varchar(50) NOT NULL,
`dim1` varchar(50) NOT NULL,
`dim2` varchar(50) NOT NULL,
`dim3` varchar(50) NOT NULL,
`dim4` varchar(50) NOT NULL,
`dim5` varchar(50) NOT NULL,
`visit_time` DATE NOT NULL
) ENGINE=OLAP
DUPLICATE KEY(`user_id`)
DISTRIBUTED BY HASH(`user_id`) BUCKETS 32
PROPERTIES (
"replication_allocation" = "tag.location.default: 3"
);
使用auto-increment列创建如下字典表:
sql
CREATE TABLE `demo`.`dictionary_tbl` (
`user_id` varchar(50) NOT NULL,
`aid` BIGINT NOT NULL AUTO_INCREMENT
) ENGINE=OLAP
UNIQUE KEY(`user_id`)
DISTRIBUTED BY HASH(`user_id`) BUCKETS 32
PROPERTIES (
"replication_allocation" = "tag.location.default: 3",
"enable_unique_key_merge_on_write" = "true"
);
从已有数据中导入user_id
的值到字典表中,建立user_id
到整数值的映射:
sql
insert into dictionary_tbl(user_id)
select user_id from dwd_dup_tbl group by user_id;
或者只将增量数据中的user_id
的值导入字典表:
sql
insert into dictionary_tbl(user_id)
select dwd_dup_tbl.user_id from dwd_dup_tbl left join dictionary_tbl
on dwd_dup_tbl.user_id = dictionary_tbl.user_id where dwd_dup_tbl.visit_time > '2023-12-10' and dictionary_tbl.user_id is NULL;
在实际场景中,Flink连接器也可用于将数据写入Doris。
假设dim1
、dim3
、dim5
表示我们感兴趣的统计维度,创建以下表来存储聚合结果:
sql
CREATE TABLE `demo`.`dws_agg_tbl` (
`dim1` varchar(50) NOT NULL,
`dim3` varchar(50) NOT NULL,
`dim5` varchar(50) NOT NULL,
`user_id_bitmap` BITMAP BITMAP_UNION NOT NULL,
`pv` BIGINT SUM NOT NULL
) ENGINE=OLAP
AGGREGATE KEY(`dim1`,`dim3`,`dim5`)
DISTRIBUTED BY HASH(`user_id`) BUCKETS 32
PROPERTIES (
"replication_allocation" = "tag.location.default: 3"
);
将数据聚合操作的结果存储到聚合结果表中:
sql
insert into dws_agg_tbl
select dwd_dup_tbl.dim1, dwd_dup_tbl.dim3, dwd_dup_tbl.dim5, BITMAP_UNION(TO_BITMAP(dictionary_tbl.aid)), COUNT(1)
from dwd_dup_tbl INNER JOIN dictionary_tbl on dwd_dup_tbl.user_id = dictionary_tbl.user_id;
使用以下语句执行UV和PV查询:
sql
select dim1, dim3, dim5, user_id_bitmap as uv, pv from dws_agg_tbl;
高效分页
在页面上显示数据时,通常需要分页。传统的分页通常涉及在SQL查询中使用limit
、offset
和order by
。例如,考虑下面要显示的业务表:
sql
CREATE TABLE `demo`.`records_tbl` (
`key` int(11) NOT NULL COMMENT "",
`name` varchar(26) NOT NULL COMMENT "",
`address` varchar(41) NOT NULL COMMENT "",
`city` varchar(11) NOT NULL COMMENT "",
`nation` varchar(16) NOT NULL COMMENT "",
`region` varchar(13) NOT NULL COMMENT "",
`phone` varchar(16) NOT NULL COMMENT "",
`mktsegment` varchar(11) NOT NULL COMMENT ""
) DUPLICATE KEY (`key`, `name`)
DISTRIBUTED BY HASH(`key`) BUCKETS 10
PROPERTIES (
"replication_allocation" = "tag.location.default: 3"
);
假设分页中每页显示100条记录。要获取第一页的数据,可以使用以下SQL查询:
sql
select * from records_tbl order by `key`, `name` limit 100;
获取第二页的数据可以通过以下方式完成:
sql
select * from records_tbl order by `key`, `name` limit 100, offset 100;
但是,在执行深度分页查询(具有较大偏移量)时,即使实际所需的数据行很少,该方法仍然会将所有数据读入内存中进行完整排序,然后再进行后续处理,这是非常低效的。使用自动递增列会为每行分配一个唯一值,从而允许使用where unique_value > x limit y
预先过滤大量数据,从而提高分页效率。
继续前面提到的业务表,向表中添加一个自动递增列,为每一行提供唯一标识符:
sql
CREATE TABLE `demo`.`records_tbl2` (
`key` int(11) NOT NULL COMMENT "",
`name` varchar(26) NOT NULL COMMENT "",
`address` varchar(41) NOT NULL COMMENT "",
`city` varchar(11) NOT NULL COMMENT "",
`nation` varchar(16) NOT NULL COMMENT "",
`region` varchar(13) NOT NULL COMMENT "",
`phone` varchar(16) NOT NULL COMMENT "",
`mktsegment` varchar(11) NOT NULL COMMENT "",
`unique_value` BIGINT NOT NULL AUTO_INCREMENT
) DUPLICATE KEY (`key`, `name`)
DISTRIBUTED BY HASH(`key`) BUCKETS 10
PROPERTIES (
"replication_num" = "3"
);
对于每页显示100条记录的分页,要获取第一页的数据,可以使用以下SQL查询:
sql
select * from records_tbl2 order by unique_value limit 100;
通过记录返回结果中的unique_value
的最大值,我们假设它是99。下面的查询可以获取第二页的数据:
sql
select * from records_tbl2 where unique_value > 99 order by unique_value limit 100;
如果直接查询后一页的内容,不方便直接从前一页的数据中获取unique_value
的最大值(例如直接获取第101
页的内容),可以使用以下查询:
sql
select key, name, address, city, nation, region, phone, mktsegment
from records_tbl2, (select unique_value as max_value from records_tbl2 order by unique_value limit 1 offset 9999) as previous_data
where records_tbl2.unique_value > previous_data.max_value
order by records_tbl2.unique_value limit 100;