УНИКАЛЬНОЕ ограничение перед проверкой перед INSERT

У меня есть таблица SQL-сервера RealEstate с столбцами - Id, Property, Property_Value. Эта таблица имеет около 5-10 миллионов строк и может увеличиться еще больше в будущем. Я хочу вставить строку только в том случае, если в этой таблице не существует комбинация Id, Property, Property_Value.

Таблица примеров -

1,Rooms,5
1,Bath,2
1,Address,New York
2,Rooms,2
2,Bath,1
2,Address,Miami

Вставка 2,Address,Miami НЕ ДОЛЖНА. Но, 2,Price,2billion в порядке. Мне любопытно узнать, что является "лучшим" способом сделать это и почему. Почему часть важна для меня. Два способа проверки:

  • На уровне приложения - приложение должно проверить, существует ли строка до того, как она вставляет строку.
  • На уровне базы данных - установите уникальные ограничения для всех 3 столбцов и дайте базе данных сделайте проверку вместо человека/приложения.

Есть ли какой-нибудь сценарий, в котором один лучше другого?

Спасибо.

PS: Я знаю, что есть аналогичный вопрос, но он не отвечает на мою проблему - Уникальное ограничение против предварительной проверки Кроме того, я думаю, что UNIQUE применим ко всем базам данных, поэтому я не думаю, что я должен удалить теги mysql и oracle.

Ответ 1

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

Однако, по-моему, обработка исключений имеет несколько небольших преимуществ:

  • Обработка исключений позволяет избежать возможного состояния гонки. Метод "check, then insert" может завершиться неудачно, если другой процесс вставляет запись между вашей проверкой и вашей вставкой. Таким образом, даже если вы делаете "проверить, а затем вставить", вы все еще хотите обработать исключение в вставке, и если вы уже выполняете обработку исключений, тогда вы можете также отказаться от первоначальной проверки.

  • Если ваш код не является хранимой процедурой и должен взаимодействовать с базой данных через сеть (т.е. приложение и db находятся не в одном окне), вы должны избегать двух отдельных сетевых вызовов ( один для проверки, а другой для вставки) и делает это с помощью обработки исключений, обеспечивает простой способ обработки всего объекта одним сетевым вызовом. Теперь есть несколько способов сделать метод "проверить, а затем вставить", избегая при этом второго сетевого вызова, но просто поймать исключение, скорее всего, будет самым простым способом.

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

  • Создание уникального ограничения будет медленным на очень больших таблицах, и это вызовет удар производительности для каждой отдельной вставки в эту таблицу. В действительно больших базах данных вам также необходимо запланировать дополнительное пространство на диске, потребляемое уникальным индексом, используемым для принудительного ограничения.
  • С другой стороны, это может сделать выбор из таблицы быстрее, если ваши запросы могут воспользоваться этим индексом.

Я также хотел бы отметить, что если вы находитесь в ситуации, когда то, что вы на самом деле хотите сделать, это "update else insert" (т.е. если запись с уникальным значением уже существует, вы хотите обновить эту запись, иначе вы вставьте новую запись), то, что вы на самом деле хотите использовать, это ваш конкретный метод базы данных UPSERT, если он есть. Для SQL Server и Oracle это будет оператор MERGE.

Ответ 2

В зависимости от стоимости # 1 (делая поиск) разумно, я бы сделал и то, и другое. По крайней мере, в Oracle, которая является базой данных, у меня больше всего опыта.

Обоснование:

  • Уникальные/первичные ключи должны быть основной частью вашего проекта модели данных, я не вижу причин, чтобы не реализовывать их - если у вас так много данных, что производительность страдает от сохранения уникального индекса:
    • что много данных
    • отделить его или архивировать его от вашей работы OLTP.
  • Чем больше ограничений у вас есть, тем безопаснее ваши данные против ошибок логики приложения.
  • Если вы проверите, что сначала существует строка, вы можете легко извлечь другую информацию из этой строки, чтобы использовать ее как часть сообщения об ошибке, или иначе развернуть логику приложения, чтобы справиться с дублированием.
  • В Oracle откат утверждений DML относительно дорог, потому что по умолчанию Oracle ожидает успеха (т.е. COMMIT изменений, которые были написаны).

Ответ 3

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

Ссылка - http://www.celticwolf.com/blog/2010/04/27/what-is-a-race-condition/

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

Условие гонки состоит в том, что две или более программы (или независимые части одной программы) все пытаются получить некоторый ресурс одновременно, что приводит к некорректному ответу или конфликту. Этот ресурс может представлять собой информацию, например, следующее доступное время встречи, или это может быть эксклюзивный доступ к чему-то, например, к электронной таблице. Если вы когда-либо использовали Microsoft Excel для редактирования документа на общем диске, вероятно, у вас был опыт работы с Excel, который кто-то еще редактировал таблицу. Это сообщение об ошибке является способом Excels для корректной обработки потенциального состояния гонки и предотвращения ошибок.

Общей задачей для программ является определение следующего доступного значения какого-либо типа, а затем назначение его. Этот метод используется для номеров счетов, идентификаторов студентов и т.д. Его старая проблема, которая была решена ранее. Одним из наиболее распространенных решений является предоставление базы данных, которая хранит данные для генерации числа. Существуют и другие решения, и все они имеют свои сильные и слабые стороны.

К сожалению, программисты, которые не знают эту область или просто плохо при программировании, часто пытаются сворачивать свои собственные. Умные быстро обнаруживают, что это гораздо более сложная проблема, чем кажется, и искать существующие решения. Плохие никогда не видят проблемы или, как только они это делают, настаивают на том, чтобы сделать их неработоспособное решение все более сложным, не исправляя ошибки. Давайте возьмем пример идентификатора студента. Программист-неофит говорит: "Чтобы знать, что должен быть следующий номер ученика, просто получите последний номер ученика и увеличьте его". Вот что происходит под капотом:

  • Бетти, админ. помощник в приемной комиссии запускает программу управления студентами. Обратите внимание, что это действительно просто копия программы, которая работает на ее ПК. Он разговаривает с сервером базы данных по сети школ, но не имеет возможности поговорить с другими копиями программы, запущенной на других компьютерах.
  • Бетти создает новую студенческую запись для Боба Смита, вводя всю информацию.
  • Пока Бетти делает свой ввод данных, Джордж, другой администратор. ассистент, запускает программу управления студентами на своем ПК и начинает создавать запись для Джины Верде.
  • Джордж - более быстрый машинист, поэтому он заканчивается одновременно с Бетти. Они оба одновременно нажимают кнопку "Сохранить" .
  • Программа Bettys подключается к серверу базы данных и получает самый высокий номер студента, который используется 5012.
  • Программа Жоржа, в то же время, получает тот же ответ на тот же вопрос.
  • Обе программы принимают решение о том, что новый идентификатор студента для записи, который они сохраняют, должен быть 5013. Они добавляют эту информацию в запись и затем сохраняют ее в базе данных.
  • Теперь Боб Смит (студент Беттис) и Джина Верде (ученик Жоржа) имеют одинаковый идентификатор студента.

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

Когда я описываю эту проблему для людей, обычная реакция: "Но как часто это произойдет на практике? Никогда, верно?". Неправильно. Во-первых, когда ввод данных выполняется вашим персоналом, он обычно выполняется в течение относительно небольшого периода времени всеми. Это увеличивает вероятность перекрытия. Если рассматриваемая заявка представляет собой веб-приложение, открытое для широкой публики, вероятность того, что два человека нажимают кнопку "Сохранить" одновременно, еще выше. Я видел это в производственной системе в последнее время. Это было веб-приложение в публичной бета-версии. Уровень использования был довольно низким, и каждый день подписывался только несколько человек. Тем не менее, шесть пар людей смогли получить идентичные идентификаторы в течение нескольких месяцев. В случае, если вы задаетесь вопросом, нет, ни я, ни кто-либо из моей команды не написал этот код. Однако мы были очень удивлены, сколько раз эта проблема возникала. Оглядываясь назад, мы не должны были быть. Это действительно простое применение Закона Мерфиса.

Как избежать этой проблемы? Самый простой способ - использовать существующее решение проблемы, которая была хорошо протестирована. Все основные базы данных (MS SQL Server, Oracle, MySQL, PostgreSQL и т.д.) Имеют возможность увеличивать число без создания дубликатов. MS SQL-сервер называет его столбцом "identity", в то время как MySQL называет его столбцом "auto number", но функция такая же. Всякий раз, когда вы вставляете новую запись, автоматически создается новый идентификатор и гарантированно будет уникальным. Это изменит приведенный выше сценарий следующим образом:

  • Бетти, админ. помощник в приемной комиссии запускает программу управления студентами. Обратите внимание, что это действительно просто копия программы, которая работает на ее ПК. Он разговаривает с сервером базы данных по сети школ, но не имеет возможности поговорить с другими копиями программы, запущенной на других компьютерах.
  • Бетти создает новую студенческую запись для Боба Смита, вводя всю информацию.
  • Пока Бетти делает свой ввод данных, Джордж, другой администратор. ассистент, запускает программу управления студентами на своем ПК и начинает создавать запись для Джины Верде.
  • Джордж - более быстрый машинист, поэтому он заканчивается одновременно с Бетти. Они оба одновременно нажимают кнопку "Сохранить" .
  • Программа Bettys подключается к серверу базы данных и передает ей сохраненную запись.
  • Программа Жоржа в то же время передает другую сохраненную запись.
  • Сервер базы данных помещает обе записи в очередь и сохраняет их по одному, присваивая им следующий доступный номер.
  • Теперь Боб Смит (студент Беттис) получает ID 5013, а Джина Верде (ученик Жоржа) получает идентификатор 5014.

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

Существует, по крайней мере, один проверенный способ создания идентификаторов в программном обеспечении, а не в базе данных: uuids (Универсально уникальные идентификаторы). Однако uuid принимает вид xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx, где "x" обозначает шестнадцатеричную цифру (0-9 и a-f). Вы хотите использовать это для номера счета, идентификатора студента или какого-либо другого идентификатора, видимого публикой? Наверное, нет.

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

Ответ 4

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

В вашем случае определение таблицы может быть похоже на следующее:

 CREATE TABLE `real_estate` (
   `id` int(11) NOT NULL AUTO_INCREMENT,
   `property` varchar(255) DEFAULT NULL,
   `property_value` varchar(255) DEFAULT NULL,
   PRIMARY KEY (`id`),
   UNIQUE KEY `index_id_property_property_value` (`id`, `property`, `property_value`),
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8;