So che sei principalmente preoccupato UPDATE
e soprattutto delle prestazioni, ma come collega manutentore di "ORM", lascia che ti dia un'altra prospettiva sul problema della distinzione tra valori "modificati" , "null" e "predefiniti" , che sono tre cose diverse in SQL, ma probabilmente solo una cosa in Java e nella maggior parte degli ORM:
Traducendo la tua logica in INSERT
dichiarazioni
Le tue argomentazioni a favore della batchability e della cache delle dichiarazioni sono vere allo stesso modo per le INSERT
dichiarazioni che per le UPDATE
dichiarazioni. Ma nel caso di INSERT
dichiarazioni, omettere una colonna dall'istruzione ha una semantica diversa rispetto a UPDATE
. Significa candidarsi DEFAULT
. I seguenti due sono semanticamente equivalenti:
INSERT INTO t (a, b) VALUES (1, 2);
INSERT INTO t (a, b, c) VALUES (1, 2, DEFAULT);
Questo non è vero per UPDATE
, dove i primi due sono semanticamente equivalenti e il terzo ha un significato completamente diverso:
-- 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 maggior parte delle API client del database, incluso JDBC e, di conseguenza, JPA, non consente di associare DEFAULT
un'espressione a una variabile di bind, soprattutto perché i server non lo consentono neanche. Se si desidera riutilizzare la stessa istruzione SQL per i suddetti motivi di batchability e cacheability delle istruzioni, utilizzare la seguente istruzione in entrambi i casi (supponendo che (a, b, c)
siano presenti tutte le colonne t
):
INSERT INTO t (a, b, c) VALUES (?, ?, ?);
E poiché c
non è impostato, probabilmente assoceresti Java null
alla terza variabile di bind, poiché molti ORM non sono in grado di distinguere tra NULL
e DEFAULT
( jOOQ , ad esempio essendo un'eccezione qui). Vedono solo Java null
e non sanno se ciò significhi NULL
(come nel valore sconosciuto) o DEFAULT
(come nel valore non inizializzato).
In molti casi, questa distinzione non ha importanza, ma nel caso in cui la colonna c stia utilizzando una delle seguenti funzionalità, l'affermazione è semplicemente errata :
- Ha una
DEFAULT
clausola
- Potrebbe essere generato da un trigger
Torna alle UPDATE
dichiarazioni
Mentre quanto sopra è vero per tutti i database, posso assicurarti che il problema del trigger è vero anche per il database Oracle. Considera il seguente 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;
Quando esegui quanto sopra, vedrai il seguente output:
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
Come puoi vedere, l'istruzione che aggiorna sempre tutte le colonne attiverà sempre il trigger per tutte le colonne, mentre le istruzioni che aggiornano solo le colonne che sono state modificate genereranno solo quei trigger che sono in attesa di tali cambiamenti specifici.
In altre parole:
L'attuale comportamento di Hibernate che stai descrivendo è incompleto e potrebbe persino essere considerato errato in presenza di trigger (e probabilmente di altri strumenti).
Personalmente ritengo che l'argomento di ottimizzazione della cache delle query sia sopravvalutato nel caso di SQL dinamico. Certo, ci saranno alcune query in più in tale cache e un po 'più di analisi da fare, ma questo di solito non è un problema per le UPDATE
dichiarazioni dinamiche , molto meno di per SELECT
.
Il batching è certamente un problema, ma a mio avviso, un singolo aggiornamento non dovrebbe essere normalizzato per aggiornare tutte le colonne solo perché c'è una leggera possibilità che l'istruzione sia accettabile. È probabile che l'ORM possa raccogliere sotto-lotti di istruzioni identiche consecutive e raggruppare quelli invece del "intero lotto" (nel caso in cui l'ORM sia persino in grado di tracciare la differenza tra "cambiato" , "null" e "predefinito"
UPDATE
è praticamente equivalente a unDELETE
+INSERT
(perché in realtà crea un nuovo V ersione della riga). Il sovraccarico è elevato e cresce con il numero di indici , specialmente se molte delle colonne che li compongono sono effettivamente aggiornate e l' albero (o qualsiasi altra cosa) usato per rappresentare l'indice necessita di un cambiamento significativo. Non è il numero di colonne che viene aggiornato ciò che è rilevante, ma se si aggiorna una parte di colonna di un indice.