为什么MySQL不支持毫秒/微秒精度?
所以我只是发现了MySQL中最令人沮丧的错误 。
显然, TIMESTAMP
字段和支持函数不支持比秒更高的精度!
所以我使用PHP和Doctrine,我真的需要这些微秒(我正在使用actAs: [Timestampable]
属性)。
我发现我可以使用BIGINT
字段来存储值。 但会学说加几毫秒? 我认为它只是将NOW()分配给该字段。 我也担心date操纵函数(在SQL中)通过代码散落将打破。
我也看到了一些关于编译UDF扩展的东西。 这是不可接受的,因为我或未来的维护人员会升级,篡改,改变。
有没有人find一个合适的解决方法?
有关下一个读者的信息,该错误最终在版本5.6.4中得到纠正:
“MySQL现在支持TIME,DATETIME和TIMESTAMP值的小数秒,精度达到微秒。”
从SQL92标准:
- TIMESTAMP – 包含datetime字段的YEAR,MONTH,DAY,HOUR,MINUTE和SECOND。
从我的angular度来看,符合SQL92的数据库不需要支持毫秒或微秒。 因此错误#8523正确标记为“function请求”。
教义如何处理微秒等? 我只是发现以下内容: Doctrine#时间戳 :
时间戳记数据types仅仅是date和时间数据types的组合。 时间戳types的值的表示是通过将date和时间string值连接到由空格连接的单个string中来完成的。 因此,格式模板是YYYY-MM-DD HH:MI:SS。
所以没有像SQL92文档中提到的那么微秒。 但我不深入学说,但它似乎是一个像Java中的hibernate的ORM例如。 因此,可以/应该可以定义自己的模型,可以将时间信息存储在BIGINT或STRING中,并且您的模型负责将其相应地读写到PHP类中。
顺便说一句:我不希望MySQL在不久的将来(例如未来5年)以毫秒/毫秒的速度支持TIMESTAMP。
我find了一个解决方法! 它非常干净,不需要更改任何应用程序代码。 这适用于Doctrine,也可以应用于其他ORM。
基本上,将时间戳存储为一个string。
如果datestring格式正确,比较和sorting工作。 传递datestring时,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' => 'Ymd H:i:s', 'disabled' => false, 'expression' => false, 'options' => array('notnull' => true)), 'updated' => array('name' => 'updated_at', 'alias' => null, 'type' => 'string(30)', 'format' => 'Ymd 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(); } } } }
由于您使用Doctrine存储数据,而Doctrine也不支持小数秒,所以瓶颈不是MySQL。
我build议你在需要额外精度的对象中定义额外的字段,并在其中存储microtime()
的输出。 您可能希望将其存储在两个不同的字段中 – 一个用于纪元时间戳,另一个用于微秒部分。 这样,您可以存储标准的32位整数,并使用SQL轻松对它们进行sorting和筛选。
我经常build议存储时代秒数,而不是本地时间戳types,因为它们通常更容易操作,并避免您继续使用本地时间types并在国际上提供服务的整个时区问题。
时间的另一个解决方法(以毫秒为单位)。 创build函数“time_in_msec”
用法:
两个date之间的差异,以毫秒为单位。
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 ;
从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;
只需要将datetime更改为datetime(6);
有关更多信息,请参阅以下内容: http : //dev.mysql.com/doc/refman/5.6/en/fractional-seconds.html
现在你可以使用微秒
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)
正如前面提到的微秒支持在版本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 ;
testing:
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手册”页面,其标题为“ 时间值分秒”