【问题标题】:Why doesn't MySQL support millisecond / microsecond precision?为什么 MySQL 不支持毫秒/微秒精度?
【发布时间】:2011-02-04 01:24:34
【问题描述】:

所以我刚刚找到the most frustrating bug ever in MySQL。

显然TIMESTAMP 字段和supporting functions 不支持比秒更高的精度!?

所以我正在使用 PHP 和 Doctrine,我真的需要这些微秒(我正在使用 actAs: [Timestampable] 属性)。

我发现我可以使用BIGINT 字段来存储值。但是教义会增加毫秒吗?我认为它只是将 NOW() 分配给该字段。我还担心通过代码散布的日期操作函数(在 SQL 中)会中断。

我还看到了一些关于编译 UDF 扩展的内容。这是不可接受的,因为我或未来的维护者将升级和噗,改变消失了。

有没有人找到合适的解决方法?

【问题讨论】:

  • 在您的情况下必须使用 MySQL?
  • 不是强制性的,但我很难解释为什么要为此向客户收费。我宁愿有一个解决方法。
  • 5 年了,他们还没有修复这个错误。这太可怕了。我曾在一家糟糕的软件公司工作,该公司在 3 年内修复了错误。
  • 您是否可以访问受支持版本的 MySql 的补丁?
  • 因为它没有。如果您需要这种精度,请使用 PostgreSQL。

标签: php mysql doctrine


【解决方案1】:

为了给下一个读者的信息,这个错误终于在版本5.6.4中得到纠正:

“MySQL 现在支持 TIME、DATETIME 和 TIMESTAMP 值的小数秒,精度高达微秒。”

【讨论】:

  • 使用上面的链接阅读 5.6.4 中的所有新内容......但如果您正在寻找如何执行小数秒,请阅读:dev.mysql.com/doc/refman/5.6/en/fractional-seconds.html。基本上不是 DATETIME(或 TIMESTAMP),而是 DATETIME(6)(或 TIMESTAMP(6))或其他精度。
  • mysql 人所犯的真正愚蠢的错误是,他们使事情向后不兼容。或者至少在通过 Java 访问它时。向它提供一个只有几分之一秒的时间戳,会导致 datetime 字段出现全 0。他们应该将毫秒精度作为可选的附加功能,除非您要求,否则您看不到。目前,由于它们导致的兼容性,这使公司付出了巨大的代价。他们犯了一个非常可怜和不负责任的鲁莽错误。一个好的计划是让未来的系统不使用 mysql。可能 PostgreSQL 可能是一个更好的选择。
  • @Mike 听起来像是 Java 访问的问题。默认行为是丢弃毫秒(默认情况下,列不存储毫秒,除非您更改它们的规范来这样做)。您可能在升级设计中遇到了错误而不是根本错误。也许有一些方法可以解决它?这里有没有提到这些问题的问答?
  • @ToolmakerSteve 问题发生在您更改所有内容时,就是mysql版本。 Java 连接器保持不变。解决方法是清除您提供给它的任何日期对象的毫秒部分。比如 date.setMilliseconds(date.getMilliseconds() / 1000 * 1000)。
【解决方案2】:

来自 SQL92 标准:

  • TIMESTAMP - 包含日期时间字段的 YEAR、MONTH、DAY、 小时、分钟和秒。

在我看来,符合 SQL92 的数据库不需要支持毫秒或微秒。因此,错误 #8523 被正确标记为“功能请求”。

Doctrine 将如何处理微秒等问题?我刚刚发现以下内容: Doctrine#Timestamp:

时间戳数据类型仅仅是 日期和时间的组合 当天的数据类型。这 时间价值的表示 邮票类型是通过加入来完成的 a 中的日期和时间字符串值 由空格连接的单个字符串。 因此,格式模板为 YYYY-MM-DD HH:MI:SS.

因此在 SQL92 文档中也没有提到微秒。 但我并没有深入学说,但它似乎是一个像 java 中的 hibernate 这样的 ORM。因此,可以/应该可以定义您自己的模型,您可以在其中将时间信息存储在 BIGINT 或 STRING 中,并且您的模型负责将其相应地读/写到您的 PHP 类中。

顺便说一句:我不希望 MySQL 在不久的将来(例如未来 5 年)支持毫秒/微秒的 TIMESTAMP。

【讨论】:

  • 但其他所有 RDBMS 在其时间戳字段和时间函数(GETDATE() NOW() 等)中都支持毫秒或更高精度。 +1 在技术上关于 SQL-92 是正确的
【解决方案3】:

我找到了解决方法!它非常干净,不需要任何应用程序代码更改。这适用于 Doctrine,也可以应用于其他 ORM。

基本上,将时间戳存储为字符串。

如果日期字符串格式正确,则比较和排序工作。 MySQL 时间函数将在传递日期字符串时截断微秒部分。如果date_diff 等不需要微秒精度,这没关系。

SELECT DATEDIFF('2010-04-04 17:24:42.000000','2010-04-04 17:24:42.999999');
> 0

SELECT microsecond('2010-04-04 17:24:42.021343');
> 21343 

我最终编写了一个 MicroTimestampable 类来实现它。我只是将我的字段注释为actAs:MicroTimestampable,瞧,MySQL 和 Doctrine 的微时间精度。

Doctrine_Template_MicroTimestampable

class Doctrine_Template_MicroTimestampable extends Doctrine_Template_Timestampable
{
    /**
     * Array of Timestampable options
     *
     * @var string
     */
    protected $_options = array('created' =>  array('name'          =>  'created_at',
                                                    'alias'         =>  null,
                                                    'type'          =>  'string(30)',
                                                    'format'        =>  'Y-m-d H:i:s',
                                                    'disabled'      =>  false,
                                                    'expression'    =>  false,
                                                    'options'       =>  array('notnull' => true)),
                                'updated' =>  array('name'          =>  'updated_at',
                                                    'alias'         =>  null,
                                                    'type'          =>  'string(30)',
                                                    'format'        =>  'Y-m-d H:i:s',
                                                    'disabled'      =>  false,
                                                    'expression'    =>  false,
                                                    'onInsert'      =>  true,
                                                    'options'       =>  array('notnull' => true)));

    /**
     * Set table definition for Timestampable behavior
     *
     * @return void
     */
    public function setTableDefinition()
    {
        if ( ! $this->_options['created']['disabled']) {
            $name = $this->_options['created']['name'];
            if ($this->_options['created']['alias']) {
                $name .= ' as ' . $this->_options['created']['alias'];
            }
            $this->hasColumn($name, $this->_options['created']['type'], null, $this->_options['created']['options']);
        }

        if ( ! $this->_options['updated']['disabled']) {
            $name = $this->_options['updated']['name'];
            if ($this->_options['updated']['alias']) {
                $name .= ' as ' . $this->_options['updated']['alias'];
            }
            $this->hasColumn($name, $this->_options['updated']['type'], null, $this->_options['updated']['options']);
        }

        $this->addListener(new Doctrine_Template_Listener_MicroTimestampable($this->_options));
    }
}

Doctrine_Template_Listener_MicroTimestampable

class Doctrine_Template_Listener_MicroTimestampable extends Doctrine_Template_Listener_Timestampable
{
    protected $_options = array();

    /**
     * __construct
     *
     * @param string $options 
     * @return void
     */
    public function __construct(array $options)
    {
        $this->_options = $options;
    }

    /**
     * Gets the timestamp in the correct format based on the way the behavior is configured
     *
     * @param string $type 
     * @return void
     */
    public function getTimestamp($type, $conn = null)
    {
        $options = $this->_options[$type];

        if ($options['expression'] !== false && is_string($options['expression'])) {
            return new Doctrine_Expression($options['expression'], $conn);
        } else {
            if ($options['type'] == 'date') {
                return date($options['format'], time().".".microtime());
            } else if ($options['type'] == 'timestamp') {
                return date($options['format'], time().".".microtime());
            } else {
                return time().".".microtime();
            }
        }
    }
}

【讨论】:

  • 您对这个解决方案的工作是正确的。但是,查找和比较时间(对于字符串)将比比较整数长得多。
  • 如果字段被索引了怎么办?
  • 整数很简单,在每种情况下要处理的数据要少得多,“Hi There”的处理成本要比 1871239471294871290843712974129043192741890274311234 的处理成本高得多。整数更具可扩展性,尤其是在比较中使用时,每次比较除了最短字符串之外的任何东西都有更多的 CPU 周期。索引会有所帮助,但不会改变您在比较中处理更多数据,至少在某些时候是这样。
  • -1 存储几十个字节而不是 4 个字节会使您的索引膨胀,从而破坏除玩具项目之外的任何内容的插入和查找速度。此外,所有这些强制都是昂贵的,CPU 方面。最后,字符串强制 not 提供与本地时间类型的一对一等价(例如,我可以从另一个时间戳中减去一个时间戳,但如果我为它们的字符串表示这样做,我总是得到零. 考虑到所有这些注意事项,直接将铲子称为铲子并将毫秒精度的时间戳存储在适当命名的 BIGINT 列中会更简单、更安全、更快捷。
  • 让我想知道是否可以使这段代码使用浮点值而不是字符串......在格式化值时,您可能需要使用 DateTime 类,作为一些本机日期时间函数不是微秒感知的(想到strtotime())。
【解决方案4】:

由于您使用 Doctrine 存储数据,并且 Doctrine 也不支持小数秒,因此瓶颈不是 MySQL。

我建议您在需要额外精度的对象中定义其他字段,并将microtime() 的输出存储在其中。您可能希望将其存储在两个不同的字段中 - 一个用于纪元秒时间戳,另一个用于微秒部分。这样您就可以存储标准的 32 位整数,并使用 SQL 轻松地对它们进行排序和过滤。

我经常建议存储纪元秒数而不是原生时间戳类型,因为它们通常更易于操作,并且避免了您在使用原生时间类型和提供国际服务时经常遇到的整个时区问题。

【讨论】:

  • 这看起来像是一个杂牌,但它可能是唯一的解决方法。感谢您的信息。
【解决方案5】:

Time 的另一种解决方法(以毫秒为单位)。创建函数“time_in_msec”

用法:

两个日期之间的差异,以毫秒为单位。

mysql> SELECT time_in_msec('2010-07-12 23:14:36.233','2010-07-11 23:04:00.000') AS miliseconds;
+-------------+
| miliseconds |
+-------------+
| 87036233    |
+-------------+
1 row in set, 2 warnings (0.00 sec)



DELIMITER $$

DROP FUNCTION IF EXISTS `time_in_msec`$$

CREATE FUNCTION `time_in_msec`(ftime VARCHAR(23),stime VARCHAR(23)) RETURNS VARCHAR(30) CHARSET latin1
BEGIN
    DECLARE msec INT DEFAULT 0;
    DECLARE sftime,sstime VARCHAR(27);
    SET ftime=CONCAT(ftime,'000');
    SET stime=CONCAT(stime,'000');
    SET  msec=TIME_TO_SEC(TIMEDIFF(ftime,stime))*1000+TRUNCATE(MICROSECOND(TIMEDIFF(ftime,stime))/1000,0);
    RETURN msec;
END$$

DELIMITER ;

【讨论】:

    【解决方案6】:

    从Mysql 5.6.4 版本开始,将微秒存储在一列中。

    “MySQL 现在支持 TIME、DATETIME 和 TIMESTAMP 值的小数秒,精度高达微秒。”

    例如:

    CREATE TABLE `test_table` (
    `name` VARCHAR(1000) ,
    `orderdate` DATETIME(6)
    );
    
    INSERT INTO test_table VALUES('A','2010-12-10 14:12:09.019473');
    
    SELECT * FROM test_table;
    

    只需要将datatype改为datetime改为datetime(6);

    有关更多信息,请参阅以下内容: http://dev.mysql.com/doc/refman/5.6/en/fractional-seconds.html

    【讨论】:

    • 您是说不需要对 Doctrine 进行任何更改?
    • 缺点是不能使用 ALTER TABLE table CHANGE COLUMN timestamp_column TIMESTAMP(6) NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP 你必须把它改成 CURRENT_TIMESTAMP(6)
    【解决方案7】:

    现在你可以使用微秒了

    mysql> select @@version;
    +-----------+
    | @@version |
    +-----------+
    | 5.6.26    |
    +-----------+
    1 row in set (0.00 sec)
    
    mysql> select now(6);
    +----------------------------+
    | now(6)                     |
    +----------------------------+
    | 2016-01-16 21:18:35.496021 |
    +----------------------------+
    1 row in set (0.00 sec)
    

    【讨论】:

      【解决方案8】:

      如前所述,5.6.4 版中添加了微秒支持

      也许以下对小数秒有用:

      drop procedure if exists doSomething123;
      delimiter $$
      create procedure doSomething123()
      begin
          DECLARE dtBEGIN,dtEnd DATETIME(6);
          DECLARE theCount,slp INT;
          set dtBegin=now(6); -- see http://dev.mysql.com/doc/refman/5.7/en/fractional-seconds.html
      
          -- now do something to profile
          select count(*) into theCount from questions_java where closeDate is null;
          select sleep(2) into slp;
          -- not the above but "something"
      
          set dtEnd=now(6); -- see http://dev.mysql.com/doc/refman/5.7/en/fractional-seconds.html
          select timediff(dtEnd,dtBegin) as timeDiff,timediff(dtEnd,dtBegin)+MICROSECOND(timediff(dtEnd,dtBegin))/1000000 seconds;
          -- select dtEnd,dtBegin;
      end$$
      delimiter ;
      

      测试:

      call doSomething123();
      +-----------------+----------+
      | timeDiff        | seconds  |
      +-----------------+----------+
      | 00:00:02.008378 | 2.016756 |
      +-----------------+----------+
      

      另一种看法:

      set @dt1=cast('2016-01-01 01:00:00.1111' as datetime(6)); 
      set @dt2=cast('2016-01-01 01:00:00.8888' as datetime(6)); 
      
      select @dt1,@dt2,MICROSECOND(timediff(@dt2,@dt1))/1000000 micros;
      +----------------------------+----------------------------+--------+
      | @dt1                       | @dt2                       | micros |
      +----------------------------+----------------------------+--------+
      | 2016-01-01 01:00:00.111100 | 2016-01-01 01:00:00.888800 | 0.7777 |
      +----------------------------+----------------------------+--------+
      

      请参阅标题为 Fractional Seconds in Time Values 的 MySQL 手册页

      【讨论】:

      • 我不知道我是否把上面的测试搞砸了,基数为 60 与基数 100,其他什么,或者它们是否正确。也许一些同行可以对此有所了解。谢谢。无论如何,我只是认为这个欺骗目标缺少一些现代小数秒的工作。
      猜你喜欢
      • 2015-06-25
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-08-07
      • 2013-02-03
      相关资源
      最近更新 更多