web-dev-qa-db-fra.com

Pourquoi MySQL ne prend-il pas en charge la précision en millisecondes / microsecondes?

Je viens donc de trouver le bogue le plus frustrant de MySQL .

Apparemment, le champ TIMESTAMP et fonctions de support ne prennent pas en charge une plus grande précision que les secondes!?

J'utilise donc PHP et Doctrine, et j'ai vraiment besoin de ces microsecondes (j'utilise le actAs: [Timestampable] propriété).

J'ai trouvé un que je peux utiliser un champ BIGINT pour stocker les valeurs. Mais doctrine ajoutera-t-il les millisecondes? Je pense qu'il affecte simplement NOW () au champ. Je crains également que les fonctions de manipulation de date (en SQL) dispersées dans le code ne se cassent.

J'ai également vu quelque chose sur la compilation d'une extension UDF. Ce n'est pas acceptable parce que moi ou un futur mainteneur mettrons à jour et pouf, le changement aura disparu.

Quelqu'un a-t-il trouvé une solution de contournement appropriée?

41
Byron Whitlock

J'ai trouvé une solution de contournement! Il est très propre et ne nécessite aucune modification du code d'application. Cela fonctionne pour Doctrine et peut également être appliqué à d'autres ORM.

Fondamentalement, stockez l'horodatage sous forme de chaîne.

Les comparaisons et le tri fonctionnent si la chaîne de date est formatée correctement. Les fonctions de temps de MySQL tronqueront la partie microseconde lorsqu'une chaîne de date sera passée. C'est correct si la précision en microsecondes n'est pas nécessaire pour date_diff etc.

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 

J'ai fini par écrire une classe MicroTimestampable qui implémentera cela. Je viens d'annoter mes champs comme actAs:MicroTimestampable et le tour est joué, précision microtime avec MySQL et 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();
            }
        }
    }
}
9
Byron Whitlock

Pour information pour les prochains lecteurs, ce bug a finalement été corrigé dans la version 5.6.4:

"MySQL prend désormais en charge les fractions de seconde pour les valeurs TIME, DATETIME et TIMESTAMP, avec une précision allant jusqu'à la microseconde."

36
Xavier Portebois

De la norme SQL92:

  • TIMESTAMP - contient l'année, le mois, le jour, l'heure, la minute et la seconde du champ datetime.

Une base de données compatible SQL92 n'a pas besoin de prendre en charge les millisecondes ou microsecondes de mon point de vue. Par conséquent, le bogue n ° 8523 est correctement marqué comme "demande de fonctionnalité".

Comment Doctrine va gérer les microsecondes et al? Je viens de trouver ce qui suit: Doctrine # Timestamp :

Le type de données d'horodatage est une simple combinaison de la date et de l'heure des types de données du jour. La représentation des valeurs du type d'horodatage est réalisée en joignant les valeurs de chaîne de date et d'heure en une seule chaîne jointe par un espace. Par conséquent, le modèle de format est AAAA-MM-JJ HH: MI: SS.

Il n'y a donc pas de microsecondes mentionnées comme dans les documents SQL92. Mais je ne suis pas trop en profondeur dans la doctrine, mais il semble que ce soit un ORM comme hibernate dans Java par exemple. Par conséquent, il pourrait/devrait être possible de définir vos propres modèles, où vous pouvez stocker le information de temps dans un BIGINT ou STRING et votre modèle est responsable de le lire/écrire en conséquence dans vos classes PHP.

BTW: Je ne m'attends pas à ce que MySQL supporte TIMESTAMP avec des millisecondes/microsecondes dans un avenir proche, par exemple les 5 prochaines années.

29

Comme vous utilisez Doctrine pour stocker les données et Doctrine ne prend pas en charge les secondes fractionnaires non plus, le goulot d'étranglement n'est pas MySQL).

Je vous suggère de définir des champs supplémentaires dans vos objets où vous avez besoin de la précision supplémentaire, et de stocker en eux la sortie de microtime(). Vous voudrez probablement le stocker dans deux champs différents - l'un pour l'horodatage Epoch secondes et l'autre pour la partie microsecondes. De cette façon, vous pouvez stocker des entiers 32 bits standard et les trier et les filtrer facilement à l'aide de SQL.

Je recommande souvent de stocker les secondes Epoch au lieu des types d'horodatage natifs, car ils sont généralement plus faciles à manipuler et évitent tout le problème de fuseau horaire dans lequel vous continuez à vous intéresser aux types d'heure natifs et à fournir un service international.

4
Guss

Une autre solution de contournement pour le temps en millisecondes. Fonction créée "time_in_msec"

UTILISATION:

Différence entre deux dates en millisecondes.

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 ;
3
Bijumon K N

À partir de la version 5.6.4 de Mysql, il stocke la microseconde dans une colonne.

"MySQL prend désormais en charge les fractions de seconde pour les valeurs TIME, DATETIME et TIMESTAMP, avec une précision allant jusqu'à la microseconde."

Par exemple:

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;

Il suffit de changer le type de données en datetime en datetime (6);

Pour plus d'informations, reportez-vous à ce qui suit: http://dev.mysql.com/doc/refman/5.6/en/fractional-seconds.html

3
Rahul More

Vous pouvez maintenant utiliser des micro secondes

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)
2
DehuaYang

Comme mentionné, le support en microsecondes a été ajouté dans la version 5.6.4

Peut-être que ce qui suit est utile pendant quelques secondes:

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 ;

Tester:

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

Une autre vue:

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 |
+----------------------------+----------------------------+--------+

Voir la page du manuel MySQL intitulée Fractional Seconds in Time Values

1
Drew