MySQL中创建联合主键需在CREATE TABLE时用PRIMARY KEY(a,b)并显式声明各字段NOT NULL;ALTER TABLE添加时须确保字段非空且组合值唯一;联合主键影响索引使用、外键设计及ORM适配。

MySQL 中用 PHP 创建联合主键的正确写法
PHP 本身不直接“创建联合主键”,它通过执行 SQL 语句来操作数据库。关键在 CREATE TABLE 语句中正确声明 PRIMARY KEY 包含多个字段,且这些字段必须都定义为 NOT NULL(否则会报错或被 MySQL 自动忽略)。
常见错误是只写 PRIMARY KEY (a, b),但漏掉 a 和 b 的 NOT NULL 约束,导致建表失败或主键失效。
CREATE TABLE user_role (
user_id INT NOT NULL,
role_id INT NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (user_id, role_id)
);
-
user_id和role_id都必须显式加NOT NULL - 联合主键字段顺序影响索引结构:查询条件含
user_id时能走索引;只查role_id则无法使用该主键索引 - 不能对已有表直接用
ALTER TABLE ... ADD PRIMARY KEY (a,b)添加联合主键,除非所有目标列已为NOT NULL且无重复组合值
用 PDO 执行建表语句时的注意事项
PDO 不会对 SQL 做语法校验,建表失败只会抛出 PDOException,错误信息里通常包含 MySQL 原始报错,比如 ERROR 1170 (42000): BLOB/TEXT column 'xxx' used in key specification without a key length —— 这说明你试图把 TEXT 字段放进联合主键,但没指定前缀长度。
解决方法只有两个:换用 VARCHAR 并设合理长度,或给 TEXT 字段加前缀(如 content(255)),但后者不推荐用于主键,因语义不清晰且易冲突。
立即学习“PHP免费学习笔记(深入)”;
- 建表前建议先用
$pdo->exec("DROP TABLE IF EXISTS user_role")清理测试环境 - 若需兼容不同字符集,注意
VARCHAR(255)在utf8mb4下实际占用 1020 字节,联合主键总长不能超 3072 字节(MySQL 5.7+ 默认限制) - 执行成功后可用
SHOW CREATE TABLE user_role验证主键是否生效
ALTER TABLE 添加联合主键的实操限制
对已有表添加联合主键比新建更危险,因为要同时满足:字段已存在、全部 NOT NULL、组合值全局唯一。任一不满足都会失败。
典型报错:ERROR 1062 (23000): Duplicate entry '1-1' for key 'PRIMARY',说明 (user_id, role_id) 存在重复组合。
- 先运行
SELECT user_id, role_id, COUNT(*) FROM user_role GROUP BY user_id, role_id HAVING COUNT(*) > 1检查重复 - 再用
ALTER TABLE user_role MODIFY user_id INT NOT NULL, MODIFY role_id INT NOT NULL补全约束 - 最后执行
ALTER TABLE user_role ADD PRIMARY KEY (user_id, role_id) - 如果表很大,
ALTER TABLE会锁表,生产环境务必避开高峰
联合主键 vs 单列自增 ID 的取舍
很多人误以为“联合主键一定比单列 ID 更省空间”,其实不然。InnoDB 的二级索引会隐式包含主键值,若主键是 (a,b),那每个二级索引记录都要存两列值;而单列 id 主键只需存一个整数。
真正适合联合主键的场景很明确:业务逻辑天然要求多字段唯一且无额外 ID 意义,比如 user_id + product_id 表示“用户收藏商品”关系,没有比这更自然的主键了。
- 不要为了“看起来规范”强行用联合主键替代自增
id - 如果后续可能加外键引用该表,联合主键会让外键定义变复杂(需引用多个字段)
- ORM 如 Laravel Eloquent 默认依赖单列
id,用联合主键需手动覆盖$primaryKey和$incrementing = false











