复合主键需在模型中显式声明为数组,如protected $pk = ['order_id', 'product_id'];操作时必须传全主键字段,否则查询更新静默失败;迁移需禁用默认id并手动设primary_key。

复合主键在模型里必须显式声明 $pk
ThinkPHP 默认只认 id 作为主键,哪怕数据库表本身是用 (order_id, product_id) 建的联合主键,模型不声明就当单主键处理——后续 save()、find() 全部失效或报错。
正确做法是在模型类中把 $pk 设为数组:
protected $pk = ['order_id', 'product_id'];
- 不能写成字符串
'order_id,product_id',必须是索引数组 - 字段顺序要和建表时
PRIMARY KEY (a,b)一致,影响索引匹配和 WHERE 条件生成 - 如果表前缀已配置,字段名必须是真实列名,不加前缀
save() 插入复合主键数据不返回自增 ID 是正常的
复合主键意味着没有单一自增字段,$model->id 这种写法必然报错:[0]类的属性不存在: app\serve\model\Bao->id。这不是 bug,是设计使然。
插入后想取值,只能手动读:
$bao = model('Bao');
$bao->topic_id = $topic_id;
$bao->user_id = $user_id;
$bao->save();
// 此时主键值就是你刚赋的那几个字段
$primaryKeyValues = [$bao->topic_id, $bao->user_id];
-
save()成功后模型实例会保留你设的主键字段值,直接取即可 - 不要试图调用
getLastInsID()或依赖insertGetId(),它们只对单列自增主键有效 - 若业务强依赖“插入后立刻拿到完整主键”,建议改用
Db::insert()+ 手动构造 WHERE 条件
查询、更新、删除必须传全复合主键字段
ThinkPHP 的 find()、where()、update() 等方法,在复合主键下不会自动拆解参数。传参不全就查不到,也不会报错,只是返回空。
例如主键是 ['user_id', 'role_id']:
- ✅ 正确:
UserRole::find(['1001', 'admin'])或UserRole::where(['user_id'=>1001, 'role_id'=>'admin'])->find() - ❌ 错误:
UserRole::find(1001)(只传一个,等价于WHERE user_id = 1001,缺role_id) - ❌ 错误:
UserRole::where('user_id', 1001)->update([...])(没带role_id,条件不完整)
关联查询同理:belongsTo 定义里第三个参数(父表主键)如果是复合的,也得传数组,否则关联失效。
迁移文件里定义复合主键要绕过默认 id 字段
think-migrate 默认会加 id 自增字段,和复合主键冲突。必须显式关掉,并手动指定 primary_key:
public function change()
{
$table = $this->table('order_items', [
'primary_key' => ['order_id', 'product_id']
]);
$table->addColumn('order_id', 'integer')
->addColumn('product_id', 'integer')
->addColumn('quantity', 'integer')
->create();
}
- 不写
'primary_key'配置,migrate 仍会加id字段并设为主键 - 即使表结构手工改了,迁移文件没同步,下次执行
php think migrate:refresh就会崩 - 已有表补迁移?先用
dropPrimaryKey()再addPrimaryKey(),别硬删字段
$pk 写错类型、迁移没关默认 id——都会导致操作静默失败,而不是抛异常。php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











