bigywater.tech
Elasticsearch · 10

date、date_nanos 与时间格式解析

真实对比date和date_nanos两种字段类型,用script_fields揭穿_source原文回显掩盖的真相——date类型真实把亚毫秒精度悄悄截断了。

_source显示的精度
真实只是原文回显,不代表内部存储
date类型
真实只保留毫秒精度,纳秒部分被截断
date_nanos类型
真实完整保留纳秒,但可表示时间范围更窄

做的事:同一个纳秒精度的时间戳,真实分别写进date和date_nanos字段

代码PUT /date_test {"mappings": {"properties": { "ts_ms": {"type": "date"}, "ts_ns": {"type": "date_nanos"} }}} POST /date_test/_doc/1 { "ts_ms": "2026-10-01T12:00:00.123456789Z", "ts_ns": "2026-10-01T12:00:00.123456789Z" } GET /date_test/_doc/1 # _source看到的是原始字符串吗 GET /date_test/_search # script_fields读取真正存储的精度

验证:_source里看起来两边都没丢精度,但那只是原样回显了写入的字符串

实测 · 本机 Elasticsearch 8.15.0 执行输出
GET /date_test/_doc/1
_source.ts_ms → "2026-10-01T12:00:00.123456789Z"   ← 看起来精度还在
_source.ts_ns → "2026-10-01T12:00:00.123456789Z"   ← 看起来也一样

这是本文认为最容易造成误判的一个真实现象:_source字段真实存的是原始写入的JSON文档本身(一份未经处理的备份),不是字段被索引之后真正的内部表示——不管一个date类型字段内部真实把时间精度处理成了什么样,GET单条文档看到的_source永远真实是写入时的原文,这会让人误以为"数据完整保留了",但这跟这个值真正能被用来排序、聚合、范围查询时的精度,是两件真实不同的事。

验证:用script_fields读取真正存储的值,date真实把纳秒截断成了毫秒

实测 · 本机执行输出
script_fields真实读取到的内部存储值:
ts_ms_epoch (毫秒)  → 1790856000123   ← 对应 .123(纳秒部分被真实截断)
ts_ns_epoch (纳秒部分) → 123456789     ← date_nanos真实完整保留了全部9位
ts_ms格式化输出        → "2026-10-01T12:00:00.123Z"           ← 真实只剩毫秒
ts_ns格式化输出        → "2026-10-01T12:00:00.123456789Z"     ← 真实完整纳秒
这组结果真实揭示了date类型的底层实现:它真实只用一个64位长整数存储"自1970年以来的毫秒数",任何写入时真实带有的亚毫秒精度信息,在转换成这个内部整数的那一刻就真实被截断丢弃了——本文这次写入的.123456789秒,真实只有.123这部分被保留,后面的456789纳秒信息真实永久丢失,不会因为_source里还显示着完整字符串就意味着它参与排序、范围查询、聚合时也是精确的。date_nanos真实是为了解决这个问题专门设计的类型——它底层用的是纳秒级的long,能真实完整保留写入时的全部精度,代价是能表示的时间范范围比date窄得多(date_nanos真实只能表示1970年到2262年左右,而date能表示的范围大得多)。真实需要纳秒级精度排序或者区分(比如高频交易日志、分布式追踪里纳秒级的span排序)的场景,必须显式用date_nanos,默认的date类型真实做不到。

验证:往date字段写一个无法解析的字符串,真实报错里带着具体用的是哪套格式

实测 · 本机执行输出
POST /date_test/_doc/2 {"ts_ms": "not a date"}
→ 400 document_parsing_exception:
  "failed to parse date field [not a date] with format
   [strict_date_optional_time||epoch_millis]"

这条真实报错信息里的strict_date_optional_time||epoch_millis,正是date类型没有显式指定format时真实默认采用的解析规则——||表示"依次尝试这几种格式,只要有一种能解析成功就算数",strict_date_optional_time对应标准的ISO8601日期时间字符串,epoch_millis对应直接传一个代表毫秒数的数字。"not a date"真实两种格式都解析不了,ES在写入这一刻就真实拒绝了整条文档,而不是把它当成一个普通字符串存进去——这跟本系列上一篇验证的"显式Mapping会在写入时强制校验类型"是同一套机制在日期类型上的具体体现。

分步演示:从_source的假象到script_fields揭穿的真实精度

每一步都是本文上面在本机真实的Elasticsearch 8.15.0上跑出来的原始结果。

_source的原文回显掩盖了date字段真实的精度截断
这一步新验证的字段

    本文用到的工具

    • 本机真实部署的 Elasticsearch 8.15.0(单节点,tar包直接启动)
    • 真实的 script_fields 与 Painless 脚本读取内部存储值
    京ICP备2026067502号-1京公网安备11010502063487号