MYSQL-主键(Primary Key)

作者介绍:简历上没有一个精通的运维工程师。请点击上方的蓝色《运维小路》关注我,下面的思维导图也是预计更新的内容和当前进度(不定时更新)。

数据库是一个系统(应用)最重要的资产之一,所以我们的数据库将从以下几个数据库来进行介绍。

MySQL**(本章节)**

PostgreSQL

MongoDB

Redis

Etcd

主键是数据库表设计中最为重要的概念之一,它远不止是"唯一标识符"那么简单,尤其是在 MySQL 默认的 InnoDB 存储引擎中。

一、主键是什么?

主键是数据库表中的一个或多个字段(列),它的值用于唯一地标识表中的某一条记录。

打个比方:身份证号是中国公民在主表"人口数据库"中的主键,每个人的身份证号都是唯一且不为空的,通过它可以精准地定位到一个人。

二、主键的核心特性

  1. 唯一性 (Unique),主键值在整个表中必须是唯一的,任意两行都不能拥有相同的主键值。

  2. 非空性 (Not Null),主键字段不能为空(NULL)。只要定义了主键,数据库就会强制要求插入或更新的记录必须为该字段提供一个值。

  3. 唯一标识 (Identifies One Row),主键的唯一目的是唯一地标识一条记录。一个主键值对应且只对应一行数据。

三、为什么主键如此重要?(作用与好处)

  1. 保证数据完整性:唯一性和非空性约束确保了表中每一行数据都可以被唯一、有效地标识,避免了重复数据和脏数据的产生。

  2. 作为记录的默认访问路径

  • 数据行实际上是存储在主键索引的叶子节点上的。

  • 表的数据物理存储顺序与主键顺序大致相同(按主键顺序插入效率最高)。

  • 这种索引被称为聚簇索引(Clustered Index)

  • 这是 InnoDB 引擎下主键最关键的作用。在 InnoDB 中,表数据本身就是基于主键索引组织 的一棵 B+Tree。这意味着:

  1. 提高查询性能
  • 因为数据就存储在主键索引上,所以通过主键进行查询是最快的访问方式,通常只需要 1-3 次磁盘 I/O 就能定位到数据行。
  1. 作为外键参照的基础
  • 其他表可以通过外键(Foreign Key)来引用本表的主键,从而建立表与表之间的关联关系,保证数据的一致性和参照完整性。

四、InnoDB 存储引擎下主键的特殊性

理解 InnoDB 的聚簇索引结构是理解主键的关键。

  • 如果表定义了主键:InnoDB 就使用这个主键来作为聚簇索引。

  • 如果表没有定义主键 :InnoDB 会选择一个第一个所有列都是非空的唯一索引(UNIQUE INDEX) 来作为聚簇索引。

  • 如果既没有主键,也没有合适的唯一索引 :InnoDB 会在内部自动生成一个隐藏的、名为 GEN_CLUST_INDEX 的 6 字节的行 ID(rowid)作为聚簇索引。这个行 ID 是单调递增的。

重要结论 :在 InnoDB 中,每个表都必须有且仅有一个聚簇索引,而主键就是默认的聚簇索引。


1. 代理主键 (Surrogate Key) vs 自然主键 (Natural Key)

  • 自然主键:使用具有业务含义的字段作为主键(如:身份证号、手机号、邮箱)。

  • 优点:避免新增一张表来存储关系。

  • 缺点:如果业务规则发生变化(如身份证号升位),修改主键的代价极高(会导致所有相关外键都要更新)。长度可能较长,影响二级索引性能。

  • 代理主键:使用一个与业务无关的、无意义的字段作为主键(如:自增整数、UUID)。

  • 优点:稳定,业务逻辑变化不影响主键。通常是简单的整数,性能极佳。

  • 缺点:需要额外的字段,有时需要多表连接才能表达业务含义。

现代数据库设计实践中,绝大多数推荐使用代理主键 ,尤其是使用 AUTO_INCREMENT 的自增整数。

2. 主键的选择

  • 推荐:自增整数 (BIGINT AUTO_INCREMENT)

  • 性能好INT/BIGINT 类型占用空间小,比较速度快。自增特性保证了新数据总是顺序追加到当前 B+Tree 的末尾,写入效率高,几乎不会造成页分裂。

  • 简单:对应用层透明,无需关心其生成逻辑。

  • 不推荐/谨慎使用:UUID

  • 缺点

  • 如果必须使用 UUID ,可以考虑有序 UUID(如 MySQL 8.0 的 UUID_TO_BIN(... , 1))或将其作为业务键,另设一个自增 INT 作为主键。

  1. 空间大:CHAR(36) 或 BINARY(16),比 INT(4字节)/BIGINT(8字节) 大得多。作为主键,每个二级索引的叶子节点都会存储主键值,这会显著增加索引大小。

  2. 随机性 :插入新记录时,UUID 是随机的,无法保证顺序追加。这会导致 InnoDB 不得不将新行插入到现有数据页的中间位置,造成页分裂 ,从而影响写入性能 并产生碎片

3. 复合主键 (Composite Primary Key)

主键可以由多个字段组合而成。

  • 使用场景:通常在多对多关系的中间表中使用。

  • 例如:user_role 表,有 user_idrole_id 两个字段,其组合可以唯一确定一条记录。PRIMARY KEY (user_id, role_id)

  • 缺点

  • 每个二级索引都会包含所有主键列,导致索引体积庞大。

  • 在 InnoDB 中,其他表的外键引用此表时会变得复杂。

建议:除非有非常明确的理由(如上述中间表),否则尽量使用单一的代理主键。

六、相关命令

复合主键

sql 复制代码
CREATE TABLE order_items (
    order_id INT NOT NULL,
    product_id INT NOT NULL,
    quantity INT NOT NULL,
    PRIMARY KEY (order_id, product_id) -- 指定 (order_id, product_id) 为联合主键
);

为已存在的表添加主键

sql 复制代码
ALTER TABLE table_name
ADD PRIMARY KEY (column_name);

查看主键,实际是查看表结构创建命令,就可以看到。

sql 复制代码
mysql> SHOW CREATE TABLE test_data;
+-----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Table     | Create Table                                                                                                                                                                                                                                                                                                                           |
+-----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| test_data | CREATE TABLE `test_data` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `value` varchar(255) DEFAULT NULL,
  `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
  `random_number` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `idx_test_data_value` (`value`)
) ENGINE=InnoDB AUTO_INCREMENT=8437687 DEFAULT CHARSET=latin1 |
+-----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)

运维小路

一个不会开发的运维!一个要学开发的运维!一个学不会开发的运维!欢迎大家骚扰的运维!

关注微信公众号《运维小路》获取更多内容。

相关推荐
todoitbo2 小时前
使用n8n搭建服务器监控系统:从Webhook到Telegram告警的完整实现
运维·服务器·数据库·ai·向量数据库·流处理·n8n
LCG元2 小时前
Nginx 配置入门到实战:从静态网站到负载均衡
linux
安丘贾队长2 小时前
json啊啊啊啊啊啊啊啊啊
运维
代码程序猿RIP2 小时前
【Linux面经】OSI七层模型和TCP/IP四层体系结构
linux·网络·面试·面经
我什么都学不会2 小时前
DNS主从服务器练习
linux·运维·服务器
zt1985q2 小时前
外网访问项目研发管理软件 codes
运维·服务器·windows·网络协议·tcp/ip
华纳云IDC服务商2 小时前
DNS记录更新后为什么还是访问不到新服务器?
运维·服务器
青靴3 小时前
用 Docker Compose 管理留言板多容器应用
运维·docker·容器
涛声依旧393163 小时前
安装部署自己的nginx
运维·服务器·nginx