根本原因是session驱动未显式设为database,导致即使配置数据库参数也仍用文件驱动;需确认type或driver配置为'database'、表结构正确、read()严格校验过期并返回空字符串、升级时替换旧Query调用、高并发下启用事务或改用Redis缓存。

Session写不进数据库,think_session表空着
根本原因通常是驱动没真正生效——ThinkPHP默认用文件驱动,即使你配了数据库参数,session.driver 没显式设成 database,它就完全不走数据库逻辑。
实操建议:
立即学习“PHP免费学习笔记(深入)”;
- 确认
config/session.php中'type' => 'database'(TP6)或'driver' => 'database'(TP5.1+),不能只配'table'或'connection' - TP6 要求必须启用
session.auto_start,且session.type必须是字符串'database',写成Database::class会静默失败 - 检查
think_session表结构是否含id(主键,varchar(255))、data(mediumtext)、expire(int(11)),字段名和类型错一个,Database驱动就会跳过写入、也不报错
自定义驱动类里 read() 返回空字符串导致登录态丢失
这是最常踩的坑:自定义驱动继承 think\session\driver\Database 后,重写 read($id) 时忘了处理过期逻辑,查出过期记录却仍返回 $data,框架后续不会自动清理,下次 write() 又覆盖旧数据,表面看“能读”,实际 session 已失效。
实操建议:
立即学习“PHP免费学习笔记(深入)”;
-
read($id)中必须先查SELECT data FROM think_session WHERE id = ? AND expire > ?,用time()对比expire字段 - 查不到或已过期,必须返回空字符串
''(不是null,不是false),否则框架认为 session 有效 - 别在
read()里手动删过期记录——那是gc()的事;但如果你重写了gc(),记得返回实际删除条数,否则 TP 可能误判回收失败
TP5.1 升级 TP6 后 Database 驱动报 Call to undefined method think\db\Query::value()
TP6 的 think\session\driver\Database 内部直接调用了 Query::value(),而 TP5.1 的 Query 类没有这个方法,升级后若残留旧驱动或手动 new 了 TP5.1 的 Query 实例,就会崩在这里。
实操建议:
立即学习“PHP免费学习笔记(深入)”;
- 彻底删掉项目里所有自定义的
Database驱动类,改用 TP6 自带的think\session\driver\Database - 确保
composer.json中"topthink/framework": "^6.0"且已执行composer update - 如果必须兼容老代码,不要继承旧驱动,而是复制 TP6 的
Database.php到本地,替换掉所有->value()为->find()['field_name'] ?? ''
高并发下 write() 覆盖导致购物车数据错乱
数据库 Session 默认没加行锁,两个请求同时 read() 同一 id,各自修改后 write(),后写的会完全覆盖前写的,比如 A 加商品、B 改地址,最终只剩 B 的结果。
实操建议:
立即学习“PHP免费学习笔记(深入)”;
- 在
write($id, $data)中改用INSERT ... ON DUPLICATE KEY UPDATE,避免先查后更新的竞态 - TP6 可直接在配置里加
'use_transaction' => true,驱动会自动包事务(但要求表引擎是 InnoDB) - 更稳妥的做法是:把高频变更的业务数据(如购物车)移出 Session,改用 Redis + 用户 ID 做独立缓存,Session 只存登录态等低频字段
Session 数据库驱动真正的复杂点不在配置,而在过期判断时机、并发写入控制、以及 TP 版本间 Query 接口的隐性断裂——这些地方不报错,但一上线就丢数据。



















