MySQL只允许一个主键,可为单列或联合主键;已有主键时应加UNIQUE约束保证业务唯一性,联合主键需在CREATE TABLE末尾显式声明且各列NOT NULL。

MySQL 里不能同时存在两个主键,所谓“既有 id 主键,又加联合主键”,本质是误解:你只能有一个主键,但这个主键可以是单列(如 id),也可以是多列(如 (user_id, order_id))。如果已有 id 主键,再想靠多列组合保证业务唯一性,正确做法是加 UNIQUE 约束,不是“加第二个主键”。
创建表时直接定义联合主键
这是最干净的方式,适用于新表设计。联合主键必须在 CREATE TABLE 语句末尾用 PRIMARY KEY(column1, column2) 显式声明,不能在字段定义行后加 PRIMARY KEY —— 那样只会让 MySQL 认为你要建单列主键,报错“重复定义主键”。
常见错误现象:ERROR 1068: Multiple primary key defined
正确写法示例:
CREATE TABLE orders ( user_id INT NOT NULL, order_id INT NOT NULL, amount DECIMAL(10,2), PRIMARY KEY (user_id, order_id) );
注意点:
-
user_id和order_id都必须声明为NOT NULL,否则建表失败 - 联合主键会自动创建一个 B+ 树索引,顺序很重要:查询条件含
user_id能走索引,只查order_id则无法使用该索引(最左前缀原则) - 一旦设为联合主键,就不能再对其中任一列单独加
PRIMARY KEY或AUTO_INCREMENT
已有表想把单主键换成联合主键
这需要两步:先删原主键,再建新联合主键。不能跳过删除步骤,否则报错 ERROR 1068。
操作顺序必须严格:
- 执行
ALTER TABLE t DROP PRIMARY KEY—— 注意:如果原主键是AUTO_INCREMENT,这一步会同时移除自增属性,后续需手动补回(若仍需) - 再执行
ALTER TABLE t ADD PRIMARY KEY (col_a, col_b)
容易踩的坑:
- 删主键前没备份数据,尤其当表有外键引用时,
DROP PRIMARY KEY可能触发级联约束检查失败 - 误以为
ADD PRIMARY KEY是“添加”,其实是“替换”,它隐式包含删除旧主键逻辑(但仅限无名主键;若主键有显式名称,得用DROP INDEX) - 联合主键列顺序和业务查询模式不匹配,导致大量
Using filesort或全表扫描
已有 id 主键,还想保证多列组合唯一
这才是实际中最常见的需求。比如用户表有自增 id 主键,但要求每个 email 只能注册一次,且每对 (department, role) 组合唯一 —— 这时应该用 UNIQUE 约束,不是动主键。
实操建议:
- 加唯一约束:
ALTER TABLE users ADD UNIQUE (email) - 加联合唯一约束:
ALTER TABLE permissions ADD UNIQUE (department, role) - 约束名可选但推荐:
ADD CONSTRAINT uk_dept_role UNIQUE (department, role),方便后续定位和删除
关键区别:
-
UNIQUE允许NULL值(单个NULL不参与唯一性比较),而主键绝对不允许NULL - 一张表可有多个
UNIQUE约束,但只能有一个PRIMARY KEY -
UNIQUE同样会建索引,性能影响和主键索引类似,但语义更清晰:它表达的是“业务规则”,不是“数据标识”
联合主键对 INSERT 和 UPDATE 的实际影响
联合主键不是语法糖,它直接影响 DML 行为。最常被忽略的是冲突判定粒度。
例如表 student_course (student_id, course_id) PRIMARY KEY:
-
INSERT INTO student_course VALUES (101, 201)成功 - 再执行同一条语句会报
ERROR 1062: Duplicate entry '101-201' for key 'PRIMARY' - 但
INSERT INTO student_course VALUES (101, 202)完全合法 —— 因为是另一组组合
真正容易出问题的地方在于:
- 应用层做“是否存在”的判断时,习惯性只查
student_id,漏了course_id,结果重复插入 - 批量导入脚本没处理好冲突,用
INSERT IGNORE或ON DUPLICATE KEY UPDATE时,KEY 必须对应联合主键名(通常是PRIMARY),不能写成单列名 - ORM 框架映射联合主键需显式配置(如 JPA 的
@IdClass或@EmbeddedId),否则 save() 可能静默失败
联合主键本身不难写,难的是所有上下游代码、迁移脚本、权限策略都得按“多维唯一”来理解——稍一松懈,就变成数据层面的“薛定谔的唯一性”。


















