MySQL慢查询日志(slow_query_log_file)中# Time:行的时间戳默认为UTC,但可通过log_timestamps=SYSTEM设为服务器本地时区;该参数不影响写入mysql.slow_log表的时间戳,后者始终为系统本地时间,且不带毫秒。

MySQL慢查询日志里的时间戳是本地时区还是UTC?
MySQL慢查询日志(slow_query_log_file)中记录的# Time:行,用的是服务器系统当前时区(system_time_zone),不是UTC,也不是PHP设置的date.timezone。这意味着如果你的DB服务器在CST(UTC+8),而PHP应用部署在UTC服务器上,两者时间戳对不上——不是bug,是设计如此。
验证方式很简单:
mysql> SELECT @@global.time_zone, @@global.system_time_zone;
常见坑:
- 误以为
# Time: 2024-05-12T14:30:22是UTC,结果发现比应用日志晚8小时 - 用
date_default_timezone_set('UTC')改PHP时区,但没同步改MySQL系统时区,导致排查时“时间对不上”卡半天 - 容器化部署时,MySQL容器未挂载宿主机
/etc/localtime,导致时区为UTC,而宿主机是CST
PHP如何给SQL打上可追踪的时间戳?
不能依赖MySQL日志里的# Time:做精确归因,因为它是语句执行完成后的系统时间,且不带毫秒、不关联请求上下文。更可靠的做法是在PHP层主动记录:执行前打点、执行后计算耗时、拼入唯一请求ID。
立即学习“PHP免费学习笔记(深入)”;
关键实操点:
- 用
microtime(true)获取浮点秒级时间戳(精度~微秒),别用time()(秒级,慢查询阈值常设1s,根本分不出先后) - 把
$_SERVER['REQUEST_ID']或uniqid('', true)作为trace_id注入到日志行,方便和Nginx/PHP-FPM日志联动 - PDO执行前记一次
$start = microtime(true),execute()后立刻算$cost = microtime(true) - $start,再写入自定义慢SQL日志 - 避免在
catch块里才记录——超时异常可能根本进不去catch(如MySQL wait_timeout中断)
示例片段:
$start = microtime(true);
$stmt = $pdo->prepare("SELECT * FROM orders WHERE user_id = ?");
$stmt->execute([$uid]);
$cost = microtime(true) - $start;
if ($cost > 1.0) {
error_log(sprintf("[SLOW SQL] %s | %.3fs | %s", $stmt->queryString, $cost, $_SERVER['REQUEST_ID'] ?? 'noid'));
}为什么PDO::ATTR_EMULATE_PREPARES=true会让慢查询时间失真?
开启模拟预处理(PDO::ATTR_EMULATE_PREPARES = true)时,PHP在本地解析SQL、拼接参数、再发完整SQL给MySQL。此时microtime测得的“执行耗时”,实际包含了字符串拼接、引号转义、类型转换等PHP开销,并非真实数据库执行时间。
真实场景影响:
- 一条含多个
IN(?)的查询,参数数组有500个元素——PHP拼SQL就花了300ms,但MySQL执行只用了20ms;日志显示“慢SQL 320ms”,误导你去优化索引,其实该优化的是PHP侧参数处理 - 开启
emulate_prepared后,MySQL的SHOW PROCESSLIST看不到这条语句,因为根本没走prepare协议,查不到执行中的状态 - 某些字符集(如gbk)下,PHP模拟转义可能出错,导致SQL语法错误,错误时间也被计入“执行耗时”
建议:生产环境一律设PDO::ATTR_EMULATE_PREPARES = false,并确保MySQL支持对应prepared statement协议版本(5.6+基本无问题)。
log_output=TABLE时,slow_log表里的start_time字段怎么和PHP对齐?
当MySQL配置log_output='TABLE',慢查询会写入mysql.slow_log表,其中start_time字段是DATETIME类型,值为服务器本地时间(同# Time:行)。它和PHP里date('Y-m-d H:i:s')输出格式一致,但要注意两点:
-
start_time没有毫秒,而PHP用microtime(true)能拿到毫秒,直接字符串比对会丢失精度;建议PHP侧也用date('Y-m-d H:i:s')截断后比对 - 如果MySQL开启了
log_slow_admin_statements,slow_log里会出现ALTER TABLE等管理语句,它们的start_time和业务请求完全无关,过滤时要加sql_text NOT LIKE 'ALTER%'等条件 -
slow_log表默认用MyISAM引擎,高并发写入可能锁表;若需实时分析,建议定期导出到InnoDB表或外部ES
查最近10条慢SQL并关联PHP trace_id(假设你已将trace_id写入sql_text注释):
SELECT start_time, sql_text FROM mysql.slow_log WHERE sql_text LIKE '%/* TRACE_ID:%' ORDER BY start_time DESC LIMIT 10;
时间戳对齐的本质不是“统一用UTC”,而是明确每个环节的时区来源和精度边界。MySQL日志、PHP microtime、Nginx $time_iso8601三者只要标注清楚来源,就能串起来。最容易被忽略的是:PDO模拟预处理带来的耗时污染,以及slow_log表的存储引擎对查询延迟的反向影响。



















