Ci sono un paio di possibili scenari facili da risolvere e uno pernicioso che non lo è.
Per un utente che inserisce un valore, inserisce lo stesso valore qualche tempo dopo una semplice SELECT prima che l'INSERT rilevi il problema. Questo funziona per il caso in cui un utente invia un valore e qualche tempo dopo un altro utente invia lo stesso valore.
Se l'utente invia un elenco di valori con duplicati - ad esempio {ABC, DEF, ABC} - in una singola chiamata del codice l'applicazione può rilevare e filtrare i duplicati, forse generando un errore. Dovrai anche verificare che il DB non contenga nessuno dei valori univoci prima dell'inserto.
Lo scenario difficile è quando la scrittura di un utente si trova all'interno del DBMS contemporaneamente alla scrittura di un altro utente e sta scrivendo lo stesso valore. Quindi hai una gara una condizione tra loro. Poiché il DBMS è (molto probabilmente - non si dice quale si sta utilizzando) un sistema di 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 una riga esistente, quindi l'attività di user2 può controllare che non ci sia una 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 di fare la cosa giusta. Globalmente si verifica comunque un errore.
Normalmente un DBMS gestirà questo bloccando il valore in questione. In questo problema stai creando una nuova riga quindi non c'è ancora nulla da bloccare. La risposta è un blocco di gamma. Come suggerisce, questo blocca un intervallo di valori, indipendentemente dal fatto che esistano o no. Una volta bloccato, non è possibile accedere a quell'intervallo da un'altra attività finché non viene rilasciato il blocco. Per ottenere blocchi di intervalli devi specificare e il livello di isolamento di SERIALIZABLE . Il fenomeno di un'altra attività di nascosto in una riga dopo che l'attività è stata controllata è noto come record fantasma .
L'impostazione del livello di isolamento su Serializable su tutta l'applicazione avrà implicazioni. La velocità sarà ridotta. Altre condizioni di gara che hanno funzionato abbastanza bene in passato potrebbero iniziare a mostrare errori ora. Ti suggerisco di impostarlo sulla connessione che esegue il tuo codice di induzione duplicata e lasciando il resto dell'applicazione così com'è.
Un'alternativa basata su codice è di controllare dopo la scrittura piuttosto che prima. Quindi fai l'INSERT, quindi conta il numero di righe che hanno quel valore hash. Se ci sono duplicati, esegui il rollback dell'azione. Questo può avere dei risultati perversi. Pronunciare l'attività 1, quindi scrivere l'attività 2. Quindi l'attività 1 controlla e trova un duplicato. Si arretra anche se era il primo. Allo stesso modo entrambe le attività possono rilevare il duplicato e il 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. Si noti bene che il tutto funzionante 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à terribilmente lento a meno che non si disponga di 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 rientrare nella finestra di manutenzione (anche se ovviamente si conosce meglio il proprio sistema). Supponiamo che l'hash sia otto byte. Per cento milioni di righe vale circa 1 GB. L'esperienza suggerisce che un ragionevole numero di hardware elaborerebbe queste numerose 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 accostamento, però.