PAYATHON 2026

为什么 created_at(服务器时间)有时早于 clicked_at(客户端时间)?

数据小陈

客户端和服务器使用各自的系统时钟。即使点击先发生,记录中也不一定满足 clicked_at <= created_at。只要客户端时钟比服务器快一些,就可能出现示例中的结果。

处理这类数据时,建议同时保留客户端事件时间和服务器接收时间,统一存储格式与精度,再根据业务目的选择时间依据。统一时区无法消除两台设备之间的时钟偏差。

为什么点击时间会晚于服务器时间?

Date.now() 返回客户端系统时钟对应的 Unix 时间戳,单位为毫秒。PHP 的 date() 使用服务器系统时钟,按照服务器配置的时区格式化输出。

假设客户端时钟比服务器快 2 秒,请求经过 200 毫秒到达服务器,那么服务器记录的接收时间仍会比客户端记录的点击时间早约 1.8 秒。事件的实际顺序没有改变,两个时钟的读数却倒置了。

两者的关系可以表示为:

服务器接收时间 - 客户端点击时间
≈ 传输与发送等待时间 - 客户端相对服务器的时钟偏差

偏差为正表示客户端时钟较快。客户端手动修改时间、设备时钟同步不准确或服务器时钟存在偏差,都可能影响结果。

navigator.sendBeacon() 和 fetch() 负责传输数据,不会同步两端时钟。发送排队、网络传输和服务端处理会增加事件发生到记录之间的耗时,但无法消除时钟偏差。

Asia/Kolkata 和秒级格式是否是原因?

Date.now() 返回的时间戳数值本身不带客户端时区。原代码在相同的 PHP 时区配置下用 date() 格式化两个时间,因此将服务器时区设为 Asia/Kolkata 本身不会造成这种先后倒置。

如果数据库、其他写入程序或读取程序使用了不同的时区,则需要检查时区转换是否一致。

原代码还把毫秒级时间格式化为:

Y-m-d H:i:s

这样会丢失毫秒信息,无法准确分析较短的时间差。不过,在时钟一致且转换规则相同的前提下,丢失毫秒本身不会反转原本正确的先后顺序。

应该信任哪个时间?

两个字段表达的含义不同:

  • clicked_at:客户端报告的点击时间,适合描述用户端事件发生的时刻,但可能存在时钟偏差,也可能被修改。
  • received_at:服务器在请求处理入口处采样的时间,适合表示服务器何时观察到事件。
  • created_at:如果需要表示数据库创建时间,应明确它是在应用代码中生成,还是由数据库写入时生成。

当前代码中的 created_at = date(...) 记录的是执行这行 PHP 的时间,不等同于数据库实际完成插入的时间。

对于服务端接收顺序、审计记录等用途,通常以受控且已同步的服务器时间为依据。做用户行为分析时,可以保留客户端时间,同时标明它是客户端上报值。跨多台服务器比较时,还需要保证服务器之间的时钟同步。

如何保存和统一时间?

建议保存原始 Unix 毫秒时间戳,或使用明确支持毫秒精度的数据库时间类型。如果采用时间戳,两个字段都应使用毫秒;如果采用日期时间字段,应统一按 UTC 写入和读取,并确认数据库字段及连接配置的时区行为。

以下 PHP 示例假设请求体已解析到 $payload,运行环境使用 64 位整数。应在请求处理入口附近采样服务器时间:

// 服务器开始处理请求时采样。
$received_at_ms = (int) floor(microtime(true) * 1000);

// 假设 $payload 是已解析的请求数据。
$clicked_at_ms = filter_var(
    $payload['clicked_at'] ?? null,
    FILTER_VALIDATE_INT
);

if ($clicked_at_ms === false) {
    $clicked_at_ms = null;
}

// 将这些值绑定到数据库参数,分别保存。
$event = [
    'clicked_at_ms' => $clicked_at_ms,
    'received_at_ms' => $received_at_ms,
];

数据库字段需要能容纳毫秒时间戳,例如 BIGINT。这里的整数校验只验证输入格式,业务上仍可按需要检查时间是否异常。

展示时再把时间转换为 UTC 或用户所在时区。不要为了满足大小关系,直接把客户端时间改成服务器时间,或强行截断为两者中的较小值;这样会丢失排查时钟异常所需的信息。

是否应该计算延迟后校正?

仅凭一次事件的 clicked_at 和 received_at,无法算出真实网络延迟,因为两者之差同时包含发送等待、网络耗时和时钟偏差。

如果业务确实需要估算客户端相对服务器的时间偏移,可以增加一次专门的时间同步请求。客户端记录发送时间 C0 和收到响应的时间 C1,服务器在响应附近返回时间 S。在往返路径近似对称、服务器处理时间可忽略的前提下,可估算:

服务器相对客户端的时间偏移 ≈ S - (C0 + C1) / 2
校正后的客户端事件时间 ≈ clicked_at + 时间偏移

这只是估计值,网络不对称、处理耗时和设备时钟变化都会引入误差。需要校正时,建议同时保存原始时间、估计偏移和校正时间,避免覆盖原始记录。

如果只需要判断点击顺序或时间间隔

在同一页面内测量点击间隔,可以使用 performance.now()。它适合测量经过的时间,不受系统日期调整的直接影响,但不能直接与服务器 Unix 时间戳比较,也不能作为跨页面共享的绝对时间。

如果要判断同一会话内的事件顺序,可以额外发送递增序号。服务器接收顺序可能受排队、并发和重试影响,仅靠两个来自不同设备的时间字段无法保证还原准确顺序。

对于当前场景,可以先保留毫秒精度的客户端点击时间和服务器接收时间,并检查两端时钟。若只是统计网站点击,通常无需为了让所有记录满足 clicked_at <= created_at 而校正数据。

备注:内容仅供参考。