tp6中主键必须在模型内显式声明protected $pk = '字段名',否则find()等操作默认使用id字段,导致查不到数据或insertgetid()返回0;$pk仅支持字符串单主键,且字段名须与数据库完全一致。

主键必须在模型类里显式声明 $pk,否则所有基于主键的操作(find()、update()、delete()、save())都会默认用 id 字段,哪怕数据库主键叫 user_id 或 log_no —— 这是绝大多数“查不到数据”“删不掉记录”“insertGetId() 返回 0”的根本原因。
为什么 find(123) 查不到,但日志显示 WHERE id = 123?
ThinkPHP 6 默认只认 id 为主键字段,不会从数据库 schema 自动推导。即使你建表时用的是 CREATE TABLE user (user_id INT PRIMARY KEY AUTO_INCREMENT),只要模型里没写 protected $pk = 'user_id',框架就始终拿 id 去拼 SQL。
-
$pk必须是字符串,不能是数组(TP6 原生不支持复合主键自动识别) - 字段名必须和数据库**完全一致**:数据库是
user_id,就得写'user_id',不是'userId'也不是'USER_ID' - 不能靠配置文件、运行时赋值或构造参数覆盖,必须在模型类属性中直接定义
- 如果同时用了时间戳自动写入(
$autoWriteTimestamp = true),建议关掉它,避免字段名冲突干扰主键逻辑
insertGetId() 总是返回 0,怎么让它读对自增字段?
insertGetId() 底层调用的是 PDO 的 lastInsertId(),而该函数只对数据库中标记为 AUTO_INCREMENT(MySQL)或 SERIAL(PostgreSQL)的列生效。但 TP 不会自己去查数据库元信息——它只信任你写的 $pk 和是否开启 $auto。
- 必须同时设置
protected $pk = 'log_id'和protected $auto = true - 确保数据库中该字段确实是自增类型(TP 不校验,只按你写的来)
- 如果用了分表中间件、重写了
insert()方法,要确认是否绕过了insertGetId()的默认流程 - 别试图用
DB::table()->insertGetId()替代模型方法——它不走模型的$pk逻辑,仍会读id
关联查询(如 hasMany)里外键对不上怎么办?
ThinkPHP 关联方法默认按“当前模型主键名”去匹配关联表的外键。比如 User::hasMany('ScoreRecord', 'user_id'),它会把 User 模型的 $pk 值填进 score_record.user_id。如果你没设 $pk,它就填 id,哪怕你的用户表根本没有 id 字段。
- 外键字段名(第二个参数)必须和关联表中的列名一致,例如数据库里是
openid,就得写hasMany('ScoreRecord', 'openid') - 主键字段名(第三个参数)默认取
$pk,所以必须先保证$pk正确;不写第三个参数就等于用$pk - 不要依赖“自动拼接
_id”规则,那是针对默认$pk = 'id'的快捷写法,自定义主键后必须显式传参
最容易被忽略的一点:改了 $pk 之后,所有基于主键的链式操作(比如 User::where('user_id', 123)->update([...]))仍然会走默认逻辑,除非你用的是模型实例方法((new User())->where(...)->update())或显式指定主键条件。换句话说:$pk 只影响模型内部的主键感知,不改变 where() 的行为——这点在调试时经常让人绕晕。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











