Situation mystérieuse MySQL TIME

Environ. trad. : Cette analyse détaillée d'un détail apparemment insignifiant dans l'implémentation à l'intérieur de MySQL a provoqué des discussions naturelles sur l'exactitude des approches de développement d'un projet Open Source bien connu en général. Ce que l'ingénieur portugais a effectivement découvert, il le raconte dans un format proche d'un roman policier ...



Beaucoup en 2020 ont été victimes d'un étrange phénomène de perception du temps, mais certains systèmes de gestion de bases de données manipulent le temps beaucoup plus longtemps. J'ai remarqué cela pour la première fois lorsqu'un de mes amis dans l'un de ses projets ( Accord est un bot Discord populaire) a rencontré l'exception suivante du connecteur MySQL lorsqu'il était utilisé avec EF Core:



MySqlException: Incorrect TIME value: '960:00:00.000000'


Pas trop doué pour MySQL (comme je préfère PostgreSQL pour des raisons qui apparaîtront bientôt), j'ai pensé pendant une seconde que le nombre d'heures était faux. Il est raisonnable de supposer que les valeurs TIME sont limitées à 24 heures, ou que les valeurs couvrant plusieurs jours nécessitent une syntaxe différente - par exemple, 40:00:00:00représenteraient 40 jours. Mais la réalité s'est avérée beaucoup plus compliquée et déroutante.



La prochaine étape évidente était de vérifier la documentation MySQL . Il disait:



MySQL reçoit et affiche les valeurs TIME au format 'hh: mm: ss' (ou au format 'hhh: mm: ss' pour les grandes valeurs horaires).


Jusqu'à présent, tout va bien: notre valeur TIME problématique correspond bien à ce format, bien que le fait hhqu'elles hhhsoient spécifiées explicitement soulève des soupçons (qu'en est-il des valeurs d'horloge dépassant 999?). La phrase suivante de la documentation explique en partie tout, stimulant en cours de route un tas de questions comme "Qu'est-ce que ...?":



Les valeurs TIME peuvent aller de «-838: 59: 59» à «838: 59: 59».


Bon d'accord ... Une étrange gamme. Il doit y avoir une bonne raison technique à cela. 839 heures équivaut à 34,958 (3) jours et la plage entière est exactement de 6040798 secondes. La documentation se lit comme suit:



MySQL reconnaît les valeurs TIME dans plusieurs formats, dont certains peuvent inclure des fractions de seconde jusqu'à 6 décimales (microsecondes).


En d'autres termes, l'intervalle entier est de 6 040 798 000 000 microsecondes. Encore une fois, un chiffre étrange. C'est loin d'être une puissance de deux (entre 2 42 et 2 43 ), donc MySQL semble utiliser un format de représentation interne unique. Mais avant d'entrer dans ce problème, permettez-moi de souligner à quel point ce type est mauvais.



C'est tout ce que MySQL a à offrir pour mesurer les intervalles de temps, avec une durée totale d'un peu plus d'un mois. Quelle est la taille de ce «petit peu»? Comme vous pouvez le voir, ce n'est même pas un multiple d'un nombre entier de jours.



Pire encore, le fournisseur MySQL dans EF Core le plus populaire convertit .NET TimeSpanen TIME par défaut , malgré le fait queTimeSpanpeut contenir des intervalles de dizaines de millénaires (il utilise des entiers 64 bits et la précision autorisée est de 10 à 8 s). Comparez cela à quelques mois dans TIME. D'autres personnes ont rencontré



ce problème et la discussion dans le problème correspondant contient une référence au comportement de SQL Server: «Cela imite le comportement de SQL Server». J'ai vérifié - en effet, le type d'heure SQL Server a une plage de 00: 00: 00.0000000 à 23: 59: 59.9999999, ce qui est généralement beaucoup plus raisonnable que l'étrange plage TIME. Mais revenons à MySQL. Quelle est la raison d'une gamme aussi inhabituelle? Dans le manuel de l'appareil MySQL



dit que dans la version 5.6.4, le type TIME a changé et qu'il existe un support pour les fractions de secondes. Trois octets sont utilisés pour la partie entière. Si ces trois octets sont entièrement utilisés pour coder les secondes, cela se traduit par un laps de temps de plus de 2330 heures - bien plus que le maximum actuel de 838 heures (bien que même cela ne soit pas très utile lors de la conversion de TimeSpan'a).



Cela signifie que le processus qui encode le temps dans MySQL gaspille des bits - peut-être pour des raisons de facilité d'utilisation (même si je ne suis pas sûr dans quelles circonstances cela est pertinent). Peut-être que cela a du sens si le SGBD (et l'idée des développeurs de ce que les utilisateurs en feront) est orienté vers le travail avec des chaînes et que les développeurs veulent accélérer la présentation hh:mm:ss.



Alors voyez:



1 — (1 = , 0 = )

1 ( )

10 — (0-838)

6 — (0-59)

6 — (0-59)

— 24 = 3


Cela explique tout, n'est-ce pas? Eh bien, regardons de plus près. 10 bits pendant des heures ... et la plage est de zéro à 838. Je m'empresse de vous rappeler que 2 10 = 1024, pas 838. L'intrigue prend de l'ampleur ...



Bien sûr, je ne suis pas la première personne à poser cette question (j'ai déjà posé cette question sur StackOverflow ). Tout semble être indiqué dans la réponse «acceptée» ici, cependant, l'étrange choix de 838 heures est d'abord expliqué par «la rétrocompatibilité avec des applications qui ont été écrites il y a assez longtemps», et alors seulement il est mentionné que cela a quelque chose à voir avec la compatibilité avec MySQL 3 - soit dit en passant Windows 98 était alors considéré comme une nouveauté, et Linux n'avait même pas 10 ans.



Dans MySQL 3, le type TIME utilisait également 3 octets, mais il le faisait d'une manière complètement différente. Un des bits était également réservé pour le signe, mais les 23 bits restants correspondaient à des entiers obtenus comme suit: heures × 10 000 + minutes × 100 + secondes. En d'autres termes, les deux chiffres les moins significatifs étaient des secondes, les deux suivants étaient des minutes et les deux autres étaient des heures. 2 * 23 est 83888608, soit 838: 86: 08, donc la valeur d'heure valide maximale dans ce format est 838: 59: 59.



Ce format est encore moins pratique que le format actuel, car il nécessite une multiplication et une division pour presque toutes les opérations de temps (à l'exception du formatage et de l'analyse des chaînes - ce qui prouve une fois de plus que MySQL accorde trop d'attention aux E / S de chaînes et ne se soucie pas vraiment de la présence de types. ce qui serait pratique pour les opérations internes et les protocoles non basés sur des chaînes).



Les développeurs MySQL ont été en mesure de corriger ce type de nombreuses fois, ou du moins en fournir une alternative qui est libre de la limitation existante. Le type TIME a changé deux fois depuis MySQL 3 jusqu'à aujourd'hui, mais à chaque fois l'étrange plage est restée la même - peut-être pour des raisons de compatibilité.



Je n'arrive pas à imaginer une situation où l'élargissement de la plage d'une valeur pour un type pourrait rompre la compatibilité des applications: les types dans MySQL ont-ils un comportement de débordement spécifique? Quel programmeur sensé s'appuierait sur les contraintes internes des types de base de données pour valider quelque chose dans son application? S'il y a une telle personne, pourquoi diable aurait-il soudainement décidé de transférer cette limite ridicule de 838 heures dans le modèle de données de son application sans aucun changement? Pour être honnête, je ne veux même pas connaître les réponses à ces questions.



Malgré quelques transformations majeures dans l'histoire de MySQL, le type TIME est toujours maladroit et limité. Et le point fort du programme ici, à mon avis, est le bit inutilisé «réservé aux futures extensions». J'espère qu'à long terme, il pointera vers l'ancien TIME, hérité, et d'ici là MySQL et / ou MariaDB auront un type de temps sensible tel que INTERVAL dans PostgreSQL , qui a une plage de ± 178 millions d'années et une microseconde précision.



PS du traducteur



Lisez aussi sur notre blog:






All Articles