2010-04-03 128 views
41

所以我刚发现the most frustrating bug ever in MySQL为什么MySQL不支持毫秒/微秒精度?

显然TIMESTAMP字段和supporting functions不支持比秒更大的精度!?所以我使用PHP和Doctrine,我真的需要那些微秒(我正在使用actAs: [Timestampable]属性)。

我发现我可以使用BIGINT字段来存储值。但是学说会增加毫秒?我认为它只是将NOW()分配给该字段。我也担心通过代码撒播的日期操纵函数(在SQL中)会中断。

我还看到了一些关于编译UDF扩展的信息。这是不可接受的,因为我或未来的维护人员会升级并呕心沥血地改变。

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

+0

在你的情况下使用MySQL是强制性的? – VolkerK 2010-04-03 18:42:12

+0

不是强制性的,但我很难解释为什么我会为此为客户计费。我宁愿有一个解决方法。 – 2010-04-03 18:44:45

+8

5年,他们还没有修复这个错误。那太糟糕了。我曾与一家可在3年内修复漏洞的可怕软件公司合作。 – ChaosPandion 2010-04-03 18:55:12

回答

9

我找到了解决方法!它非常干净,不需要更改任何应用程序代码。这适用于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和瞧,microtime精度与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(); 
      } 
     } 
    } 
} 
+0

你对这个解决方案的工作是正确的。但是,查找和比较时间(字符串)比比较整数要长得多。 – 2010-04-08 19:32:38

+0

如果这个字段被索引的话,这是多么的如此呢? – 2010-04-08 23:08:18

+3

整数很简单,在每种情况下处理的数据要少得多,“Hi There”的处理要比1871239471294871290843712974129043192741890274311234要贵得多。整数更具可扩展性,特别是在比较中使用时,除了最短的字符串之外,每个比较的CPU周期更多。索引有帮助,但不会改变你处理更多的比较数据,至少在某些时候。 – 2010-04-09 10:49:06

28

从SQL92-标准:

  • TIMESTAMP - 包含日期时间字段的年,月,日, 小时,分钟和秒。

一个SQL92兼容的数据库并不需要支持毫或微秒从我的观点。因此错误#8523正确标记为“功能请求”。

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

时间戳数据类型是日期和时间的日数据类型的 区区 组合。 戳类型的值的 表示通过将 单个字符串中的日期和时间字符串值加入空格来实现。 因此,格式模板是 YYYY-MM-DD HH:MI:SS。

所以没有提到的微秒或者在SQL92文档中提到。 但我不想深入学说,但它似乎是一个像Java中的休眠的ORM例如。因此,它可能/应该有可能定义自己的模型,您可以将时间信息存储在BIGINT或STRING中,并且您的模型负责相应地读取/写入您的PHP类。

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

+5

但是其他每个RDBMS都支持其时间戳字段和时间函数(GETDATE()NOW()等)中的毫秒或更高精度。 +1在技术上正确的SQL-92 – 2010-04-04 00:42:13

+1

是的,但你总是得离开他们想要更多... – Drew 2010-07-15 08:11:00

4

由于您使用Doctrine来存储数据,而Doctrine也不支持小数秒,那么瓶颈就不是MySQL。

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

我经常建议存储时代秒数,而不是原生时间戳类型,因为它们通常更容易操作,并避免您继续使用本地时间类型和国际化服务提供的整个时区问题。

+1

这似乎是一个kludge,但它可能是解决方法的唯一方法。谢谢(你的)信息。 – 2010-04-04 18:21:31

3

另一个以毫秒为单位解决方法的时间。创建功能 “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 ; 
35

对于下一个读者的信息,这个bug终于可以在版本5.6.4 纠正

“的MySQL现在支持TIME,DATETIME,TIMESTAMP和值小数秒,最多可微秒精度“。

+3

参考MySQL文档:http:// dev .mysql.com/doc/refman/5.6/en/news-5-6-4.html – Halcyon 2012-05-11 11:10:52

+2

使用上面的链接阅读5.6.4中的新内容...但是如果您正在寻找如何执行小数秒,阅读:http://dev.mysql.com/doc/refman/5.6/en/fractional-se conds.html。基本上,而不是DATETIME(或TIMESTAMP),做DATETIME(6)(或TIMESTAMP(6))或其他精度。 – Ephraim 2013-04-30 06:54:25

+0

mysql家伙们犯的真正愚蠢的错误是,他们让事情向后兼容。或者至少通过Java访问它时。为它提供一个小数秒的时间戳,会导致datetime字段全部为0。 他们应该使毫秒级的精度成为一个可选的添加项,除非您要求,否则您不会看到。 目前,这是由于它们的兼容性造成巨大成本的公司。他们的一个非常可怜和不负责任的鲁莽的错误。 一个很好的计划是未来的系统不使用mysql。可能PostgreSQL可能是更好的选择。 – Mike 2016-01-29 18:18:02

3

从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; 

仅需要改变数据类型为日期时间为日期时间(6);

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

+0

你是说你不需要对教义做任何修改? – malhal 2013-07-06 20:01:11

+1

缺点是你不能使用ALTER TABLE表CHANGE COLUMN timestamp_column TIMESTAMP(6)NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP你将不得不将它更改为CURRENT_TIMESTAMP(6) – fyrye 2014-01-19 05:04:59

2

现在你可以使用微秒

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) 
1

如前所述微秒支持在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 | 
+----------------------------+----------------------------+--------+ 

请参见MySQL手册有权Fractional Seconds in Time Values

+0

良好的工作德鲁! – 2016-10-04 19:51:46

+0

我不知道我是否把上面的测试搞错了,基数为60,基数为100,还有其他的东西,或者如果他们是现货。也许有些同行可能会这样做。谢谢。无论如何,我只是认为这个愚蠢的目标是缺少一些现代分数的第二部作品。 – Drew 2016-10-04 19:53:21