Laravel查询MySQL8时created_at时间与数据库存储值不一致问题
问题核心原理
这个2小时偏差本质是三个链路环节的时区配置没有对齐,不是框架偷偷修改了时间值:
- 你在
config/app.php里设置的Europe/France时区,仅对Laravel框架本身生效:包括Carbon实例的默认时区、PHP内置时间函数的默认时区,这个配置不会自动同步给MySQL连接。 - 你没有给MySQL连接单独配置时区,打开
config/database.php查看connections.mysql的默认配置,是没有timezone项的。这时候Laravel连接MySQL时不会主动设置会话时区,会直接使用MySQL服务端的默认时区。从你返回的时间带Z后缀(UTC时区标识)可以判断,当前连接会话实际运行在+00:00UTC时区上。
偏移来源可以结合字段类型对应:- 如果
created_at是TIMESTAMP类型:MySQL本身自带时区转换逻辑——存值时会把传入的时间从当前会话时区转成UTC存在底层,取值时再从UTC转回会话时区返回。你当初存储2022-06-25 07:44:43时,会话时区是法国夏令时(UTC+2),MySQL底层实际存的UTC时间就是2022-06-25 05:44:43,现在用UTC时区的会话读取,自然直接返回05:44,和你看到的返回值完全匹配。 - 如果
created_at是DATETIME类型:MySQL不会做任何时区转换,存什么值就返回什么值,但Laravel拿到返回的时间字符串后,会默认按连接时区(UTC)解析成Carbon实例,序列化输出时就按UTC格式输出,同样会产生2小时偏差。
- 如果
- Laravel默认的JSON序列化规则,会把所有Carbon时间对象转成带
Z后缀的UTC标准ISO8601格式,不会自动转成app.php里配置的本地时区格式,这也是你直接拿接口返回值看不到和数据库存储一致字符串的直接原因。
你写的自定义访问器能生效,本质是绕开了框架默认的时区解析逻辑:先把拿到的时间字符串转成无时区属性的时间戳,再手动切到配置的应用时区格式化输出,相当于手动把偏移量校正回来。
更稳妥的修复方案
自定义访问器属于补丁式修复,后续写时间范围查询、whereDate筛选的时候很容易因为时区隐式转换踩坑,正确做法是对齐全链路时区:
- 对齐数据库连接时区
在config/database.php的mysql连接配置块里添加timezone项,和应用时区保持一致,直接写命名时区可以自动处理法国的冬夏令时切换:
如果你的MySQL没有导入命名时区表,也可以根据时令写固定偏移,夏令时填'mysql' => [ // 保留原有其他配置不动 'timezone' => 'Europe/Paris', ],+02:00、冬令时填+01:00。 - 统一时间序列化格式(可选)
如果你不需要接口返回UTC标准格式的时间,希望所有时间输出都和数据库存储的YYYY-MM-DD HH:MM:SS格式一致,不需要给每个模型写重复访问器,直接在app/Providers/AppServiceProvider.php的boot方法里加全局配置即可:use Illuminate\Support\Carbon; public function boot() { Carbon::serializeUsing(function ($carbon) { return $carbon->toDateTimeString(); }); }
内容的提问来源于stack exchange,提问作者Under Voltage
相关产品推荐
相关产品推荐

