Ci sono un paio di possibili scenari che sono facili da risolvere e uno pernicioso che non lo è.
Per un utente che inserisce un valore, inserisce lo stesso valore qualche tempo dopo un semplice SELECT prima che INSERT rilevi il problema. Questo funziona nel caso in cui un utente invii un valore e qualche tempo dopo un altro utente invii lo stesso valore.
Se l'utente invia un elenco di valori con duplicati, ad esempio {ABC, DEF, ABC}, in un'unica chiamata del codice l'applicazione può rilevare e filtrare i duplicati, magari generando un errore. Sarà inoltre necessario verificare che il DB non contenga alcun valore univoco prima dell'inserimento.
Lo scenario difficile è quando la scrittura di un utente si trova nel DBMS contemporaneamente alla scrittura di un altro utente e stanno scrivendo lo stesso valore. Quindi hai una gara tra loro. Poiché il DBMS è (molto probabilmente - non si dice quale si sta utilizzando) un sistema multitasking preventivo, qualsiasi attività può essere messa in pausa in qualsiasi momento della sua esecuzione. Ciò significa che l'attività di user1 può controllare che non ci sia riga esistente, quindi l'attività di user2 può verificare che non ci sia riga esistente, quindi l'attività di user1 può inserire quella riga, quindi l'attività di user2 può inserire quella riga. Ad ogni punto i compiti sono individualmente felici che stanno facendo la cosa giusta. A livello globale, tuttavia, si verifica un errore.
Di solito un DBMS lo gestirà mettendo un blocco sul valore in questione. In questo problema stai creando una nuova riga, quindi non c'è ancora nulla da bloccare. La risposta è un blocco dell'intervallo. Come suggerisce ciò, blocca un intervallo di valori, sia che esistano attualmente o meno. Una volta bloccato, tale intervallo non è accessibile da un'altra attività fino a quando il blocco non viene rilasciato. Per ottenere i blocchi di intervallo è necessario specificare e il livello di isolamento di SERIALIZABLE . Il fenomeno di un'altra attività che si intrufola di seguito dopo aver verificato l'attività è noto come record fantasma .
L'impostazione del livello di isolamento su Serializable nell'intera applicazione avrà implicazioni. Il rendimento sarà ridotto. Altre condizioni di gara che hanno funzionato abbastanza bene in passato potrebbero iniziare a mostrare errori ora. Suggerirei di impostarlo sulla connessione che esegue il codice che induce i duplicati e di lasciare il resto dell'applicazione così com'è.
Un'alternativa basata sul codice è quella di controllare dopo la scrittura piuttosto che prima. Quindi fai INSERT, quindi conta il numero di righe che hanno quel valore di hash. Se ci sono duplicati rollback dell'azione. Ciò può avere esiti perversi. Pronuncia l'attività 1, quindi l'attività 2. Quindi l'attività 1 verifica e trova un duplicato. Torna indietro anche se era il primo. Allo stesso modo entrambe le attività possono rilevare il duplicato ed entrambi i rollback. Ma almeno avrai un messaggio con cui lavorare, un meccanismo di tentativi e nessun nuovo duplicato. I rollback sono disapprovati, proprio come usare le eccezioni per controllare il flusso del programma. Nota bene che tuttoil lavoro nella transazione verrà ripristinato, non solo la scrittura che induce i duplicati. E dovrai avere transazioni esplicite che potrebbero ridurre la concorrenza. Il controllo duplicato sarà orribilmente lento a meno che tu non abbia un indice sull'hash. Se lo fai puoi anche renderlo unico!
Come hai commentato, la vera soluzione è un indice univoco. Mi sembra che questo dovrebbe adattarsi alla finestra di manutenzione (anche se ovviamente conosci meglio il tuo sistema). Supponiamo che l'hash sia di otto byte. Per cento milioni di righe equivalgono a circa 1 GB. L'esperienza suggerisce che un ragionevole pezzo di hardware elaborerebbe queste righe in un minuto o due al massimo. Il controllo e l'eliminazione duplicati si aggiungeranno a questo, ma possono essere programmati in anticipo. Questo è solo un lato, però.