だから私はちょうど見つけました MySQLでこれまでで最もイライラするバグ 。
どうやらTIMESTAMP
フィールド、および サポート関数 は秒よりも高い精度をサポートしていません!?
だから私はPHPとDoctrineを使用しています、そして私は本当にそれらのマイクロ秒が必要です(私はactAs: [Timestampable]
プロパティ)。
BIGINT
フィールドを使用して値を保存できることがわかりました。しかしdoctrineミリ秒を追加しますか?フィールドにNOW()を割り当てるだけだと思います。また、コードに散らばった日付操作関数(SQL)が壊れるのではないかと心配しています。
また、UDF拡張機能のコンパイルについても見ました。私または将来のメンテナーがアップグレードし、プーフ、変更を行ってしまうため、これは受け入れられません。
誰かが適切な回避策を見つけましたか?
回避策を見つけました!非常にクリーンで、アプリケーションコードの変更は必要ありません。これは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
およびvoila、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();
}
}
}
}
次の読者のために、このバグはバージョン5.6.4で最終的に修正されました。
「MySQLは、TIME、DATETIME、およびTIMESTAMP値の小数秒を、マイクロ秒までの精度でサポートするようになりました。」
SQL92標準から:
- TIMESTAMP-日時フィールドのYEAR、MONTH、DAY、HOUR、MINUTE、およびSECONDが含まれます。
SQL92準拠のデータベースは、私の観点からミリ秒またはマイクロ秒をサポートする必要はありません。したがって、バグ#8523は「機能要求」として正しくマークされています。
Doctrineはマイクロ秒などを処理しますか?次のことがわかりました: Doctrine#Timestamp :
タイムスタンプデータ型は、日付と時刻のデータ型の単なる組み合わせです。タイムスタンプタイプの値の表現は、日付と時刻の文字列値をスペースで結合された単一の文字列に結合することによって実現されます。したがって、フォーマットテンプレートはYYYY-MM-DD HH:MI:SSです。
そのため、SQL92-docsにあるようなマイクロ秒はありません。しかし、私は教義を深く掘り下げることはしませんが、例えばJavaの休止状態のようなORMのようです。したがって、独自のモデルを定義して、 BIGINTまたはSTRINGの時間情報とモデルは、PHPクラスに応じてそれを読み書きします。
ところで:MySQLが近い将来、たとえば5年後にミリ/マイクロ秒でTIMESTAMPをサポートするとは思わない。
Doctrine=を使用してデータを保存し、Doctrineは秒の小数部もサポートしていないため、ボトルネックはMySQLではありません。
追加の精度が必要なオブジェクトに追加フィールドを定義し、microtime()
の出力を保存することをお勧めします。おそらく2つの異なるフィールドに保存する必要があります。1つはエポック秒タイムスタンプ用で、もう1つはマイクロ秒部分用です。そうすれば、標準の32ビット整数を保存し、SQLを使用して簡単にソートおよびフィルタリングできます。
通常、操作が簡単で、ネイティブのタイムタイプを使用して国際的なサービスを提供し続けるタイムゾーン全体の問題を回避するため、ネイティブのタイムスタンプタイプの代わりにエポック秒を保存することをお勧めします。
ミリ秒単位の時間の別の回避策。作成された関数「time_in_msec」
使用法 :
ミリ秒単位の2つの日付の差。
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 ;
テスト:
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マニュアルページを参照してください