Есть несколько возможных сценариев, которые легко решить, и пагубный, которых нет.
Для пользователя, который вводит значение, а затем вводит то же значение через некоторое время простым SELECT, прежде чем INSERT обнаружит проблему. Это работает для случая, когда один пользователь отправляет значение, а через некоторое время другой пользователь отправляет то же значение.
Если пользователь отправляет список значений с дубликатами - скажем, {ABC, DEF, ABC} - в одном вызове кода, приложение может обнаружить и отфильтровать дубликаты, возможно, выдав ошибку. Вам также необходимо проверить, что БД не содержит каких-либо уникальных значений перед вставкой.
Сложный сценарий, когда запись одного пользователя находится внутри СУБД одновременно с записью другого пользователя, и они записывают одно и то же значение. Тогда у вас есть гонка условие между ними. Поскольку СУБД является (скорее всего - вы не говорите, какую вы используете) превентивной многозадачной системой, любая задача может быть приостановлена в любой момент ее выполнения. Это означает, что задача user1 может проверить, что строки не существует, затем задача user2 может проверить, что строки нет, затем задача user1 может вставить эту строку, а задача user2 - эту строку. В каждой точке задачи индивидуально счастливы, они делают правильные вещи. Однако во всем мире возникает ошибка.
Обычно СУБД справится с этим, установив блокировку соответствующего значения. В этой задаче вы создаете новую строку, поэтому блокировать пока нечего. Ответ - блокировка диапазона. Это предполагает блокировку диапазона значений, независимо от того, существуют они в настоящее время или нет. После блокировки этот диапазон не может быть доступен для другой задачи, пока блокировка не будет снята. Чтобы получить блокировки диапазона, вы должны указать уровень изоляции SERIALIZABLE . Феномен другой задачи, крадущейся подряд после проверки вашей задачи, называется фантомными записями .
Установка уровня изоляции на Serializable для всего приложения будет иметь последствия. Пропускная способность будет уменьшена. Другие условия гонки, которые работали достаточно хорошо в прошлом, могут начать давать ошибки сейчас. Я бы предложил установить его на соединение, которое выполняет ваш код, вызывающий дубликаты, и оставить оставшуюся часть приложения как есть.
Альтернативой на основе кода является проверка после записи, а не до. Сделайте INSERT, затем посчитайте количество строк с этим значением хеша. При наличии дубликатов откатите действие. Это может иметь некоторые ошибочные результаты. Скажем, задача 1 записывает, затем задачу 2. Затем задача 1 проверяет и находит дубликат. Откатывается, хотя это было первым. Точно так же обе задачи могут обнаружить дубликат и оба отката. Но, по крайней мере, у вас будет сообщение для работы, механизм повторных попыток и никаких новых дубликатов. Откаты не одобряются, очень похоже на использование исключений для управления потоком программ. Обратите внимание, что всеработа в транзакции будет отменена, а не только запись, вызывающая дубликаты. И вам придется иметь явные транзакции, которые могут уменьшить параллелизм. Проверка дубликатов будет ужасно медленной, если у вас нет индекса для хеша. Если вы это сделаете, вы можете сделать его уникальным!
Как вы прокомментировали, реальное решение - это уникальный индекс. Мне кажется, что это должно вписаться в ваше окно обслуживания (хотя, конечно, вы знаете свою систему лучше). Скажем, хеш составляет восемь байтов. Для ста миллионов строк это около 1 ГБ. Опыт показывает, что разумное количество оборудования может обработать эти несколько строк за одну-две минуты. Дублирующая проверка и удаление добавят к этому, но могут быть записаны заранее. Это только в стороне, хотя.