【问题标题】:PostgreSQL/JDBC and TIMESTAMP vs. TIMESTAMPTZPostgreSQL/JDBC 和 TIMESTAMP 与 TIMESTAMPTZ
【发布时间】:2014-05-20 06:29:58
【问题描述】:

我最近在使用 JPA 处理时间戳时遇到了很多痛苦。我发现我的很多问题都通过使用 TIMESTAMPTZ 而不是 TIMESTAMP 来解决。我的服务器使用 UTC,而我的 JVM 使用 PST。使用 TIMESTAMP WITHOUT TIMEZONE 时,JPA 似乎几乎不可能对数据库中的 UTC 值进行规范化。

对我来说,我将这些字段用于“用户何时创建”、“他们上次使用设备的时间”、“他们最后一次收到警报的时间”等内容。这些通常是事件,因此他们是时间类型的值的实例。而且因为他们现在将通过 TIMESTAMPTZ 如果我不希望他们使用 UTC,我可以随时向他们查询特定区域。

所以我的问题是,对于 Java/JPA/PostgreSQL 服务器,我什么时候想使用 TIMESTAMP 而不是 TIMESTAMPTZ?它的用例是什么?现在我很难理解我为什么要使用 TIMESTAMP,因此我担心我没有掌握它的价值。

【问题讨论】:

标签: postgresql jpa jdbc


【解决方案1】:

您可以使用它来表示 Joda-Time 和新的 Java 8 时间 API 调用的 LocalDateTimeLocalDateTime 并不代表时间线上的精确点。它只是一组字段,从年到纳秒。它是“对日期的描述,用于生日,结合挂钟上的当地时间”。

例如,您可以使用它来表示您的确切出生日期是 1975 年 7 月 19 日下午 6 点这一事实。或者说,全世界都在 2015 年 1 月 1 日 00:00 庆祝下一个新年。

为了表示精确的时刻,比如阿姆斯特朗在月球上行走的那一刻,带有时区的时间戳确实更合适。无论 JVM 的时区和数据库的时区如何,它都应该返回正确的时刻。

【讨论】:

  • 只是检查我是否理解,而不是纠正你,但听起来即使生日作为 TIMESTAMPTZ 会更好,除非我希望我的生日在世界各地都一样,无论我在哪个时区. 虽然新年显然需要在任何地方都不同,并且不能表示为单个 TIMESTAMPTZ。谢谢你,我想你终于穿透了我厚厚的脑袋。这些东西(尤其是 JPA)让我发疯了。
  • 过年的例子确实清晰一点。但是,假设您喜欢在您出生的准确时间(下午 6 点)庆祝您的生日。如果你搬到另一个时区,你可能会继续在下午 6 点庆祝(特别是如果考虑到时间变化迫使你在凌晨 4 点庆祝它:-))。
  • 唯一让我大吃一惊的是,如果我有一个带有 TIMESTAMP 字段的表,该字段的默认值为 CURRENT_TIMESTAMP 插入其中(没有设置该字段)会给我 JVM 区域,但如果我从 PSQL 发出相同的插入语句,我会得到 Postgres 区域。无论哪种方式 TIMESTAMPTZ 也解决了这个问题。
  • 我对上述行为的工作理论。 JDBC 驱动程序以某种方式设置会话的时区。见stackoverflow.com/questions/22850236/…
  • @user1888440 PgJDBC 确实将TimeZone 设置为UTC。按设计。
【解决方案2】:

一般使用TIMESTAMPTZ

以下是 Postgres 专家 David E. Wheeler 在一篇博文中的建议,其标题说明了一切:
Always Use TIMESTAMP WITH TIME ZONE (TIMESTAMPTZ)

如果您要跟踪实际时刻、时间线上的特定点,请使用TIMESTAMP WITH TIME ZONE

一个例外:分区

Wheeler 的唯一例外是在时间戳分区时,因为技术限制。对于我们大多数人来说,这是一个罕见的例外

有关分区的信息,请参阅docWiki

用词不当

数据类型名称timestamp with time zonetimestamp without time zone 用词不当。在这两种情况中,日期时间值以 UTC 格式存储(无时区偏移)。再读一遍前面那句话。 UTC,始终。 “with time zone” 短语的意思是“注意时区”,而不是“将时区存储在此值旁边”。类型之间的区别在于是否应在存储(INSERT 或 UPDATE)或检索(SELECT 查询)期间应用任何时区。 (这种行为针对 Postgres 进行了描述——其他数据库在这方面差异很大。)

更准确地说,应该说 TIMESTAMP WITHOUT TIME ZONE 存储没有时区的日期时间值。但如果没有任何时间框架参考,任何查看该数据的人都必须假设(希望,祈祷?)这些值是 UTC。但同样,没有实际意义,因为您几乎不应该使用这种类型。

仔细阅读the doc,并进行一些实验以阐明您的理解。

未分区

如果您想存储可能时间的一般概念而不是特定时刻,请使用另一种类型,TIMESTAMP WITHOUT TIME ZONE

例如,今年圣诞节从 2017 年 12 月 25 日的第一刻开始。那将是 2017-12-25T 00:00:00,没有时区指示,也没有与 UTC 的偏移。这个值只是关于可能时刻的一个模糊概念。在我们应用时区(或偏移量)之前,它没有任何意义。所以我们使用TIMESTAMP WITHOUT TIME ZONE 存储它。

为圣诞老人的特殊活动后勤部门工作的精灵们将时区作为他们计划过程的一部分。目前最早的时区是Pacific/Kiribati,比UTC 早14 小时。精灵们安排圣诞老人第一次到达那里。精灵们安排了一个飞行计划,将驯鹿带到其他时区,午夜过后不久,例如Pacific/Auckland。随着每个区域的午夜到来,他们继续向西行进。 Asia/Kolkata 几小时后,Europe/Paris 更晚,America/Montreal 更晚,等等。

精灵们会使用WITH TIME ZONE 记录每个具体的送达时刻,而圣诞节的一般概念将存储为WITHOUT TIME ZONE

WITHOUT TIME ZONE 在商业应用程序中的另一个用途是安排比几周更远的约会。世界各地的政客们莫名其妙地偏爱搞乱时钟和重新定义时区规则。他们加入夏令时 (DST),离开 DST,在不同的日期开始 DST,或在不同的日期结束 DST,或者将时钟移动 15 分钟或半小时。所有这些都是土耳其、美国、俄罗斯、委内瑞拉和其他国家在过去几年中完成的。

政客们经常在没有预先警告的情况下做出这些改变。因此,如果您在 13:00 安排牙科预约六个月,则可能应存储为TIMESTAMP WITHOUT TIME ZONE,否则政客可能会将您的预约有效地更改为中午、下午 2 点或 13:30。

【讨论】:

    【解决方案3】:

    更新上述答案:由于修剪,分区不再是 PG11 中的例外情况。

    https://www.postgresql.org/docs/11/ddl-partitioning.html#DDL-PARTITION-PRUNING

    个人成功测试了针对 PG11 AWS RDS 的查询。官方 PG wiki 还指出使用 timestamp without timezone 是一个坏主意:

    https://wiki.postgresql.org/wiki/Don%27t_Do_This#Don.27t_use_timestamp_.28without_time_zone.29_to_store_UTC_times

    【讨论】:

      【解决方案4】:

      使用 Java 8 日期和时间 API,我不会盲目地跳入 timestamptz 阵营。

      如果您映射timestamp LocalDateTime,无论Java 应用程序的默认时区如何,您总是会得到相同的值。无论您在SELECT/INSERT 之间放置了多少个调用TimeZone.setDefault(TimeZone.getTimeZone("TZ")) 混合不同的TZ,您都将在Java 中随时获得相同的LocalDateTime,并且日期/时间组件将与Postgresql TO_CHAR(ts, 'YYYY-MM-DD HH24:MI:SS') 中的相同。

      如果您映射 timestamptz LocalDateTime Postgresql JDBC 驱动程序(支持 JDBC 4.2 规范)在将值保存到数据库时使用默认 Java 时区将 LocalDateTime 转换为 UTC。如果将其保存在一个默认 TZ 中并在另一个中读取,则会得到不同的“本地”结果。

      飞机起飞时间是机场当地的时间。如果您不需要比较不同城市timestamptz 和UTC 之间的出发时间没有意义,您只需在机票上打印准确的城市当地时间。使用timestamp,可以保持日期/时间不变,避免由于 Java 应用默认 TZ + 城市特定 TZ(业务逻辑)导致的双重 TZ 校正。

      timestamptz 在 SQL 中大量转换 TZ 时很有用。只有timestamp 你写:

      date_trunc('day', x.datecol AT TIME ZONE 'UTC' AT TIME ZONE x.timezone)
        AT TIME ZONE x.timezone AT TIME ZONE 'UTC'
      

      虽然timestamptz 没有必要提到时间是UTC(如果你遵循这样的约定,也许你应该xD):

      date_trunc('day', x.datecol AT TIME ZONE x.timezone)
        AT TIME ZONE x.timezone
      

      操作员AT TIME ZONE 过载:

      • timestamp AT TIME ZONE 'X' => timestamptz
      • timestamptz AT TIME ZONE 'X' => timestamp

      Postgresql JDBC + Java 8 date&time API spec.

      【讨论】:

      • timestamptz 应该映射到 OffsetDateTime im Java,而不是 LocalDateTime
      • @a_horse_with_no_name 你是对的。我正在阅读规范,表 B.5 提到了这一点。我必须重新思考我对 JDBC 的了解。
      • SQL timestamp 和 Java LocalDateTime 都不能用作时间戳。他们的解释因时区而异。对于记录时间点,推荐timestamptzOffsetDateTime,无需任何保留。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2020-02-01
      • 2018-11-03
      • 2015-02-18
      • 2012-12-03
      • 1970-01-01
      • 2017-05-30
      • 2013-12-15
      相关资源
      最近更新 更多