在 MySQL 数据库中,无主键表的创建会对高可用性架构引入较大风险:
Binlog 同步机制失效 当发生全表数据更新或范围删除操作时,基于 Row 格式的 binlog 在从库回放过程中,由于缺乏聚簇索引,每条 binlog 事件都需要执行全表扫描定位数据。实测发现,百万级数据表的这类操作会导致主备同步延迟呈指数级增长。
存储引擎特性冲突 InnoDB 引擎的聚簇索引特性要求必须有唯一行标识。当表缺失显式主键时:
引擎自动生成的隐式 ROW_ID
不具备索引可见性。
二级索引叶子节点无法精确定位数据页。
每次 DML 操作实质上都退化为全表遍历。
高可用链路雪崩效应 无主键表引发的延迟具有自我恶化特性:
主库持续产生新 binlog 事件。
从库因全表扫描无法及时消费事件队列。
最终导致复制中断。
故障切换时出现数据不一致风险。
事后处理 - 重做备库
在无主键表已经产生后,在不改变表结构的情况下处理的办法,办法是设置主从延迟告警,并人工查看是否为无主键表造成,并通过工单重做备库,带来了额外的人力负担,不可持续。
事前处理 - 隐藏主键
MySQL 社区 8.0.30 带来了隐藏主键(GIPK, i.e. Generated Invisible Primary Key)特性,可以识别 CREATE TABLE
建表语句中的无主键表,通过 MySQL 的「隐藏列」属性,给表添加一个用户不可见的主键索引。这样既可以对用户使用不造成影响,又可以解决无主键表的问题,让新建表不存在无主键的情况。
云数据库 MySQL 版在 8.0 社区版的基础上对社区的隐藏主键进行了若干优化,并在 MySQL 5.7 通过唯一索引实现了类似 GIPK 的功能。云数据库 MySQL 版在 MySQL 8.0 和 MySQL 5.7 中提供的隐藏主键能力对比如下。
GIPK属性 | MySQL 8.0 | MySQL 5.7 |
---|---|---|
KEY类型 | 主键索引(Primary Key) | 唯一索引(Unique Index) |
主键列名称 | my_row_id | byte_rds_ai_uk_row_id |
创建 GIPK 条件 | CREATE TABLE 语句无主键 | CREATE TABLE 语句无主键或唯一键 |
用户新增 PK 后,是否自动DROP 主键列 | 是 | 是 |
用户 Select * 查询是否能选中 GIPK | 否 | 否 |
控制参数(session) | sql_generate_invisible_primary_key | loose_sql_generate_invisible_primary_key |
展示 GIPK 参数 | show_gipk_in_create_table_and_information_schema | loose_show_gipk_in_create_table_and_information_schema |
修改实例参数为 ON
。
MySQL 8.0:sql_generate_invisible_primary_key
MySQL 5.7:loose_sql_generate_invisible_primary_key
说明
ON
。会话级别打开 loose_show_gipk_in_create_table_and_information_schema
参数后可以在 show create table
中看到隐藏主键 byte_rds_ai_uk_row_id
。
mysql> set loose_show_gipk_in_create_table_and_information_schema=on; mysql> show variables like '%gipk%'; +--------------------------------------------------------+-------+ | Variable_name | Value | +--------------------------------------------------------+-------+ | loose_show_gipk_in_create_table_and_information_schema | ON | +--------------------------------------------------------+-------+ 1 row in set (0.01 sec) mysql > show create table tt1; +-------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | Table | Create Table | +-------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | tt1 | CREATE TABLE tt1 ( `byte_rds_ai_uk_row_id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT 'BYTE_RDS_INVISIBLE', `f1` int DEFAULT NULL, `f2` int DEFAULT NULL, UNIQUE KEY (`byte_rds_ai_uk_row_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 | +-------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ 1 row in set (0.01 sec)
会话级别打开 show_gipk_in_create_table_and_information_schema
参数后可以在 show create table
中看到隐藏主键 my_row_id
。
mysql> set show_gipk_in_create_table_and_information_schema=on; mysql> show variables like '%gipk%'; +--------------------------------------------------+-------+ | Variable_name | Value | +--------------------------------------------------+-------+ | show_gipk_in_create_table_and_information_schema | ON | +--------------------------------------------------+-------+ 1 row in set (0.01 sec) mysql> show create table t00; +-------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | Table | Create Table | +-------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | t00 | CREATE TABLE `t00` ( `my_row_id` bigint unsigned NOT NULL AUTO_INCREMENT /*!80023 INVISIBLE */, `id` int DEFAULT NULL, `c` varchar(16) DEFAULT NULL, PRIMARY KEY (`my_row_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 | +-------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ 1 row in set (0.01 sec)
打开 GIPK 后,云数据库 MySQL 版对 DDL 的支持情况如下表所示。
说明
内核小版本为 20241015 或更新版本。
描述 | DDL 示例 | MySQL 5.7 | MySQL 8.0 |
---|---|---|---|
建表 DDL 中有用户定义的 auto_increment列(不定义为索引) |
| 不允许。 | |
建表 DDL 中有用户定义的 auto_increment 列(定义为索引) |
| 允许。 | |
建表 DDL 中使用 GIPK 预留列名,不作为隐藏主键(非 |
| 不允许。 | 允许,但输出告警 |
使用 GIPK 相关 COMMENT |
| 允许。 | |
创建无主键分区表 DDL |
| 允许。跳过 GIPK 创建。 | |
binlog_format=statment 下 |
| 不允许。无法修改为 SBR。 |
示例中使用的表结构
create table gipk_test(i int, c varchar(255));
create table gipk_test(i int auto_increment primary key, c varchar(255));
描述 | DDL 示例 | MySQL 5.7 | MySQL 8.0 |
---|---|---|---|
给 GIPK 主键表添加主键 |
| 允许。 | |
给无主键表添加了自增列 |
| 不允许。 | |
给无主键表添加了自增列并作为 KEY |
| 允许。 | |
用户给无主键表添加了自增列并作为 UNIQUE KEY |
| 允许。 | |
Drop 表中的隐藏主键列 |
| 允许。 | |
Drop 表中的主键列 |
| 允许。 | |
Drop 表中的主键 |
| 允许。 | |
Drop 表中的隐藏主键 |
| 允许。 | |
将表中的一列升级为主键 |
| 允许。 | |
GIPK 表将 GIPK 作为分区键 |
| 不允许。 | |
GIPK 表将其他列作为分区键 |
| 允许。 |
关于隐藏主键相关的告警处理,请参见如何处理隐藏主键相关报错。