【问题标题】:Leap second handling in database数据库中的闰秒处理
【发布时间】:2013-11-14 02:11:34
【问题描述】:

作为:

The Unix time number is zero at the Unix epoch, and increases by exactly 86400
per day since the epoch. So it cannot represent leap seconds. The OS will slow 
down the clock to accomodate for this. 

那么,如果我将 Unix 纪元(例如 ts)存储在 DB(毫秒精度)中,如何处理以下情况?

  1. 如何确保 ts 始终在增加且不向后?
  2. 如何从 db 中准确选择考虑闰秒的 100s 间隔?

例如

SELECT * FROM events WHERE ts >= T1 and ts < T1 + 100

上面的 SQL 将返回发生在 T1, T1+1, T1+2, .. 直到 T1+99 的事件,但是由于闰秒,包含 1s 的闰时间,结果可能是错误的,如何考虑到这一点?

【问题讨论】:

    标签: unix timezone jodatime unix-timestamp


    【解决方案1】:

    来自Joda Time FAQ

    是否支持闰秒?

    Joda-Time 不支持闰秒。可以通过编写一个新的来支持闰秒, 专门的年表,或对现有的ZonedChronology 进行一些改进 班级。在任何一种情况下,未来版本的 Joda-Time 都不会默认启用闰秒。 大多数应用程序都不需要它,而且它可能会产生额外的性能成本。

    来自 IANA/Olson TZDB file on leap seconds

    虽然定义中也包含了掉秒的可能性 (“负”闰秒),这从未做过并且不太可能是必要的 在可预见的未来。

    你的第一个问题:

    如何确保 ts 一直在增加而不是向后?

    一个负闰秒将使您处于相同的时间戳(两个经过的秒的一个值),因此如果没有 两个 负闰秒,您将无法真正向后退。由于似乎不太可能出现负闰秒,所以我认为这是您永远不会真正遇到的问题。

    更新:我可以设想时间戳倒退的唯一方法是,如果您使用毫秒精度并遇到下面的行为 #3。

    你的第二个问题:

    如何从 db 中准确选择考虑闰秒的 100s 间隔?

    由于您使用 UTC 记录时间,因此您的值已经包含闰秒。我知道这听起来可能违反直觉,因为正如您所描述的那样,一天中正好有 86400 秒(86400000 毫秒)。但他们确实在那里。如果他们不是 - 那么我们将与 TAI 同步,而不是 UTC。那怎么可能呢?好吧,当闰秒发生时,可能会发生一些不同的事情:

    1. 如果操作系统和应用程序代码都支持闰秒,那么它确实可以将显示秒显示为:60:61。但是几乎没有真正的实现,因为编程语言通常只允许几秒钟的时间去:59

    2. 操作系统可能会“冻结”一秒钟,并在一整秒内给出相同的值。

    3. 操作系统可能会前进到 :59.999,然后跳回到 :59.000 以重复闰秒所覆盖的时间段。 (感谢@Teo)

    4. 操作系统可能会“漂移”或“拖尾”一段时间,一次缓慢地将系统时钟增加几毫秒,直到它完全赶上额外的一秒。

      李>
    5. 操作系统可能会直接跳过它并且什么也不做。您的时钟将不同步,直到下一次通过 NTP 同步。如果它恰好在闰秒的那一刻同步,它可能只是将时间设置为:59:00 并再次不同步一段时间。

    让我们考虑一个真实的例子。值 1341100800000 代表 2012 年 7 月 1 日,恰好在 UTC 午夜。 (您可以在 this web site 或在您的 Java 或 Joda 时间代码中检查它。以验证。)如果我们除以 86400000,我们将得到 准确自 15522 天以来1970 年 1 月 1 日 UTC。

    此值包含 35 个闰秒,包括在 2012 年 6 月 30 日结束前一秒发生的闰秒。就好像闰秒根本没有发生。

    所以大多数时候,您无需担心闰秒。假装它们不存在。让您的操作系统随心所欲地处理它们。

    如果您需要超精确的时间测量,也许是在科学环境中,那么您无论如何都不应该使用计算机的系统时钟。除了可以保持、延长或忽略闰秒这一事实之外,它的设计并不是为了像计时器那样精确。相反,您可能应该使用一些非常专业的计时硬件,例如 this vendor 提供的那些。

    更新:您可能需要处理闰秒的地方是您正在快速记录事件(每秒有很多事件),并且您的操作系统是否具有上述 #3 中描述的行为。在这种情况下,我的建议是不要按时间戳排序,而是考虑保留一个单独的单调递增序列号,然后按此排序。

    例如,您的数据库中可能已经有一个自动递增的整数 ID。您仍然可以在 where 子句中按时间戳 过滤 以获取特定日期的数据,但随后您将按 ID 进行排序,以便事件按顺序排列,即使时间戳不是。

    有关其他建议,请参阅Teo's answer

    【讨论】:

    • 如果您想要跨大陆同步某些功能并且您正在处理从不同位置检索的 Unix 时间戳怎么办?如果时间戳顺序无论出于何种原因很重要,我认为你不能忽略它。
    • @Teo - Unix 时间戳始终采用 UTC。当闰秒确实发生时,它们都在全球完全相同的 UTC 时间发生。因此,如果您处理的是 Unix 时间戳(或任何其他类型的 UTC 或 GMT),那么位置就无关紧要了。
    • 是的,但是如果您需要在这些多个位置之间进行同步怎么办?一个位置可能会发送 x 毫秒的时间戳以插入数据库,而另一个位置可能会发送 x+500。然而,如果你在那个闰秒内,难道不是 x+500 发生在 x 之前吗?此处对此进行了描述:en.wikipedia.org/wiki/Unix_time#Encoding_time_as_a_number
    • @Teo - 非常感谢那篇文章。我明白你现在所描述的了。请查看我的更新,让我知道您的想法! :)
    【解决方案2】:

    我首先要说我在现实生活中没有遇到过这样的问题,所以我只会猜测,但这将是一个有根据的猜测。根据http://en.wikipedia.org/wiki/Unix_time#Encoding_time_as_a_number,当插入闰秒时,问题是 2 次(例如 1998-12-31T23:59:60.00 和 1999-01-01T00:00:00.00)具有相同的 Unix 时间(915.148.800.000)。删除闰秒应该没有问题。

    根据同一维基百科页面上的注释 #2,闰秒是不可预测的,这给您留下了 2 个选择:一个通用解决方案(假设您有由这些时间戳索引的表)总是可以插入条目,并且在一个条目的时刻发生在最后插入的条目之前(可能在闰秒内),您可以开始一个“拖尾”过程,该过程基本上是向条目添加一些毫秒,以确保它超出闰秒的范围。当插入的条目再次具有比先前插入的条目更大的值时,该过程可以停止。我称其为“涂片”,因为它在某种程度上受到了 Google 的“Leap Smear”技术的启发(虽然不完全相同):http://googleblog.blogspot.in/2011/09/time-technology-and-leaping-seconds.html 我看到它的方式虽然这会给您的数据库带来一些压力,并且插入查询几乎是我见过的最复杂的查询之一(如果它甚至可以单独在 SQL 中使用的话)。

    另一种解决方案是(我假设您使用的是 Java)手动检查时间戳是否在闰秒内。如果是这样,只需阻止对数据库的任何访问并将条目插入队列。当闰秒结束时,只需以先进先出的方式将队列插入数据库以保证您关心的顺序(类似于上面的解决方案,但完全使用 Java,因此甚至在它触及 DB 层之前)。您可以通过消除队列并直接插入数据库来优化这一点 - 只需像上面一样在一秒钟内“涂抹”条目。

    当然,缺点是您在闰秒中牺牲了一点准确性(考虑到闰秒如此罕见,牺牲并不大),但好处是它很简单并且您的订单有保证。

    如果你或其他人找到更好的解决方案,请在这里分享,这个话题很有趣:)

    更新:我已经为第三种解决方案编写了伪代码(完全在 SQL 查询中),它依赖于对闰秒的硬编码检查(比通用解决方案更快)。它可能可以进行很多优化,但只是为了证明我的观点:

    if (newTime is in a leap second){
        read smearCount from db;
        if (smearCount <= 0) {
            smearCount = 1000; // making sure we land outside the leap second
            update smearCount in db;
        }
        newTime += smearCount;
        insert newTime into db;
    } else { // gradually reducing smearCount by 1 millisecond over the following 1000 insertions
        read smearCount from db;
        if (smearCount > 0){
            smearCount -= 1;
            update smearCount in db;
            newTime += smearCount;
        }
        insert newTime into db;
    }
    

    【讨论】:

    • +1 但在 db 层上实现它可能听起来比它更容易(想想著名的 ORA-04091:表 XXXX 正在变异,触发器/函数可能看不到它)
    • 想一想,也许它真的比我想象的要简单。闰秒检查也可以硬编码在 SQL 插入代码中。唯一的问题(这根本不应该是一个问题)是读取(并在数据库本身中增加)一个“拖尾”计数(您人为添加的毫秒数)。
    • @HAL9000:“触发器正在变异”错误通常出现在人们不了解 Oracle 具有不需要在触发表上的任何 DML 的行级触发器时。上面的代码可以毫无问题地更改单行的时间(尽管我不会为此使用触发器 - 处理所有事情的过程会是更好的选择)
    猜你喜欢
    • 2015-09-18
    • 1970-01-01
    • 1970-01-01
    • 2016-07-05
    • 2011-07-16
    • 2015-11-12
    • 1970-01-01
    • 2019-10-30
    • 2021-02-13
    相关资源
    最近更新 更多