所以我刚刚找到了the most frustrating bug ever in MySQL。
显然TIMESTAMP
字段和supporting functions不支持比秒更高的精度!?
所以我使用PHP和Doctrine,我真的需要那些微秒(我使用的是actAs: [Timestampable]
属性)。
我发现我可以使用BIGINT
字段来存储值。但是教条会加几毫秒吗?我认为它只是将NOW()赋予该领域。我也担心通过代码散布的日期操作函数(在SQL中)会破坏。
我还看到了有关编译UDF扩展的一些信息。这是不可接受的,因为我或未来的维护者会升级和改变,改变了。
有没有人找到合适的解决方法?
答案 0 :(得分:36)
有关下一位读者的信息,最终会在版本 5.6.4 中更正此错误:
“MySQL现在支持TIME,DATETIME和TIMESTAMP值的小数秒,精度高达微秒。”
答案 1 :(得分:29)
来自SQL92-Standard:
- TIMESTAMP - 包含日期时间字段的年,月,日, 小时,分钟和秒。
从我的角度来看,符合SQL92的数据库不需要支持毫秒或微秒。因此Bug#8523被正确标记为“功能请求”。
Doctrine如何处理微秒等?我刚刚发现以下内容: Doctrine#Timestamp:
时间戳数据类型仅仅是一种 日期和时间的组合 当天的数据类型。该 表示时间的价值 邮票类型是通过加入完成的 a中的日期和时间字符串值 单个字符串由空格连接。 因此,格式模板是 YYYY-MM-DD HH:MI:SS。
因此,在SQL92-docs中没有提到微秒。 但我不是深入学说,但它似乎是一个像java中的hibernate之类的ORM。因此,可以/应该可以定义您自己的模型,您可以将时间信息存储在BIGINT或STRING中,并且您的模型负责相应地将其读/写到PHP类中。
BTW:我不认为MySQL会在不久的将来(例如未来5年)以毫秒/微秒的速度支持TIMESTAMP。答案 2 :(得分: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
,并且使用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();
}
}
}
}
答案 3 :(得分:4)
当你使用Doctrine存储数据时,Doctrine也不支持小数秒,那么瓶颈就不是MySQL。
我建议您在对象中定义需要额外精度的其他字段,并在其中存储microtime()
的输出。您可能希望将其存储在两个不同的字段中 - 一个用于纪元秒时间戳,另一个用于微秒部分。这样,您可以存储标准的32位整数,并使用SQL轻松地对它们进行排序和过滤。
我经常建议存储纪元秒而不是本机时间戳类型,因为它们通常更容易操作,并且避免了您继续使用本机时间类型并在国际上提供服务的整个时区问题。
答案 4 :(得分:3)
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 ;
答案 5 :(得分: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;
只需要将数据类型更改为datetime到datetime(6);
有关更多信息,请参阅以下内容: http://dev.mysql.com/doc/refman/5.6/en/fractional-seconds.html
答案 6 :(得分: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)
答案 7 :(得分: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手册页