PHP和MySQL时间差8小时的根本原因是Linux系统、PHP运行时、MySQL会话/全局时区三者未对齐;必须同步设置Asia/Shanghai时区,并在数据库连接层显式执行SET time_zone='+08:00',同时推荐统一用UTC存储TIMESTAMP。

PHP 和 MySQL 时间差 8 小时,根本不是某一行代码写错了,而是三套时间系统没对齐:Linux 系统时区、PHP 运行时区、MySQL 会话/全局时区。只改 date_default_timezone_set() 或只调 NOW(),治标不治本。
PHP 层必须显式设置时区,不能依赖系统默认
PHP 5.1+ 默认不设时区,date()、strtotime()、DateTime 全部按 UTC 解析和输出。即使服务器 date 命令显示的是北京时间,PHP 也看不到。
- 优先在项目入口(如
index.php或公共配置文件)顶部加:date_default_timezone_set('Asia/Shanghai'); - 避免用
PRC—— 它是旧别名,部分 PHP 版本已弃用,Asia/Shanghai是 IANA 标准且兼容性最好 - 验证是否生效:
echo date_default_timezone_get();必须输出Asia/Shanghai,而不是UTC或空字符串 - 不要在每个函数调用前重复设置,更不要用
time() + 8*3600手动加秒——这绕过时区逻辑,后续换时区会崩
MySQL 的 time_zone 必须和 PHP 对齐
MySQL 有两个关键时区变量:system_time_zone(系统启动时读取的 OS 时区,只读)和 time_zone(会话级/全局级,决定 NOW()、CURDATE() 等函数行为)。两者常不一致。
- 查当前会话时区:
SELECT @@session.time_zone;—— 如果返回SYSTEM,它就跟着system_time_zone走;如果返回+00:00或UTC,那就是问题根源 - 临时修复(仅当前连接):
SET time_zone = '+08:00';或SET time_zone = 'Asia/Shanghai'; - 永久生效(需有 SUPER 权限):
SET GLOBAL time_zone = '+08:00';,再确认SELECT @@global.time_zone; - 注意:
TIMESTAMP类型字段会自动按time_zone转换存取,DATETIME不会——业务中若混用,时间值含义就乱了
数据库连接层要主动同步时区
PHP 连接 MySQL 后,默认不会把 PHP 的时区“告诉”数据库。PDO 和 mysqli 都需要手动执行一次时区设置语句,否则 NOW() 仍可能走错路。
立即学习“PHP免费学习笔记(深入)”;
- PDO 初始化后立即执行:
$pdo->exec("SET time_zone = '+08:00'"); - mysqli 初始化后:
$mysqli->query("SET time_zone = '+08:00'"); - 如果用 Laravel / ThinkPHP 等框架,检查数据库配置里是否有
timezone选项(如 Laravel 的config/database.php中'options' => [PDO::MYSQL_ATTR_INIT_COMMAND => "SET time_zone = '+08:00'"]) - 不要依赖连接池自动继承——不同连接可能来自不同进程,
time_zone状态不共享
统一时间存储策略比修时区更重要
就算 PHP 和 MySQL 都设成 Asia/Shanghai,一旦系统未来要支持海外用户或做跨时区调度,硬编码本地时间就会反噬。
- 数据库一律用
TIMESTAMP类型 +time_zone = '+00:00',所有写入都转为 UTC 存储 - PHP 写入前用
(new DateTime())->setTimezone(new DateTimeZone('UTC'))->format('Y-m-d H:i:s')显式转 UTC - 读取时再按用户所在时区转换展示,而不是让数据库“猜”用户在哪
- 日志、定时任务、支付回调验签等关键路径,全部以 UTC 时间戳(
time())为唯一可信源,避免任何字符串时间参与逻辑判断
真正容易被忽略的点是:MySQL 的 time_zone 可能在连接建立后被中间件、监控探针或某些 ORM 自动重置。上线后得用真实请求链路抓包验证,不能只靠命令行 mysql -e "SELECT NOW()" 测。



















