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
declaraciones
Sus 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 para UPDATE
declaraciones. Pero en el caso de las INSERT
declaraciones, omitir una columna de la declaración tiene una semántica diferente que en UPDATE
. Significa aplicar DEFAULT
. Los dos siguientes son semánticamente equivalentes:
INSERT INTO t (a, b) VALUES (1, 2);
INSERT INTO t (a, b, c) VALUES (1, 2, DEFAULT);
Esto no es cierto para UPDATE
, donde los dos primeros son semánticamente equivalentes, y el tercero tiene un significado completamente diferente:
-- These are the same
UPDATE t SET a = 1, b = 2;
UPDATE t SET a = 1, b = 2, c = c;
-- This is different!
UPDATE t SET a = 1, b = 2, c = DEFAULT;
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 columnas t
):
INSERT INTO t (a, b, c) VALUES (?, ?, ?);
Y dado c
que no está configurado, probablemente vinculará Java null
a la tercera variable de vinculación, porque muchos ORM tampoco pueden distinguir entre NULL
y DEFAULT
( jOOQ , por ejemplo, es una excepción aquí). Solo ven Java null
y no saben si esto significa NULL
(como en el valor desconocido) o DEFAULT
(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 :
- Tiene una
DEFAULT
cláusula
- Puede ser generado por un disparador
Volver a las UPDATE
declaraciones
Si 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:
CREATE TABLE x (a INT PRIMARY KEY, b INT, c INT, d INT);
INSERT INTO x VALUES (1, 1, 1, 1);
CREATE OR REPLACE TRIGGER t
BEFORE UPDATE OF c, d
ON x
BEGIN
IF updating('c') THEN
dbms_output.put_line('Updating c');
END IF;
IF updating('d') THEN
dbms_output.put_line('Updating d');
END IF;
END;
/
SET SERVEROUTPUT ON
UPDATE x SET b = 1 WHERE a = 1;
UPDATE x SET c = 1 WHERE a = 1;
UPDATE x SET d = 1 WHERE a = 1;
UPDATE x SET b = 1, c = 1, d = 1 WHERE a = 1;
Cuando ejecute lo anterior, verá el siguiente resultado:
table X created.
1 rows inserted.
TRIGGER T compiled
1 rows updated.
1 rows updated.
Updating c
1 rows updated.
Updating d
1 rows updated.
Updating c
Updating d
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 para SELECT
.
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"
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.