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