在数据库设计里,主键的作用是唯一标识表中的每一行记录。但有些业务场景下单靠一个字段无法保证唯一性,比如订单明细表需要订单号加商品编号共同确定一行数据,学生选课表需要学号加课程号组合定位一条记录。这种由两个或多个字段共同组成的主键,就叫做联合主键(也叫复合主键)。这篇文章就来详细说说在MySQL中如何设置联合主键,以及使用过程中需要注意的坑。

建表时直接定义联合主键
最常见的做法是在创建表的时候就把联合主键写进建表语句。语法上非常简单,在字段定义完成之后,使用PRIMARY KEY关键字括号包裹多个字段名,字段之间用逗号分隔。下面是一个订单明细表的经典例子:
CREATE TABLE order_item (
order_id BIGINT NOT NULL COMMENT '订单ID',
product_id BIGINT NOT NULL COMMENT '商品ID',
quantity INT NOT NULL DEFAULT 1 COMMENT '数量',
price DECIMAL(10,2) NOT NULL COMMENT '成交单价',
PRIMARY KEY (order_id, product_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
这条语句执行后,order_id和product_id两个字段共同构成了主键。也就是说,订单号可以重复,商品编号也可以重复,但二者的组合不允许出现重复。比如同一订单里插入两次相同商品会报主键冲突错误,而不同订单里出现相同商品则完全没问题。
还有一种写法是把PRIMARY KEY直接写在字段定义行上,但这种写法只适用于单列主键,多列组合必须放在表级约束里声明。另外要注意,构成联合主键的每个字段都不能允许NULL,即使你没有显式写NOT NULL,MySQL也会自动把这些字段设为NOT NULL,因为主键本身就要求非空且唯一。
给已有表添加或修改联合主键
如果表已经存在,之前没有设置主键或者设置错了,可以通过ALTER TABLE语句来补加。前提是表里现有数据不能违反联合主键的唯一性约束,否则添加会失败。示例语句如下:
-- 先给没有主键的表添加联合主键 ALTER TABLE order_item ADD PRIMARY KEY (order_id, product_id); -- 如果原来有单列主键,必须先删除再重建 ALTER TABLE order_item DROP PRIMARY KEY; ALTER TABLE order_item ADD PRIMARY KEY (order_id, product_id); -- 查看当前表的主键结构 SHOW INDEX FROM order_item WHERE Key_name = 'PRIMARY';
这里有一个新手常踩的坑:想直接在已有主键的表上执行ADD PRIMARY KEY,MySQL会报Multiple primary key defined错误。正确流程是先DROP掉旧主键再添加新的组合。如果旧主键字段上设置了AUTO_INCREMENT,删除主键前还需要先修改该字段去掉自增属性,否则会收到Incorrect table definition的报错。
如果表中数据已经存在重复组合,添加联合主键时会报Duplicate entry错误,并提示具体是哪个值冲突了。这时需要先用查询语句找出重复数据,手动清理或者归并后再重新添加约束。
联合主键与联合唯一索引的区别
很多人分不清PRIMARY KEY (a, b)和UNIQUE KEY (a, b)的差异。从唯一性约束的角度看,两者确实都能防止重复组合,但它们有几个本质区别,直接影响表设计时的选型。
| 对比项 | 联合主键 | 联合唯一索引 |
|---|---|---|
| 是否允许NULL | 所有列强制NOT NULL | 列可为NULL,NULL组合不参与唯一判断 |
| 一表数量 | 只能有一个 | 可以建多个 |
| 作为外键引用 | 可被其他表外键引用 | 一般不作为外键引用目标 |
| 默认索引类型 | 聚簇索引(InnoDB) | 二级索引 |
在InnoDB引擎中,主键就是数据的物理组织方式,整张表按主键顺序存放,这就是所谓的聚簇索引。所以联合主键的字段顺序不仅影响唯一性判断(其实不影响唯一性本身,但影响索引利用),更直接关系到查询性能。建议把最常出现在WHERE条件中等值匹配的字段放在最前面,比如订单明细表中order_id在前,因为按订单号查明细的频率远高于按商品查。
还有一种常见的替代方案是额外增加一个自增ID做主键,再给业务字段组合建唯一索引。这种做法的好处是主键短小,作为其他表外键时占用空间更少,二级索引也不会因为带着长主键而膨胀,在互联网业务中往往是更被推荐的实践。
使用联合主键的注意事项
第一点是字段长度问题。联合主键总长度不宜过长,MySQL对索引键长度有限制,InnoDB默认是3072字节,超长字段组合做主键会浪费大量空间,因为所有二级索引的叶子节点都要存一份主键值。
第二点是外键关联的写法。如果其他表要引用联合主键,外键也必须包含相同的全部字段,示例如下:
CREATE TABLE order_item_log (
log_id BIGINT AUTO_INCREMENT PRIMARY KEY,
order_id BIGINT NOT NULL,
product_id BIGINT NOT NULL,
action VARCHAR(50) NOT NULL,
FOREIGN KEY (order_id, product_id)
REFERENCES order_item (order_id, product_id)
) ENGINE=InnoDB;
第三点是要警惕NULL带来的语义差异。唯一索引下(1, NULL)和(1, NULL)不算重复,因为NULL不等于任何值包括它自己,这可能让去重逻辑出现漏洞。而联合主键由于强制非空,不存在这个问题,这也是需要严格去重的场景下选择主键方案的理由之一。
总的来说,联合主键适合字段组合天然唯一、且组合长度可控的业务表,例如订单明细、选课记录、角色权限关联表等。设置方法本身很简单,建表时写PRIMARY KEY (a, b),改表时先DROP再ADD,真正需要花心思的是字段顺序的设计和是否改用代理主键加唯一索引的方案,这些决策会长期影响数据库的性能和可维护性。
mysql联合主键联合主键设置primary key修改时间:2026-09-13 14:44:41