MySQL连接层必须显式设置time_zone为'+08:00',PHP时区设置不影响MySQL的NOW()等函数;ThinkPHP 6需在每个数据库连接的options中配置PDO::MYSQL_ATTR_INIT_COMMAND,且CLI与Web环境需分别验证生效。

MySQL 连接层必须显式设置 time_zone
PHP 时区设成 Asia/Shanghai 只影响 PHP 自身的时间函数,对 MySQL 执行的 NOW()、CURDATE() 等毫无作用。数据库连接建立后,MySQL 会按自己的时区解释时间字面量——若服务端时区是 SYSTEM 但系统实际是 UTC,那 NOW() 就比北京时间慢 8 小时。
ThinkPHP 6 不自动帮你设 MySQL 时区,必须手动注入初始化命令:
- 在
config/database.php的对应连接配置(如connections.mysql)里加'options' => [PDO::MYSQL_ATTR_INIT_COMMAND => "SET time_zone = '+08:00'"] - 不要写
SET time_zone = 'Asia/Shanghai':MySQL 对时区名支持有限,部分版本不识别,+08:00最稳 - 该配置只对当前连接生效,换库(
Db::connect('slave_db'))也得单独配一遍
Db::raw() 或原生查询中调用 NOW() 仍可能出错
即使连接层设了 time_zone,你在 Db::raw('NOW()') 或 ->whereTime('create_time', '>=', Db::raw('NOW() - INTERVAL 1 DAY')) 里用 NOW(),结果仍取决于 MySQL 当前 session 时区——而这个 session 时区可能被中间件、触发器或别的 SQL 覆盖。
更可控的做法是把时间计算移回 PHP 层:
立即学习“PHP免费学习笔记(深入)”;
- 用
date('Y-m-d H:i:s', strtotime('-1 day'))生成字符串再传入where - 或统一用 Unix 时间戳字段(
int类型),查create_time >= 1746572400,彻底避开格式化与解析歧义 - 避免混用:别一边用
Db::raw('NOW()'),一边又用模型的createTime自动写入,二者来源时区逻辑不同
多数据库连接时,每个连接都得独立配时区
ThinkPHP 6 的 connections 是扁平结构,mysql 和 slave_db 完全隔离。你给 mysql 配了 time_zone,slave_db 依然走 MySQL 默认,很可能还是 UTC。
必须逐个连接补上:
- 检查每个连接数组(如
connections.admin、connections.user)是否都有'options' => [...]段 - CLI 命令(如
php think command)走的也是默认连接,如果它连的是admin库,就得确保admin连接里也写了时区初始化 - 别依赖
default配置:它只是指定哪个连接名被当默认,不继承参数;default设为'admin',不代表user连接也自动获得同样配置
验证是否真生效,不能只看 PHP 输出
很多人在控制器里 dump(date('Y-m-d H:i:s')) 看到北京时间就以为搞定了,其实 MySQL 层可能还差着 8 小时。
分三步实测:
- 执行
Db::query("SELECT @@session.time_zone, NOW(), UNIX_TIMESTAMP(NOW())"),确认第一列返回+08:00,第二列是当前北京时间,第三列和time()接近 - 插入一条记录,立刻查数据库原始字段值(不是模型
toArray()后的结果),对比插入时刻 - 开两个终端:一个跑 Web 请求,一个连 MySQL 命令行执行
SELECT NOW(),看两者是否一致——不一致说明连接层没生效或被覆盖
最易忽略的是 CLI 环境和 Web 环境用了不同 php.ini 或不同数据库连接配置,导致时区表现不一致。



















