Misteriosa situación TIME en MySQL

Aprox. transl. : Este análisis detallado de un detalle aparentemente insignificante en la implementación dentro de MySQL provocó discusiones naturales sobre la corrección de los enfoques para el desarrollo de un proyecto de código abierto conocido en general. Lo que el ingeniero portugués realmente descubrió, lo cuenta en un formato cercano a una historia de detectives ...



Muchos en 2020 fueron víctimas de un extraño fenómeno de percepción del tiempo, pero algunos sistemas de administración de bases de datos manipulan el tiempo durante mucho más tiempo. Me di cuenta de esto por primera vez cuando un amigo mío en uno de sus proyectos ( Accord es un popular bot de Discord) se encontró con la siguiente excepción del conector MySQL cuando se usa con EF Core:



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


No muy familiarizado con MySQL (ya que prefiero PostgreSQL por razones que pronto se harán evidentes), pensé por un segundo que la cantidad de horas era incorrecta. Es razonable suponer que los valores de TIME están limitados a 24 horas, o que los valores que abarcan varios días requieren una sintaxis diferente; por ejemplo, 40:00:00:00representarían 40 días. Pero la realidad resultó ser mucho más complicada y confusa.



El siguiente paso obvio fue verificar la documentación de MySQL . Decía:



MySQL recibe y muestra valores de TIEMPO en formato 'hh: mm: ss' (o en formato 'hhh: mm: ss' para valores horarios grandes).


Hasta ahora, todo está bien: nuestro valor de TIME problemático encaja bien con este formato, aunque el hecho de que hhse hhhespecifiquen explícitamente levanta sospechas (¿qué pasa con los valores de reloj que exceden 999?). La siguiente oración en la documentación explica parcialmente todo, en el camino estimulando un montón de preguntas como "¿Qué demonios ...?":



Los valores de TIEMPO pueden oscilar entre '-838: 59: 59' y '838: 59: 59'.


Bueno, está bien ... Un rango extraño. Debe haber una buena razón técnica para ello. 839 horas son 34,958 (3) días y el rango completo es exactamente 6040798 segundos. La documentación dice lo siguiente:



MySQL reconoce valores de TIEMPO en varios formatos, algunos de los cuales pueden incluir fracciones de segundo hasta 6 decimales (microsegundos).


En otras palabras, el intervalo completo es 6.040.798.000.000 microsegundos. De nuevo, un número extraño. Está lejos de ser una potencia de dos (entre 2 42 y 2 43 ), por lo que MySQL parece estar usando algún formato de representación interno único. Pero antes de entrar en este tema, permítanme señalar lo malo que es este tipo.



Esto es todo lo que MySQL tiene para ofrecer para medir intervalos de tiempo, con el lapso de tiempo completo un poco más de un mes. ¿Qué tan grande es este "pedacito"? Como puede ver, ni siquiera es un múltiplo de un número entero de días.



Peor aún, el proveedor MySQL más popular en EF Core convierte .NET TimeSpana TIME de forma predeterminada , a pesar de queTimeSpanpuede contener intervalos de decenas de milenios (utiliza enteros de 64 bits y la precisión permitida es de 10 a 8 s). Compare eso con un par de meses en TIME. Otras personas han encontrado



este problema , y la discusión en el problema correspondiente contiene una referencia al comportamiento de SQL Server: "Esto imita el comportamiento de SQL Server". Verifiqué, de hecho, el tipo de tiempo de SQL Server tiene un rango de 00: 00: 00.0000000 a 23: 59: 59.9999999, que generalmente es mucho más razonable que el extraño rango de TIEMPO. Pero volvamos a MySQL. ¿Cuál es la razón de un rango tan inusual? En el manual del dispositivo MySQL



dice que en la versión 5.6.4 el tipo TIME ha cambiado y hay soporte para fracciones de segundos. Se utilizan tres bytes para toda la parte. Si estos tres bytes se utilizan por completo para codificar segundos, esto da como resultado un lapso de tiempo de más de 2,330 horas, mucho más que el máximo actual de 838 horas (aunque incluso eso no es muy útil al convertir TimeSpan'a).



Esto significa que el proceso que codifica la hora en MySQL está desperdiciando bits, tal vez para facilitar su uso (aunque no estoy seguro de en qué circunstancias esto es relevante). Quizás esto tenga sentido si el DBMS (y la idea de los desarrolladores de lo que los usuarios harán con él) está orientado a trabajar con cadenas, y los desarrolladores quieren acelerar la presentación hh:mm:ss.



Así que vea:



1 — (1 = , 0 = )

1 ( )

10 — (0-838)

6 — (0-59)

6 — (0-59)

— 24 = 3


Eso lo explica todo, ¿no? Bueno, echemos un vistazo más de cerca. 10 bits por horas ... y el rango es de cero a 838. Me apresuro a recordarles que 2 10 = 1024, no 838. La intriga está ganando impulso ...



Por supuesto, no soy la primera persona que hizo esta pregunta (ya he preguntado sobre esto en StackOverflow antes ). Todo parece estar indicado en la respuesta "aceptada" allí, sin embargo, la extraña elección de 838 horas se explica primero por la "compatibilidad con versiones anteriores con aplicaciones que se escribieron hace bastante tiempo", y solo entonces se menciona que esto tiene algo que ver con la compatibilidad con MySQL 3 , por cierto Windows 98 se consideró entonces una novedad, y Linux no tenía ni 10 años.



En MySQL 3, el tipo TIME también usó 3 bytes, solo que lo hizo de una manera completamente diferente. Uno de los bits también se reservó para el signo, pero los 23 bits restantes correspondían a enteros obtenidos de la siguiente manera: horas × 10.000 + minutos × 100 + segundos. En otras palabras, los dos dígitos menos significativos fueron los segundos, los dos siguientes minutos y los dos restantes horas. 2 * 23 es 83888608, que es 838: 86: 08, por lo que el valor de tiempo máximo válido en este formato es 838: 59: 59.



Este formato es incluso menos conveniente que el actual, ya que requiere multiplicación y división para casi cualquier operación de tiempo (con la excepción del formato y análisis de cadenas, lo que demuestra una vez más que MySQL presta demasiada atención a la IO de cadenas y realmente no le importa la presencia de tipos. que sería conveniente para operaciones internas y protocolos no basados ​​en cadenas).



Los desarrolladores de MySQL han podido corregir este tipo muchas veces, o al menos proporcionar una alternativa que está libre de la limitación existente. El tipo TIME ha cambiado dos veces desde MySQL 3 hasta hoy, pero cada vez el extraño rango ha permanecido igual, quizás por razones de compatibilidad.



No puedo imaginar una situación en la que expandir el rango de un valor para un tipo podría romper la compatibilidad de la aplicación: ¿los tipos en MySQL tienen un comportamiento de desbordamiento específico? ¿Qué programador sensato se basaría en las restricciones internas del tipo de base de datos para validar algo en su aplicación? Si existe una persona así, ¿por qué demonios decidirá de repente transferir este ridículo límite de 838 horas al modelo de datos de su aplicación sin ningún cambio? Para ser honesto, ni siquiera quiero saber las respuestas a estas preguntas.



A pesar de un par de transformaciones importantes en la historia de MySQL, el tipo TIME sigue siendo incómodo y limitado. Y lo más destacado del programa aquí, en mi opinión, es el bit no utilizado "reservado para futuras extensiones". Espero que a largo plazo apunte al antiguo TIEMPO heredado, y para entonces MySQL y / o MariaDB tendrán un tipo de tiempo sensible como INTERVAL en PostgreSQL , que tiene un rango de ± 178.000.000 años y un microsegundo exactitud.



PD del traductor



Lea también en nuestro blog:






All Articles