Каковы возможные способы избежать дублирования, когда вы не можете добавить уникальный индекс

10

Я застрял в проблеме параллелизма.

Это типичная проблема, когда пользователь отправляет 2 или 3 транзакции для сохранения некоторых данных, которые НЕ ДОЛЖНЫ дублироваться в БД, в случае дублированной записи вы должны вернуть ошибку.

Эта проблема проста, когда вы можете просто добавить индекс (уникальный) в столбец, где вы храните хеш.

Но в этом случае у меня огромная таблица (возможно, миллионы записей), и я не могу просто изменить таблицу.

Фактически, у нас есть столбец, в котором мы храним хеш данных, которые не должны дублироваться, но уникальный индекс не был установлен.

Я пытаюсь на своем коде Java, чтобы проверить, существует ли непосредственно перед сбросом, все еще получая дубликаты.

Мои возможные решения для этого:

  • Создайте триггер, который проверяет, существует ли на столе хеш, который я пытаюсь вставить.
  • Создайте другую таблицу для хранения уникальных индексов для этой таблицы и добавьте внешний ключ в основную таблицу.
  • Сядьте на позу плода и плачь
rafuru
источник
Ваша проверка хэша не удалась из-за коллизий хешей или ошибки в проверке?
candied_orange
4
Я не получил ваш вопрос. Таким образом, вместо того, чтобы индексировать один раз для всей вашей огромной таблицы с миллионами записей, вы предпочитаете читать для каждого следующего миллиона записей, которые вы добавите, существующие миллионы, чтобы искать двойные числа? или дублируете некоторую информацию и добавляете объединения для проверки?
Кристоф
Проблема в том, что для внесения этого изменения меня предупредили, что нам нужно много места и длительное время простоя для нашего сервиса, чтобы выполнить некоторые требования, наш сервис не может быть остановлен более чем на 2 часа в месяц. Я знаю, что лучший способ - провести техобслуживание на этом столе, но сейчас я не могу этого сделать, поэтому нам нужно обойти это.
Рафуру
4
Я не понимаю - почему добавление триггера или добавление другой таблицы для «эмуляции» индекса занимает меньше времени, чем простое добавление индекса в существующую таблицу?
Док Браун
2
@rafuru: кто сказал, что вам нужно создать уникальный индекс? Стандартный, неуникальный индекс, вероятно, все, что вам нужно, чтобы быстро найти все строки с одинаковым значением хеш-функции.
Док Браун

Ответы:

3

Есть несколько возможных сценариев, которые легко решить, и пагубный, которых нет.

Для пользователя, который вводит значение, а затем вводит то же значение через некоторое время простым SELECT, прежде чем INSERT обнаружит проблему. Это работает для случая, когда один пользователь отправляет значение, а через некоторое время другой пользователь отправляет то же значение.

Если пользователь отправляет список значений с дубликатами - скажем, {ABC, DEF, ABC} - в одном вызове кода, приложение может обнаружить и отфильтровать дубликаты, возможно, выдав ошибку. Вам также необходимо проверить, что БД не содержит каких-либо уникальных значений перед вставкой.

Сложный сценарий, когда запись одного пользователя находится внутри СУБД одновременно с записью другого пользователя, и они записывают одно и то же значение. Тогда у вас есть гонка условие между ними. Поскольку СУБД является (скорее всего - вы не говорите, какую вы используете) превентивной многозадачной системой, любая задача может быть приостановлена ​​в любой момент ее выполнения. Это означает, что задача user1 может проверить, что строки не существует, затем задача user2 может проверить, что строки нет, затем задача user1 может вставить эту строку, а задача user2 - эту строку. В каждой точке задачи индивидуально счастливы, они делают правильные вещи. Однако во всем мире возникает ошибка.

Обычно СУБД справится с этим, установив блокировку соответствующего значения. В этой задаче вы создаете новую строку, поэтому блокировать пока нечего. Ответ - блокировка диапазона. Это предполагает блокировку диапазона значений, независимо от того, существуют они в настоящее время или нет. После блокировки этот диапазон не может быть доступен для другой задачи, пока блокировка не будет снята. Чтобы получить блокировки диапазона, вы должны указать уровень изоляции SERIALIZABLE . Феномен другой задачи, крадущейся подряд после проверки вашей задачи, называется фантомными записями .

Установка уровня изоляции на Serializable для всего приложения будет иметь последствия. Пропускная способность будет уменьшена. Другие условия гонки, которые работали достаточно хорошо в прошлом, могут начать давать ошибки сейчас. Я бы предложил установить его на соединение, которое выполняет ваш код, вызывающий дубликаты, и оставить оставшуюся часть приложения как есть.

Альтернативой на основе кода является проверка после записи, а не до. Сделайте INSERT, затем посчитайте количество строк с этим значением хеша. При наличии дубликатов откатите действие. Это может иметь некоторые ошибочные результаты. Скажем, задача 1 записывает, затем задачу 2. Затем задача 1 проверяет и находит дубликат. Откатывается, хотя это было первым. Точно так же обе задачи могут обнаружить дубликат и оба отката. Но, по крайней мере, у вас будет сообщение для работы, механизм повторных попыток и никаких новых дубликатов. Откаты не одобряются, очень похоже на использование исключений для управления потоком программ. Обратите внимание, что всеработа в транзакции будет отменена, а не только запись, вызывающая дубликаты. И вам придется иметь явные транзакции, которые могут уменьшить параллелизм. Проверка дубликатов будет ужасно медленной, если у вас нет индекса для хеша. Если вы это сделаете, вы можете сделать его уникальным!

Как вы прокомментировали, реальное решение - это уникальный индекс. Мне кажется, что это должно вписаться в ваше окно обслуживания (хотя, конечно, вы знаете свою систему лучше). Скажем, хеш составляет восемь байтов. Для ста миллионов строк это около 1 ГБ. Опыт показывает, что разумное количество оборудования может обработать эти несколько строк за одну-две минуты. Дублирующая проверка и удаление добавят к этому, но могут быть записаны заранее. Это только в стороне, хотя.

Майкл Грин
источник
2

Фактически, у нас есть столбец, в котором мы храним хеш данных, которые не должны дублироваться, но уникальный индекс не был установлен.

Проверка коллизий хешей - хороший первый шаг, но будьте осторожны, вы не можете гарантировать, что одна и та же программа выдаст тот же хеш для тех же данных, если она будет перезапущена . Многие «быстрые» хеш-функции используют встроенный prng, который высеивается во время запуска программы. Используйте криптографический хеш, если хеш должен быть всегда одинаковым, несмотря на то, что вы делаете в этом приложении. Обратите внимание, что вам не нужен хороший или безопасный криптографический хеш.

Второй шаг - проверка фактического равенства данных, поскольку даже самые лучшие хеш-функции иногда приводят к коллизиям, поскольку вы (как правило) уменьшаете энтропию ваших данных.

Так:

Шаг 1: проверьте, нет ли у вас столкновения с криптографическим хешем

Шаг 2: если хэши совпадают, проверьте, что фактические данные совпадают

Turksarama
источник
Я не вижу, как это отвечает на вопрос. Предположим на мгновение, что доступный столбец хеш-функции заполнен детерминированной хэш-функцией (в противном случае любая попытка использовать ее не имеет смысла). Насколько я понимаю, проблема в том, что для этого столбца хэша в базе данных нет индекса, поэтому даже первый шаг в вашем ответе - проверка наличия коллизии - все равно потребует полного сканирования таблицы для каждой новой записи в таблице с несколько миллионов записей, которые, вероятно, станут слишком медленными.
Док Браун
Это лучшее, что вы можете сделать, не создавая индекс, как и задавался вопрос. Сканирование хэша, по крайней мере, означает, что вам нужно проверять только один столбец, что намного быстрее, чем проверять, сколько столбцов они должны были бы проверить.
Турксарама
Я почти уверен, что даже когда создание индекса невозможно (что в данном случае, вероятно, возможно), оригинальное предложение ОП « создать другую таблицу для хранения уникальных индексов для этой таблицы и добавить внешний ключ в основную таблицу » делает многое больше смысла.
Док Браун
Детерминированный хеш и криптографический хеш - это две ортогональные концепции, не так ли? криптографический хеш может не быть детерминированным, и наоборот, детерминированный хеш вполне может не иметь криптографической силы.
Newtopian
Это не одно и то же, но они не ортогональны. Криптографические хеши - это подмножество детерминированных хэшей, но никто действительно не потрудится делать некриптографические детерминированные хэши, если вы по какой-то причине не хотите, чтобы они были обратимыми.
Турксарама
2

Создайте новую таблицу с уникальным первичным ключом

На стороне клиента начните генерировать идентификаторы GUID для каждой записи, чтобы вы могли обнаружить простые повторные отправки.

Поместите новые записи в новую таблицу, чтобы, по крайней мере, вы подходили для новых поступающих данных.

Есть столбец в новой таблице "CheckedAgainstOldData"

Иметь бэкэнд-задачу, которая выполняет любую текущую медленную проверку хеша, чтобы увидеть, может ли она найти дубликат в старых данных и установить соответствующий флаг, отклонить дубликаты на этом этапе, отправив уведомление клиенту.

Между тем есть еще одна бэкэнд-задача, которая перемещает данные из старой таблицы в новую, проверяя дубликаты с помощью вашей хэш-проверки и генерируя GUID.

Вы можете оставить эту задачу запущенной на несколько дней (если требуется), передавая данные без простоев.

После завершения передачи вы можете отключить медленный процесс «CheckedAgainstOldData». и перенести все данные в одну таблицу.

Честно говоря, если проблема настолько серьезна, как вы описываете, а программное обеспечение устарело, у вас будут тысячи дубликатов.

Ewan
источник
1

Предполагая, что данные, поступающие от «пользователя», означают, что кто-то сидит за клавиатурой, и что обман возникает из-за того, что два пользователя одновременно вводят одни и те же данные. Попробуйте добавить функцию, которая вызывает случайную задержку в начале триггера. Дайте ему минимум того времени, которое требуется для записи новой записи в таблицу, и, возможно, максимум не более наноцентрия или около того. Таким образом, когда вы получаете запросы на дублирование, первый должен быть выполнен, а триггер существования должен дать правильный результат. (Пояснение: каждый вызов должен иметь свое собственное уникальное время случайной задержки, по тем же принципам, что и протокол ALOHA )

Грегор у
источник