时间戳类型是否有(隐藏)毫秒?

Bre*_*bad 2 timestamp mysql-5.7

updated列创建为

| updated | timestamp | NO | | CURRENT_TIMESTAMP | on update CURRENT_TIMESTAMP |
Run Code Online (Sandbox Code Playgroud)

一个简单的SELECT给出

+---------------------+
| updated             |
+---------------------+
| 2021-12-25 00:15:47 |
+---------------------+
Run Code Online (Sandbox Code Playgroud)

不幸的是,updated没有被创建为timestamp(3).

有什么方法可以从中提取更高的准确性吗updated?(MySQL 是否存储比秒更高的精度,即使它只显示秒?)

Vér*_*ace 6

<TL;DR>

先回答一下问题:

  • 有什么办法可以从更新中提取更高的准确性吗?(MySQL 是否存储比秒更高的精度,即使它只显示秒?)

我的研究强烈表明,如果没有指定精度,则无法恢复比最初存储在字段中的数据更多的数据 - 这是TIMESTAMP(0)( ) 的默认值(请参阅下面的讨论)。p

</TL;DR>

可以肯定地说这一点的原因是,如果查看此处各种类型的存储要求,您将看到:

Data Type   Storage requirement
Timestamp   4 bytes + fractional seconds storage
Run Code Online (Sandbox Code Playgroud)

就在其下方,您会看到:

在此输入图像描述

因此,我从中得出的结论是,如果您选择不专门存储TIMESTAMP精度高于 0 的 s,那么您将丢失所有较高的精度,因为没有为该额外精度保留空间。

一个完全明确的答案需要一个十六进制编辑器和所有家庭探索文件的乐趣.ibd和/或通过 MySQL 的源代码进行搜索,这两者都比我的工资等级稍高一些!我想你可以认为你的额外精确度已经消失了,或者更确切地说,它从一开始就不存在!

</TL;DR>

在评论中犯了一些错误(自删除以来),我决定实际测试(正如他在自己的答案下的评论中所建议的那样)我的断言(mea culpa,mea maxima culpa)。

我很高兴地报告,我对舍入问题的看法是正确的 - 如果插入精度为 6 秒的时间,则该值将被舍入不是被截断。

下面的所有代码(除非另有说明)都可以在此处的小提琴上找到。

MySQLTIMESTAMP和时区!

对于 MySQL,TIMESTAMP 存储为自 UTC 以来的秒数 - 从这里开始:

一个时间戳。范围为“1970-01-01 00:00:01.000000”UTC 到“2038-01-19 03:14:07.999999”UTC。TIMESTAMP 值存储为自纪元 ('1970-01-01 00:00:00' UTC) 以来的秒数。

顺便说一句,这里TIMESTAMP还有另一个小提琴显示了 MySQL 和之间的区别DATETIME- 后者数据类型没有时区。

强烈建议您(或任何开发人员)尽可能使用 UTC 来存储时间 - 它更简单、更清晰,并且服务器会为您处理夏令时 (DST)。

另外,如果您必须使用本地时区,请不要使用偏移量(小提琴中的+07:00),因为您会丢失任何 DST 信息 - 我这样做的原因是因为 db<>fiddle 的 MySQL 实现不支持时区(请参阅此处),但它确实支持偏移量。

MySQL 安装过程会创建时区表,但不会加载它们。

有关 MySQL 的更多TIMESTAMP信息:

TIMESTAMP不管怎样,让我震惊的第一件事是没有精度的事实是TIMESTAMP(0),但这手册中:

可以给出 0 到 6 范围内的可选 fsp 值来指定小数秒精度。值 0 表示没有小数部分。如果省略,则默认精度为 0。

这里开始:

(这与标准 SQL 默认值 6 不同,是为了与以前的 MySQL 版本兼容。)

很公平,但他们至少[could | should]有一个服务器 SQL 模式设置来覆盖这一点,并将其更改为 SQL 标准行为 - 他们之前已经为一堆其他MySQL-ism 这样做了!

另一个棘手的问题(但再次记录在案)是,ROUND当您尝试将较高精度的分数压缩到较低精度的字段中时,会发生 ing:

将带有秒小数部分的 TIME、DATE 或 TIMESTAMP 值插入到相同类型但小数位数较少的列中会导致四舍五入。

MySQL 的行为:

因此,现在,我们将检查该行为的实际情况,如下所示:

CREATE TABLE test 
(
  s TIMESTAMP,
  t TIMESTAMP(0) NULL DEFAULT NULL,
  u TIMESTAMP(1) NULL DEFAULT NULL,
  v TIMESTAMP(2) NULL DEFAULT NULL,
  w TIMESTAMP(3) NULL DEFAULT NULL,
  x TIMESTAMP(4) NULL DEFAULT NULL,
  y TIMESTAMP(5) NULL DEFAULT NULL,
  z TIMESTAMP(6) NULL DEFAULT NULL
);
Run Code Online (Sandbox Code Playgroud)

然后,填充它:

INSERT INTO test VALUES -- insert the same value 8 times into the different fields!
(
  '2021-12-27 10:30:15.567899',
  '2021-12-27 10:30:15.567899', '2021-12-27 10:30:15.567899', 
  '2021-12-27 10:30:15.567899', '2021-12-27 10:30:15.567899',
  '2021-12-27 10:30:15.567899', '2021-12-27 10:30:15.567899',
  '2021-12-27 10:30:15.567899'
);
Run Code Online (Sandbox Code Playgroud)

然后我们检查我们的数据:

SELECT 's' AS dt, s  AS ts FROM test  UNION
SELECT 't',       t        FROM test  UNION
SELECT 'u',       u        FROM test  UNION
SELECT 'v',       v        FROM test  UNION
SELECT 'w',       w        FROM test  UNION
SELECT 'x',       x        FROM test  UNION
SELECT 'y',       y        FROM test  UNION
SELECT 'z',       z        FROM test;
Run Code Online (Sandbox Code Playgroud)

结果:

dt  ts
s   2021-12-27 10:30:16.000000
t   2021-12-27 10:30:16.000000
u   2021-12-27 10:30:15.600000
v   2021-12-27 10:30:15.570000
w   2021-12-27 10:30:15.568000
x   2021-12-27 10:30:15.567900
y   2021-12-27 10:30:15.567900
z   2021-12-27 10:30:15.567899
Run Code Online (Sandbox Code Playgroud)

因此,我们可以看到(如文档所述)TIMESTAMPTIMESTAMP(0)(无论如何都是相同的)都从 到15.567899向上舍入16,并且根据指定的精度,该舍入继续向下进行 - 按照手册!

那么,如何补救呢?

有两种可能性 - 第一种是您正在从某处获取旧的 TIMESTAMP(6) 测量值,并且您需要截断(而不是舍入它们 - 请参阅main fiddle),和/或可能,展望未来,您可能想要用作TIMESTAMP(3)您的字段类型和NOW(3)输入(不同的小提琴)。

  • TIMESTAMP(6) 测量:

这些将需要截断字段中的值TIMESTAMP(6)。事实证明,这比看起来更棘手 - 请参阅阶段的小提琴,但我能想到的最好的办法是:

TIMESTAMP(SUBSTRING(DATE_FORMAT(ts, '%Y-%m-%d %H:%i:%s.%f'), 1, 23))
Run Code Online (Sandbox Code Playgroud)

这可能看起来有点像绕着房子走,但 MySQLDATE_FORMAT函数的粒度是微秒,但(奇怪的是)不是毫秒!欢迎任何关于更简单、更优雅的解决方案的建议!

然后可以将该值插入 ap = 3 TIMESTAMP(参见fiddle)。

  • NOW() 函数:

手册)说:

如果给定 fsp 参数来指定从 0 到 6 的小数秒精度,则返回值包括该多个数字的小数秒部分。

那么,快乐的日子。并检查(小提琴):

CREATE TABLE now_test 
(
  s TIMESTAMP,
  t TIMESTAMP(0) NULL DEFAULT NULL,
  u TIMESTAMP(1) NULL DEFAULT NULL,
  v TIMESTAMP(2) NULL DEFAULT NULL,
  w TIMESTAMP(3) NULL DEFAULT NULL,
  x TIMESTAMP(4) NULL DEFAULT NULL,
  y TIMESTAMP(5) NULL DEFAULT NULL,
  z TIMESTAMP(6) NULL DEFAULT NULL
);
Run Code Online (Sandbox Code Playgroud)

插入八个NOW(p)值:

INSERT INTO now_test VALUES
(NOW(), NOW(0), NOW(1), NOW(2), NOW(3), NOW(4), NOW(5), NOW(6));
Run Code Online (Sandbox Code Playgroud)

进而:

SELECT 'NOW()' , 's' AS dt, s  AS ts FROM now_test  UNION
SELECT 'NOW(0)', 't',       t        FROM now_test  UNION
SELECT 'NOW(1)', 'u',       u        FROM now_test  UNION
SELECT 'NOW(2)', 'v',       v        FROM now_test  UNION
SELECT 'NOW(3)', 'w',       w        FROM now_test  UNION
SELECT 'NOW(4)', 'x',       x        FROM now_test  UNION
SELECT 'NOW(5)', 'y',       y        FROM now_test  UNION
SELECT 'NOW(6)', 'z',       z        FROM now_test;
Run Code Online (Sandbox Code Playgroud)

结果:

NOW()   dt  ts
NOW()   s   2021-12-27 19:28:38.000000
NOW(0)  t   2021-12-27 19:28:38.000000
NOW(1)  u   2021-12-27 19:28:38.600000
NOW(2)  v   2021-12-27 19:28:38.620000
NOW(3)  w   2021-12-27 19:28:38.628000
NOW(4)  x   2021-12-27 19:28:38.628700
NOW(5)  y   2021-12-27 19:28:38.628760
NOW(6)  z   2021-12-27 19:28:38.628767
Run Code Online (Sandbox Code Playgroud)

从上面的示例中我们可以看到,如果发生舍入,则w字段 ( NOW(3)) 将向上舍入为 ,19:28:38.629因为以下情况7。为了使这一点变得明显,可能需要运行几次小提琴!

磁盘上的大小:

我试图找到一个查询来获取磁盘上 MySQL 字段的各个大小,但 MySQL 显然不会公开此信息 - 至少不会通过其 SQL 接口公开。PostgreSQL 具有该pg_column_size()功能,并且还pageinspect为那些喜欢处理十六进制转储和位旋转的人提供了 contrib 扩展。

除了使用十六进制编辑器和/或深入研究 MySQL 的源代码之外,我能想到的最好的办法是(参见小提琴底部):

SELECT
  table_name  AS "The table",
  data_length AS "Data length"
FROM
  information_schema.tables
WHERE
    TABLE_NAME = 'test'
ORDER BY
  1 + 2
DESC;
Run Code Online (Sandbox Code Playgroud)

结果:

The table   Data length
     test         16384
Run Code Online (Sandbox Code Playgroud)

因此,8 条记录需要 16kB。快速搜索显示(手动):

每个表空间由数据库页组成。MySQL 实例中的每个表空间都具有相同的页面大小。默认情况下,所有表空间的页大小均为 16KB;

在同一页面上,提到了innodb_page_size

SHOW VARIABLES LIKE 'innodb_page_size';
Run Code Online (Sandbox Code Playgroud)

结果:

Variable_name   Value
innodb_page_size    16384
Run Code Online (Sandbox Code Playgroud)

因此,上面的所有Data Length...查询告诉我们的是,我们已经为一个小表使用了 1 页磁盘空间 - 这并不奇怪,也没有太多帮助。请参阅我在<TL;DR>关于存储要求的简介中的讨论,以了解为什么(很可能)您的额外精度位已经进入了天空中的那个伟大的位桶...... de profundis ......。


Aki*_*ina 5

\n

有什么办法可以从更新中提取更高的准确性吗?

\n
\n

不可以。无法选择表中不存在的数据,也无法根据表中存在的数据进行计算。

\n
\n

MySQL 是否存储比秒更高的精度,即使它只显示秒?

\n
\n

不。它存储的正是您所要求的内容。它可以存储高达微秒的数据 - 但您并没有要求这种精度。

\n
\n

好吧,这个问题确实是 d\xc3\xa9j\xc3\xa0 又是一遍又一遍!MySQL 的实现中似乎存在一个错误...(令人惊讶 - MySQL 中的一个错误...)至少在 dbfiddle.uk 和 dbfiddle.com 上。\xe2\x80\x93 V\xc3\xa9race - 立即接种疫苗

\n
\n

你打电话NOW()- 你会得到没有毫秒的日期时间。致电NOW(3)NOW(6)并获得更准确的值。

\n

https://dbfiddle.uk/?rdbms=mysql_5.7&fiddle=ccaf9b2c25e372c7bf0ca82c66ded787

\n
\n

好吧,来自 MySQL 阵营的好消息 - 你根本不需要担心秒的小数值 - 5.7 不支持它们 \xe2\x80\x93 V\xc3\xa9race - 立即接种疫苗

\n
\n

再也不要向说这种废话的MySQL阵营的人咨询了。

\n