PHP & mySQL: Year 2038 Bug: 是什么? 如何解决?

我想用 TIMESTAMP 来存储日期 + 时间,但是我读到它有一个2038年的限制。与其问大量的问题,我更喜欢把它分成小的部分,这样新手用户也很容易理解。所以我的问题是:

  1. 2038年问题到底是什么?
  2. 它为什么会发生,当它发生时会发生什么?
  3. 我们要怎么解决?
  4. 是否有任何可能的替代方案来使用它,这不会造成类似的问题?
  5. 我们可以对使用 TIMESTAMP 的现有应用程序做些什么,以避免在问题真正发生时出现所谓的问题?

先谢谢你。

48347 次浏览

当使用 UNIX 时间戳来存储日期时,实际上使用的是32位整数,它记录了自1970-01-01以来的秒数; 参见 Unix 时间

那个32位的数字将在2038年溢出,这就是2038年的问题。


为了解决这个问题,不能使用32位 UNIX 时间戳来存储日期——这意味着,在使用 MySQL 时,不应该使用 TIMESTAMP,而应该使用 DATETIME(参见 10.3.1日期、日期和时间戳类型) :

使用 DATETIME类型时 需要同时包含日期和 时间信息。支持的范围 是 '1000-01-01 00:00:00''9999-12-31 23:59:59'.

TIMESTAMP数据类型有一个范围 协调世界时 '1970-01-01 00:00:01'至 协调世界时 '2038-01-19 03:14:07'


为了避免/修复这个问题,您可以对应用程序做的最好的事情是不使用 TIMESTAMP,而是使用 DATETIME来处理必须包含不在1970年至2038年之间的日期的列。

不过有一点需要注意: 在2038年之前,你的应用程序很可能已经被重写了很多次,这个值很高。 ^ ^ 所以,如果你将来不需要处理日期,你就不需要处理当前版本的应用程序的问题。

我已经标记为一个社区维基,所以请随意编辑在您的空闲时间。

2038年问题到底是什么?

发言人说: 「这个2038年问题(亦称为 Unix 千年虫,Y2K38,类似千年虫问题)可能会导致某些电脑软件在2038年之前或之内出现故障。这个问题会影响所有将系统时间存储为有符号32位整数的软件和系统,并将这个数字解释为自1970年1月1日协调世界时00:00:00以来的秒数。”


它为什么会发生,当它发生时会发生什么?

超过 世界协调时2038年1月19日星期二03:14:07的时间将“环绕”并在内部以负数存储,这些系统将解释为1901年12月13日的时间,而不是2038年的时间。这是因为自 UNIX 时代(1970年1月1日00:00:00 GMT)以来的秒数将超过计算机对32位有符号整数的最大值。


我们要怎么解决?

  • 使用长数据类型(64位就足够了)
  • 对于 MySQL (或 MariaDB) ,如果不需要时间信息,可以考虑使用 DATE列类型。如果需要更高的精度,请使用 DATETIME而不是 TIMESTAMP。请注意,DATETIME列不存储有关时区的信息,因此应用程序必须知道使用了哪个时区。
  • Wikipedia 上描述的其他可能的解决方案
  • 升级你的 Mysql 到 8.0.28或更高

是否有任何可能的替代方案来使用它,这不会造成类似的问题?

尽可能使用大型类型在数据库中存储日期: 64位就足够了—— GNU C 和 POSIX/SuS 中的长类型,PHP 中的 sprintf('%u'...)或 BC數扩展中的长类型。


即使我们还没有到2038年,有哪些潜在的破坏性用例?

所以 MySQL日期时间的范围是1000-9999,而 TIMESTAMP 的范围是1970-2038。如果您的系统存储生日、未来的远期日期(例如30年的抵押贷款)或类似的日期,那么您已经遇到了这个 bug。同样,如果这将成为一个问题,请不要使用 TIMESTAMP。


我们可以对使用 TIMESTAMP 的现有应用程序做些什么,以避免在问题真正发生时出现所谓的问题?

到2038年,PHP 应用程序仍将寥寥无几,尽管很难预见网络将成为一个遗留平台。

下面是修改数据库表列以将 TIMESTAMP转换为 DATETIME的过程。它首先创建一个临时列:

# rename the old TIMESTAMP field
ALTER TABLE `myTable` CHANGE `myTimestamp` `temp_myTimestamp` int(11) NOT NULL;


# create a new DATETIME column of the same name as your old column
ALTER TABLE `myTable` ADD `myTimestamp` DATETIME NOT NULL;


# update all rows by populating your new DATETIME field
UPDATE `myTable` SET `myTimestamp` = FROM_UNIXTIME(temp_myTimestamp);


# remove the temporary column
ALTER TABLE `myTable` DROP `temp_myTimestamp`

资源

在谷歌上快速搜索一下就可以了: 2038年问题

  1. 这种2038年问题(也称为 Unix 千年虫,Y2K38,类似于千年虫问题)可能会导致一些计算机软件在2038年之前或之后出现故障
  2. 这个问题会影响所有将系统时间存储为有符号32位整数的软件和系统,并将这个数字解释为自1970年1月1日00:00:00 UTC 以来的秒数。这样表示的最新时间是世界协调时2038年1月19日(星期二)03:14:07。超过这一时刻的时间将会“环绕”并在内部以负数形式存储,这些系统将把负数解释为1901年的日期而不是2038年的日期
  3. 对于现有的 CPU/OS 组合、现有的文件系统或现有的二进制数据格式,要解决这个问题并不容易

兄弟们,如果你需要使用 PHP 来显示时间戳,这是最好的 PHP 解决方案,不需要改变 UNIX _ TIMESTAMP 格式。

使用自定义 _ date ()函数,在函数内部使用 DateTime.以下是日期时间解决方案

只要数据库中的时间戳是 UNSIGNED BIGINT (8)。 只要你有 PHP 5.2.0 + +

因为我不想升级任何东西,所以我让我的后端(MSSQL)代替 PHP 来做这个工作!

$qry = "select DATEADD(month, 1, :date) next_date ";
$rs_tmp = $pdo->prepare($qry);
$rs_tmp->bindValue(":date", '2038/01/15');
$rs_tmp->execute();
$row_tmp = $rs_tmp->fetch(PDO::FETCH_ASSOC);


echo $row_tmp['next_date'];

可能不是一个有效的方法,但它的工作。

最近我一直在问自己这些问题,想要分享我为新项目找到的解决方案。

比金特

在阅读了诸如此类问题的各种答案后,我发现在 Bigint 列中存储 Unix 时间戳是一个更好的解决方案。

双基点范围将覆盖你从开始之前的时间直到年 292277026596可能被称为永远。

另外:

  • 它使用与 DATETIME 相同的8字节存储空间。
  • 它的时区是不可知的。
  • 您仍然可以通过 DEFAULT (unix_timestamp())使用自动生成的时间戳
  • 范围是如此之大,你的服务器将化为灰烬之前,包围可以发生,即使存储时间在毫秒。

十进制

这就是我想到的解决方案,你可以更好地控制自己。 您可以存储一个过度杀伤的日期范围,如 bigint 或减少它的东西现实和使用较少的存储。

在我的示例中,我还希望将秒的分数存储为实际的分数,并且仍然希望在 insert 上生成时间戳。

下面是创建表模式中的列定义:

`created` decimal(18,2) NOT NULL DEFAULT (unix_timestamp(now(2))) COMMENT 'unix timestamp'

使用十进制(18,2)提供了一个与 bigint/datetime 相同存储的绝对过度杀伤的时间范围,同时显示从秒到2位的分数。

存储是根据数字的数量,9个数字 = 4个字节有符号或无符号无关紧要。

您可以将范围限制在更现实的范围内,并使用比日期时间少得多的内容,或者将精度提高到纳秒。你决定什么是重要的。

另一个好处是,如果在遥远的未来你达到了你的射程极限,Mysql 会告诉你。 不会发生换行问题,相反,您将得到一个阻止插入的错误,并且可以轻松地再次更改表以添加另一个数字。

这对我来说意义非凡,我强烈建议用这种方法开始新的数据库。