您建议使用datetime或timestamp字段吗?为什么(使用MySQL)?

我在服务器端使用PHP。


当前回答

也不DATETIME和TIMESTAMP类型在一般用例中基本上是断开的。MySQL将在未来改变它们。您应该使用BIGINT和UNIX时间戳,除非您有特定的理由使用其他时间戳。

特殊情况

以下是一些具体情况,在这些情况下,您的选择更容易,您不需要在这个答案中进行分析和提出一般建议。

仅限日期-如果您只关心日期(如下一个农历新年的日期,2022-02-01),并且您清楚了解该日期适用的时区(或不关心,如农历新年),则使用Date列类型。记录插入时间-如果您正在记录数据库中的行的插入日期/时间,并且您不担心应用程序在未来17年内会崩溃,那么继续使用默认值为CURRENT_TIMESTAMP()的TIMESTAMP。

为什么TIMESTAMP坏了?

TIMESTAMP类型以UTC时区存储在磁盘上。这意味着,如果您实际移动服务器,它不会损坏。很好✅. 但目前定义的时间戳将在2038年完全停止工作❌.

每次INSERT INTO或SELECT FROM TIMESTAMP列时,都会考虑客户端/应用程序服务器的物理位置(即时区配置)。如果移动应用程序服务器,则日期会中断❌.

(更新2022-04-29 MySQL在8.0.28中修复了这一问题,但如果您的生产环境位于CentOS 7或许多其他风格,那么您的迁移路径将需要很长时间才能获得此支持。)

为什么VARCHAR坏了?

VARCHAR类型允许以ISO8601格式明确地存储非本地日期/时间/两者,并且适用于2037年以后的日期。通常使用祖鲁时间,但ISO 8601允许对任何偏移进行编码。这不太有用,因为尽管MySQL日期和时间函数支持字符串作为输入,但如果输入使用时区偏移,则结果不正确。

VARCHAR还使用额外的存储字节。

为什么DATETIME中断?

DATETIME在同一列中存储DATE和TIME。除非时区被理解,并且时区没有存储在任何地方,否则这两个东西都没有任何意义❌. 您应该将预期的时区作为注释放在列中,因为时区与数据有着千丝万缕的联系。所以很少有人使用列注释,所以这是等待发生的错误。我从亚利桑那州继承了一台服务器,所以我总是需要将所有时间戳从亚利桑那州时间转换为另一个时间。

(更新2021-12-08我在多年的正常运行时间后重新启动了服务器,数据库客户端(带升级)重置为UTC。这意味着我的应用程序需要以不同的方式处理重置前后的日期。硬代码!)

DATETIME唯一正确的情况是完成以下句子:

您的2020年太阳新年正好从DATETIME(“2020-01-01 00:00:00”)开始。

DATETIMEs没有其他好的用途。也许你会想象一个特拉华州市政府的网络服务器。当然,这台服务器和所有访问这台服务器的人的时区都可以暗示在特拉华州,东部时区,对吧?错误的在这个千年里,我们都认为服务器存在于“云”中。因此,将您的服务器放在任何特定时区都是错误的,因为您的服务器总有一天会被移动。

注意:MySQL现在支持DATETIME文本中的时区偏移(谢谢@Marko)。这可能会使插入DATETIMEs更方便,但并不能解决数据的不完整和无用的含义,这一致命问题确定(“❌“)。

如何使用BIGINT?

定义:

CREATE TEMPORARY TABLE good_times (
    a_time BIGINT
)

插入特定值:

INSERT INTO good_times VALUES (
    UNIX_TIMESTAMP(CONVERT_TZ("2014-12-03 12:24:54", '+00:00', @@global.time_zone))
);

插入默认值(thx Brad):

ALTER TABLE good_times MODIFY a_time BIGINT DEFAULT (UNIX_TIMESTAMP());

或者,当然,这在你的应用程序中要好得多,比如:

$statement = $myDB->prepare('INSERT INTO good_times VALUES (?)');
$statement->execute([$someTime->getTimestamp()]);

选择:

SELECT a_time FROM good_times;

有一些过滤相对时间的技术(选择过去30天内的帖子,查找在注册后10分钟内购买的用户)超出了这里的范围。

其他回答

DATETIME与TIMESTAMP:

TIMESTAMP用于跟踪记录的更改,并在每次更改记录时进行更新。

DATETIME用于存储不受记录更改影响的特定静态值。

TIMESTAMP也受不同时区相关设置的影响。DATETIME是常量。

TIMESTAMP在内部将当前时区转换为UTC进行存储,并在检索期间将其转换回当前时区。

DATETIME无法执行此操作。

TIMESTAMP为4字节,DATETIME为8字节。

TIMESTAMP支持的范围:1970-01-01 00:00:01 UTC至2038-01-19 03:14:07 UTCDATETIME支持的范围:“1000-01-01 00:00:00”至“9999-12-31 23:59:59”

我建议不要使用DATETIME或TIMESTAMP字段。如果你想将一个特定的日子作为一个整体来表示(比如生日),那么就使用DATE类型,但是如果你要更具体一些,你可能会对记录一个实际的时刻感兴趣,而不是一个时间单位(日、周、月、年)。不要使用DATETIME或TIMESTAMP,而是使用BIGINT,只需存储自epoch以来的毫秒数(如果使用Java,则为System.currentTimeMillis())。这有几个优点:

避免供应商锁定。几乎每个数据库都以相对类似的方式支持整数。假设您想移动到另一个数据库。您想担心MySQL的DATETIME值之间的差异以及Oracle如何定义它们吗?即使在不同版本的MySQL中,TIMESTAMPS也具有不同的精度级别。直到最近,MySQL才支持时间戳中的毫秒。没有时区问题。这里有一些关于不同数据类型的时区的见解。但这是常识吗?你的同事会花时间学习吗?另一方面,很难将BigINT更改为java.util.Date。使用BigINT会导致很多时区问题被搁置一旁。无需担心范围或精度。你不必担心未来日期范围会缩短什么(TIMESTAMP只适用于2038年)。第三方工具集成。通过使用整数,第三方工具(例如EclipseLink)与数据库的接口很简单。并非所有第三方工具都会像MySQL一样理解“datetime”。如果您使用这些自定义数据类型,想尝试在Hibernate中确定是否应该使用java.sql.TimeStamp或java.util.Date对象?使用基本数据类型使第三方工具的使用变得微不足道。

这个问题与如何在数据库中存储货币值(即1.99美元)密切相关。你应该使用十进制,还是数据库的货币类型,或者最糟糕的是双精度?由于上面列出的许多相同原因,所有三种选择都很糟糕。解决方案是使用BIGINT将货币的价值存储为美分,然后在向用户显示价值时将美分转换为美元。数据库的工作是存储数据,而不是插入数据。您在数据库(尤其是Oracle)中看到的所有这些花哨的数据类型几乎没有增加任何内容,并使您开始进入供应商锁定状态。

我总是使用Unix时间戳,只是为了在处理大量日期时间信息时保持理智,尤其是在调整时区、添加/减去日期等时。在比较时间戳时,这排除了时区的复杂因素,并允许您在服务器端处理(无论是应用程序代码还是数据库查询)中节省资源,因为您使用了较轻的算术,而不是较重的日期时间加减函数。

另一件值得考虑的事情:

如果您正在构建一个应用程序,那么您永远不知道您的数据可能会被如何使用。如果你最终不得不将数据集中的一堆记录与第三方API中的一堆项目进行比较,然后按照时间顺序排列,你会很高兴为你的行设置Unix时间戳。即使您决定使用MySQL时间戳,也要存储Unix时间戳作为保险。

我发现TIMESTAMP能够基于当前时间自动更新自己,而不使用不必要的触发器,这一点非常有用。虽然TIMESTAMP是UTC,但这只是我自己。

它可以跟踪不同的时区,因此如果您需要显示相对时间,例如UTC时间就是您想要的。

时间戳和日期时间之间的另一个区别是时间戳,不能将默认值设置为NULL。