Hay un par de escenarios posibles que son fáciles de resolver y uno pernicioso que no lo es.
Para un usuario que ingresa un valor, luego ingresa el mismo valor un tiempo después, un simple SELECCIONAR antes de que INSERT detecte el problema. Esto funciona para el caso en que un usuario envía un valor y algún tiempo después otro usuario envía el mismo valor.
Si el usuario envía una lista de valores con duplicados, digamos {ABC, DEF, ABC}, en una sola invocación del código, la aplicación puede detectar y filtrar los duplicados, quizás arrojando un error. También deberá verificar que la base de datos no contenga ninguno de los valores únicos antes de la inserción.
El escenario complicado es cuando la escritura de un usuario está dentro del DBMS al mismo tiempo que la escritura de otro usuario, y están escribiendo el mismo valor. Entonces tienes una carrera una condición entre ellos. Dado que el DBMS es (muy probablemente, usted no dice cuál está usando) un sistema multitarea preventivo, cualquier tarea puede pausarse en cualquier momento de su ejecución. Eso significa que la tarea del usuario1 puede verificar que no haya una fila existente, luego la tarea del usuario2 puede verificar que no haya una fila existente, luego la tarea del usuario1 puede insertar esa fila, luego la tarea del usuario2 puede insertar esa fila. En cada punto, las tareas son individualmente felices porque están haciendo lo correcto. Sin embargo, a nivel mundial se produce un error.
Normalmente, un DBMS se encargaría de esto poniendo un bloqueo en el valor en cuestión. En este problema, está creando una nueva fila para que todavía no haya nada que bloquear. La respuesta es un bloqueo de rango. Como sugiere, esto bloquea un rango de valores, ya sean actuales o no. Una vez bloqueado, ese rango no puede ser accedido por otra tarea hasta que se libere el bloqueo. Para obtener bloqueos de rango, debe especificar un nivel de aislamiento de SERIALIZABLE . El fenómeno de otra tarea a escondidas después de que su tarea se haya verificado se conoce como registros fantasmas .
Establecer el nivel de aislamiento en Serializable en toda la aplicación tendrá implicaciones. El rendimiento se reducirá. Otras condiciones de carrera que funcionaron suficientemente bien en el pasado pueden comenzar a mostrar errores ahora. Sugeriría configurarlo en la conexión que ejecuta su código inductor duplicado y dejar el resto de la aplicación como está.
Una alternativa basada en código es verificar después de la escritura en lugar de antes. También haga INSERT, luego cuente el número de filas que tienen ese valor hash. Si hay duplicados, deshaga la acción. Esto puede tener algunos resultados perversos. Digamos que la tarea 1 escribe, luego la tarea 2. Luego, la tarea 1 verifica y encuentra un duplicado. Retrocede aunque fue el primero. Del mismo modo, ambas tareas pueden detectar el duplicado y la reversión. Pero al menos tendrá un mensaje con el que trabajar, un mecanismo de reintento y ningún duplicado nuevo. Los retrocesos están mal vistos, al igual que usar excepciones para controlar el flujo del programa. Tenga en cuenta que todosel trabajo en la transacción se revertirá, no solo la escritura que induce duplicados. Y tendrá que tener transacciones explícitas que pueden reducir la concurrencia. La verificación duplicada será terriblemente lenta a menos que tenga un índice en el hash. Si lo haces, ¡también puedes hacerlo único!
Como ha comentado, la solución real es un índice único. Me parece que esto debería encajar en su ventana de mantenimiento (aunque, por supuesto, conoce mejor su sistema). Digamos que el hash es de ocho bytes. Para cien millones de filas, eso es aproximadamente 1 GB. La experiencia sugiere que un poco de hardware razonable procesaría estas filas en un minuto o dos, como máximo. La comprobación y la eliminación duplicadas se sumarán a esto, pero se pueden programar con anticipación. Sin embargo, esto es solo un aparte.