Cuando se trata de actualizar una fila, muchas herramientas ORM emiten una instrucción UPDATE que establece cada columna asociada a esa entidad en particular .
La ventaja es que puede procesar fácilmente las declaraciones de actualización, ya que la UPDATE
declaración es la misma sin importar qué atributo de entidad cambie. Además, incluso puede usar el almacenamiento en caché de instrucciones del lado del servidor y del lado del cliente también.
Entonces, si cargo una entidad y solo establezco una sola propiedad:
Post post = entityManager.find(Post.class, 1L);
post.setScore(12);
Todas las columnas serán cambiadas:
UPDATE post
SET score = 12,
title = 'High-Performance Java Persistence'
WHERE id = 1
Ahora, suponiendo que también tengamos un índice en la title
propiedad, ¿no debería el DB darse cuenta de que el valor no ha cambiado de todos modos?
En este artículo , Markus Winand dice:
La actualización en todas las columnas muestra el mismo patrón que ya hemos observado en las secciones anteriores: el tiempo de respuesta crece con cada índice adicional.
Me pregunto por qué es esta sobrecarga, ya que la base de datos carga la página de datos asociada del disco en la memoria y, por lo tanto, puede determinar si un valor de columna debe cambiarse o no.
Incluso para los índices, no es necesario reequilibrar nada, ya que los valores del índice no cambian para las columnas que no han cambiado, pero se incluyeron en la ACTUALIZACIÓN.
¿Es necesario navegar también por los índices B + Tree asociados a las columnas redundantes sin cambios, solo para que la base de datos se dé cuenta de que el valor de la hoja sigue siendo el mismo?
Por supuesto, algunas herramientas ORM le permiten ACTUALIZAR solo las propiedades modificadas:
UPDATE post
SET score = 12,
WHERE id = 1
Pero este tipo de ACTUALIZACIÓN no siempre se beneficia de las actualizaciones por lotes o el almacenamiento en caché de instrucciones cuando se cambian diferentes propiedades para diferentes filas.
fuente
UPDATE
es prácticamente equivalente a unaDELETE
+INSERT
(porque en realidad se crea un nuevo V ersión de la fila). La sobrecarga es alta y crece con el número de índices , especialmente si muchas de las columnas que las componen se actualizan realmente, y el árbol (o lo que sea) utilizado para representar el índice necesita un cambio significativo. No es el número de columnas que se actualizan lo que es relevante, sino si actualiza una parte de una columna de un índice.Respuestas:
Sé que está más preocupado
UPDATE
y sobre todo por el rendimiento, pero como compañero de mantenimiento de "ORM", permítame darle otra perspectiva sobre el problema de distinguir entre "cambiado" , valores "nulos" y "predeterminados" , que son tres cosas diferentes en SQL, pero posiblemente solo una cosa en Java y en la mayoría de los ORM:Traducir su justificación a
INSERT
declaracionesSus argumentos a favor de la capacidad de batchability y la capacidad de almacenamiento en caché de declaraciones son verdaderas de la misma manera para
INSERT
declaraciones que paraUPDATE
declaraciones. Pero en el caso de lasINSERT
declaraciones, omitir una columna de la declaración tiene una semántica diferente que enUPDATE
. Significa aplicarDEFAULT
. Los dos siguientes son semánticamente equivalentes:Esto no es cierto para
UPDATE
, donde los dos primeros son semánticamente equivalentes, y el tercero tiene un significado completamente diferente:La mayoría de las API de cliente de base de datos, incluido JDBC y, en consecuencia, JPA, no permiten vincular una
DEFAULT
expresión a una variable de vinculación , principalmente porque los servidores tampoco lo permiten. Si desea volver a utilizar la misma instrucción SQL por los motivos de capacidad de batchability y de capacidad de almacenamiento de información mencionados anteriormente, usaría la siguiente instrucción en ambos casos (suponiendo que(a, b, c)
estén todas las columnast
):Y dado
c
que no está configurado, probablemente vinculará Javanull
a la tercera variable de vinculación, porque muchos ORM tampoco pueden distinguir entreNULL
yDEFAULT
( jOOQ , por ejemplo, es una excepción aquí). Solo ven Javanull
y no saben si esto significaNULL
(como en el valor desconocido) oDEFAULT
(como en el valor no inicializado).En muchos casos, esta distinción no importa, pero en caso de que su columna c esté usando alguna de las siguientes características, la afirmación es simplemente incorrecta :
DEFAULT
cláusulaVolver a las
UPDATE
declaracionesSi bien lo anterior es cierto para todas las bases de datos, puedo asegurarle que el problema de activación también es cierto para la base de datos Oracle. Considere el siguiente SQL:
Cuando ejecute lo anterior, verá el siguiente resultado:
Como puede ver, la declaración que siempre actualiza todas las columnas siempre activará el activador para todas las columnas, mientras que las instrucciones que actualizan solo las columnas que han cambiado activarán solo los activadores que están escuchando dichos cambios específicos.
En otras palabras:
El comportamiento actual de Hibernate que está describiendo es incompleto e incluso podría considerarse incorrecto en presencia de desencadenantes (y probablemente otras herramientas).
Personalmente, creo que su argumento de optimización de caché de consulta está sobrevalorado en el caso de SQL dinámico. Claro, habrá algunas consultas más en ese caché, y un poco más de trabajo de análisis por hacer, pero esto generalmente no es un problema para las
UPDATE
declaraciones dinámicas , mucho menos que paraSELECT
.El procesamiento por lotes es ciertamente un problema, pero en mi opinión, una única actualización no debería normalizarse para actualizar todas las columnas solo porque hay una pequeña posibilidad de que la declaración sea procesable. Lo más probable es que el ORM pueda recopilar subgrupos de sentencias idénticas consecutivas y agruparlas en lugar del "lote completo" (en caso de que el ORM sea capaz de rastrear la diferencia entre "cambiado" , "nulo" y "predeterminado"
fuente
DEFAULT
caso de uso puede ser abordado por@DynamicInsert
. La situación de TRIGGER también se puede abordar mediante controles comoWHEN (NEW.b <> OLD.b)
o simplemente cambiar a@DynamicUpdate
.Creo que la respuesta es: es complicado . Traté de escribir una prueba rápida usando una
longtext
columna en MySQL, pero la respuesta no es concluyente. Prueba primero:Por lo tanto, hay una pequeña diferencia de tiempo entre el valor lento + cambiado y el valor lento + sin cambio. Así que decidí mirar otra métrica, que eran páginas escritas:
Por lo tanto, parece que el tiempo aumentó porque tiene que haber una comparación para confirmar que el valor en sí no se ha modificado, lo que en el caso de un texto largo de 1G lleva tiempo (porque está dividido en muchas páginas). Pero la modificación en sí no parece agitar el registro de rehacer.
Sospecho que si los valores son columnas regulares que están en la página, la comparación agrega solo un poco de sobrecarga. Y suponiendo que se aplique la misma optimización, estas no son operaciones cuando se trata de la actualización.
Respuesta larga
De hecho, creo que el ORM no debería eliminar las columnas que se han modificado ( pero no cambiado ), ya que esta optimización tiene efectos secundarios extraños.
Considere lo siguiente en pseudocódigo:
El resultado si el ORM fuera a "Optimizar" la modificación sin cambios:
El resultado si el ORM envió todas las modificaciones al servidor:
El caso de prueba aquí se basa en el
repeatable-read
aislamiento (valor predeterminado de MySQL), pero también existe una ventana de tiempo para elread-committed
aislamiento donde la lectura de sesión2 ocurre antes de la confirmación de sesión1.En otras palabras: la optimización solo es segura si emite un
SELECT .. FOR UPDATE
para leer las filas seguidas de unUPDATE
.SELECT .. FOR UPDATE
no usa MVCC y siempre lee la última versión de las filas.Editar: se aseguró de que el conjunto de datos del caso de prueba estuviera 100% en la memoria. Resultados de tiempo ajustados.
fuente