主键与唯一键(AUTO模式)

本节介绍了在AUTO模式数据库中,判断表的主键是Global主键还是Local主键,表的唯一键是Global唯一键还是Local唯一键的方法。

主键

PolarDB-X中,主键分为Global主键与Local主键。当创建出的主键:

  • 能保证全局唯一,就称为Global主键;

  • 只保证分区内唯一,则称为Local主键。

单表和广播表

单表和广播表中的主键都是Global主键,能保证全局唯一。

示例1:单表和广播表中的Global主键

## 单表
CREATE TABLE single_tbl(
 id bigint NOT NULL AUTO_INCREMENT, 
 name varchar(30), 
 PRIMARY KEY(id)
) SINGLE;

## 广播表
CREATE TABLE brd_tbl(
 id bigint NOT NULL AUTO_INCREMENT, 
 name varchar(30), 
 PRIMARY KEY(id)
) BROADCAST;

分区表

在AUTO模式下,建表时如果没有指定分区键、分区算法,则称为自动分区表;建表时如果指定了分区键或者分区算法,称为手动分区表。

自动分区表

自动分区表中的主键都是Global主键,能保证全局唯一。

示例2:自动分区表中的Global主键

## 自动分区表
CREATE TABLE auto_tbl(
 id bigint NOT NULL AUTO_INCREMENT, 
 name varchar(30), 
 PRIMARY KEY(id)
);

手动分区表

Global主键

在手动分区表中,如果主键列包含了全部分区列,该主键就是Global主键,能保证全局唯一。

示例3:手动分区表中的Global主键

表key_tbl的主键列是(id, name, addr),包含了所有分区列(id, addr),所以该表的主键是Global主键,能保证全局唯一。

CREATE TABLE key_tbl(
 id bigint,
 name varchar(10),
 addr varchar(30),
 PRIMARY KEY(id, name, addr)
) PARTITION BY KEY(id, addr);

Local主键

在手动分区表中,如果主键列未包含全部分区列,则该主键就是Local主键。

示例4:手动分区表中的Local主键

表list_tbl的分区列city未被包含在主键列中,所以该表的主键是Local主键,只能保证分区内唯一,无法保证全局唯一。

CREATE TABLE list_tbl(
 order_id bigint,
 city varchar(50),
 name text,
 PRIMARY KEY(order_id)
) PARTITION BY LIST(city)
(
 PARTITION p1 VALUES IN ("Beijing"),
 PARTITION p2 VALUES IN ("Shanghai"),
 PARTITION p3 VALUES IN ("Guangzhou"),
 PARTITION p4 VALUES IN ("Shenzhen"),
 PARTITION p5 VALUES IN(DEFAULT)
);

示例5:Local主键无法保证全局唯一

由于Local主键只能保证分区内部唯一,不保证全局唯一,因此可能出现主键重复的情况。沿用示例4中的list_tbl表,该表使用city作为分区列,因此city不同的数据会被存储到不同分区。

  1. 向list_tbl插入一条数据,执行成功后,该数据被存储到p1分区。

    INSERT INTO list_tbl(order_id, city, name) VALUES (10001, "Beijing", "phone");
    Query OK, 1 row affected
  2. 向list_tbl表插入一条order_id相同且city相同的数据。由于city相同,数据仍将被存储到p1分区,执行SQL时发现插入失败,报主键冲突的错误。可以看到相同主键的值无法插入到相同分区,这说明Local主键可以保证在分区内唯一。

    INSERT INTO list_tbl(order_id, city, name) VALUES (10001, "Beijing", "book");
    (1062, "ERR-CODE: [TDDL-4614][ERR_EXECUTE_ON_MYSQL] Error occurs when execute on GROUP 'TEST_DB_P00000_GROUP' ATOM 'dskey_test_db_p00000_group#polardbx-storage-0-master#11.167.60.147-1766#test_db_p00000': Duplicate entry '10001' for key 'PRIMARY' ")
  3. 向list_tbl表插入一条order_id相同但city不同的数据,因为city的值为“Shanghai”,数据将被存储到p4分区,执行成功。此时list_tbl表内存在两行主键重复的数据,说明Local主键无法保证全局唯一。

    INSERT INTO list_tbl (order_id, city, name) VALUES (10001, "Shenzhen", "camera");
    Query OK, 1 row affected
    
    SELECT * FROM list_tbl;
    +----------+----------+--------+
    | order_id | city | name |
    +----------+----------+--------+
    | 10001 | Beijing | phone |
    | 10001 | Shenzhen | camera |
    +----------+----------+--------+
    2 rows in set

示例6:在含有重复主键的表上执行DDL,可能出现主键冲突报错

使用Local主键的表内可能存在重复的主键值,当在该表上执行数据重分布相关的操作时(如执行分区变更的DDL、将表同步至下游),可能会出现主键冲突的错误。

沿用示例5中的表list_tbl,该表已经存在两行主键相同的数据,一行存储在city为“Beijing”的分区,另一行存储在city为“Shenzhen”的分区。执行如下所示变更分区策略的DDL,使得city为“Beijing”和“Shenzhen”的数据存储在同一个分区,DDL会执行失败,报主键冲突的错误。这是因为该DDL使得主键重复的数据出现在同一个分区,违反了Local主键的分区内唯一性。

ALTER TABLE list_tbl 
PARTITION BY LIST (city)
(
 PARTITION p1 VALUES IN ("Beijing", "Shenzhen"),
 PARTITION p2 VALUES IN ("Shanghai"),
 PARTITION p3 VALUES IN ("Guangzhou"),
 PARTITION p5 VALUES IN(DEFAULT)
);
(4700, "ERR-CODE: [TDDL-4700][ERR_SERVER] server error by Failed to execute the DDL task. Caused by: ERR-CODE: [TDDL-5321][ERR_GLOBAL_SECONDARY_INDEX_BACKFILL_DUPLICATE_ENTRY] Duplicated entry '10001' for key 'PRIMARY' ")

对于使用Local主键的表,为避免主键值重复引发的主键冲突,建议:

  • 使用auto_increment属性,由PolarDB-X生成主键值;

  • 避免业务侧手动写入指定的主键值。

    重要

    对于使用Local主键的表,如果已经存在主键重复的情况,往下游同步数据的时候需避免下游出现主键冲突。例如将Local主键的表通过DTS向云原生数据仓库AnalyticDB MySQL版进行同步时,如果云原生数据仓库AnalyticDB MySQL版的主键沿用PolarDB-X的主键,就可能出现冲突,此时建议将云原生数据仓库AnalyticDB MySQL版的主键设为PolarDB-X表的主键列和分区列的全部集合。

唯一键

与主键类似,在PolarDB-X中,唯一键分为Global唯一键与Local唯一键。当创建出的唯一键:

  • 能保证全局唯一,就称为Global唯一键;

  • 只保证分区内唯一,则称为Local唯一键。

单表和广播表

单表和广播表中的唯一键都是Global唯一键,能保证全局唯一。

示例7:单表和广播表中的Global唯一键

## 单表
CREATE TABLE single_tbl(
 serial_id bigint,
 name varchar(30), 
 UNIQUE KEY(serial_id)
) SINGLE;

## 广播表
CREATE TABLE brd_tbl(
 serial_id bigint,
 name varchar(30), 
 	UNIQUE KEY(serial_id)
) BROADCAST;

分区表

在AUTO模式下,建表时如果没有指定分区键、分区算法,则称为自动分区表;建表时如果指定了分区键或者分区算法,称为手动分区表。

自动分区表

自动分区表中的唯一键都是Global唯一键,能保证全局唯一。

示例8:自动分区表中的Global唯一键

## 自动分区表
CREATE TABLE auto_tbl(
 serial_id bigint,
 name varchar(30), 
 UNIQUE KEY(serial_id)
);

手动分区表

Global唯一键

在手动分区表中,如果唯一键列包含了全部分区列,该唯一键就是Global唯一键,能保证全局唯一。

示例9:手动分区表中的Global唯一键

表hash_tbl的唯一键列是(inner_id, type_id),包含了所有的分区列(type_id),所以该表的唯一键是Global唯一键。

CREATE TABLE hash_tbl(
 type_id int,
 inner_id int,
 UNIQUE KEY(inner_id, type_id)
) PARTITION BY HASH(type_id);

示例10:手动分区表中由UNIQUE GLOBAL INDEX构成的全局唯一键

在手动分区表中,全局二级索引也是一个Global唯一键,能保证全局唯一。表key_tbl包含一个索引列为serial_id的UNIQUE GLOBAL INDEX,它能保证serial_id的全局唯一性,因此是Global唯一键。

CREATE TABLE key_tbl(
 type_id int,
 serial_id int,
 UNIQUE GLOBAL INDEX u_sid(serial_id) PARTITION BY HASH(serial_id)
) PARTITION BY HASH(type_id);

Local唯一键

示例11:手动分区表中的Local唯一键

在手动分区表中,如果唯一键列未包含全部分区列,该唯一键就是Local唯一键。

表range_tbl的唯一键列是serial_id,未包含分区列order_time,所以该表的唯一键是Local唯一键,只能保证分区内唯一,无法保证全局唯一。

CREATE TABLE range_tbl(
 id int primary key auto_increment,
 serial_id int,
 	order_time datetime NOT NULL,
 UNIQUE KEY(serial_id)
) PARTITION BY RANGE(order_time)
(
 PARTITION p1 VALUES LESS THAN ('2022-12-31'),
 PARTITION p2 VALUES LESS THAN ('2023-12-31'),
 PARTITION p3 VALUES LESS THAN (MAXVALUE)
);

示例12:Local唯一键无法保证全局唯一

与Local主键类似,由于Local唯一键不保证全局唯一,因此可能出现唯一键重复的情况。

  1. 沿用示例11中的range_tbl,向表中插入一条数据,执行成功后,该数据被存储到p1分区。

    INSERT INTO range_tbl(serial_id, order_time) VALUES (20001, '2022-01-01');
    Query OK, 1 row affected
  2. 向range_tbl表插入一条serial_id相同,且order_time为“2022-01-02”的数据。order_time的值决定了数据仍将被存储到p1分区,执行SQL发现插入失败,报唯一键冲突的错误。可以看到相同唯一键的值无法插入到相同分区,这说明Local唯一键可以保证在分区内部唯一。

    INSERT INTO info_tbl(serial_id, order_time) VALUES (20001, '2022-01-02');
    (1062, "ERR-CODE: [TDDL-4614][ERR_EXECUTE_ON_MYSQL] Error occurs when execute on GROUP 'D25_000001_GROUP' ATOM 'dskey_d25_000001_group#polardbx-storage-1-master#11.167.60.147-1766#d25_000001': Duplicate entry '20001' for key 'serial_id' ")
  3. 向range_tbl表插入一条serial_id相同,且order_time为“2023-01-01”的数据。order_time的值决定了数据将被存储到p3分区,执行成功。此时range_tbl表内存在两行唯一键重复的数据,这说明Local唯一键无法保证全局唯一。

    INSERT INTO range_tbl(serial_id, order_time) VALUES (20001, '2024-01-01');
    Query OK, 1 row affected
    
    SELECT * FROM range_tbl;
    +----+-----------+---------------------+
    | id | serial_id | order_time |
    +----+-----------+---------------------+
    | 2 | 20001 | 2024-01-01 00:00:00 |
    | 1 | 20001 | 2022-01-01 00:00:00 |
    +----+-----------+---------------------+
    2 rows in set

示例13:在含有重复唯一键的表上执行DDL,可能出现唯一键冲突报错

与Local主键类似,用到Local唯一键的表内可能存在重复的唯一键值,当在该表上执行数据重分布相关的操作时(如执行分区变更的DDL、将表同步至下游),可能会出现唯一键冲突的错误。

沿用示例12中的表range_tbl,该表已经存在两行serial_id相同的数据,一行存储在p1分区,另一行存储在p2分区。尝试执行变更表类型的DDL,使range_tbl表从手动分区表变成单表,这将引发range_tbl表内的数据重分布。

ALTER TABLE range_tbl SINGLE;
(4700, "ERR-CODE: [TDDL-4700][ERR_SERVER] server error by Failed to execute the DDL task. Caused by: ERR-CODE: [TDDL-5321][ERR_GLOBAL_SECONDARY_INDEX_BACKFILL_DUPLICATE_ENTRY] Duplicated entry '200001' for key 'PRIMARY' ")

DDL执行失败,报了关于唯一键冲突的错误。这是因为DDL在把range_tbl转换为单表时,在单表内出现了重复的唯一键值,违反了单表内唯一键的唯一性。

对于使用Local唯一键的表,为避免唯一键值重复引发的唯一键冲突,应该由业务侧采取措施确保唯一键值的唯一性。

重要

对于使用Local唯一键的表,如果已经存在唯一键值重复的情况,当往下游同步数据时出现唯一键冲突时,建议人工订正源端数据。

常见问题

  • Q:创建Global主键、Global唯一键有特殊的语法吗?

    A:没有,使用与MySQL一样的语法创建主键、唯一键即可。请注意创建的主键、唯一键需要满足上文中关于Global主键、Global唯一键的定义。

  • Q:目前使用的主键是Local主键,但我想保证主键全局唯一,该怎么做?

    A:请参见Sequence生成唯一值作为主键值,可保证主键全局唯一。