Предположим, что структура таблицы MyTable(KEY, datafield1, datafield2...)
.
Часто я хочу либо обновить существующую запись, либо вставить новую запись, если она не существует.
По существу:
IF (key exists)
run update command
ELSE
run insert command
Какой лучший способ написать это?
Ответы:
не забывай о сделках. Производительность хорошая, но простой (ЕСЛИ СУЩЕСТВУЕТ) подход очень опасен.
Когда несколько потоков попытаются выполнить вставку или обновление, вы можете легко получить нарушение первичного ключа.
Решения, предоставленные @Beau Crawford & @Esteban, показывают общую идею, но подвержены ошибкам.
Чтобы избежать взаимоблокировок и нарушений PK, вы можете использовать что-то вроде этого:
или
источник
Смотрите мой подробный ответ на очень похожий предыдущий вопрос
@Beau Crawford's - хороший способ в SQL 2005 и ниже, хотя, если вы предоставляете репутацию, он должен обратиться к первому парню, чтобы ТАК это сделать . Единственная проблема заключается в том, что для вставок это все еще две операции ввода-вывода.
MS Sql2008 вводит
merge
из стандарта SQL: 2003:Теперь это действительно только одна операция ввода-вывода, но ужасный код :-(
источник
upsert
который все другие поставщики БД решили поддержать вместо этого.upsert
Синтаксис гораздо лучше способ сделать это, так что по крайней мере MS должны были поддерживать его - это не так, как это только нестандартное ключевое слово в T-SQLMERGE
синтаксиса.HOLDLOCK
для операций слияния в ситуациях с высоким параллелизмом.Сделать UPSERT:
http://en.wikipedia.org/wiki/Upsert
источник
Многие люди предложат вам использовать
MERGE
, но я предостерегаю вас от этого. По умолчанию он не защищает вас от условий параллелизма и состязаний более, чем несколько утверждений, и создает другие опасности:http://www.mssqltips.com/sqlservertip/3074/use-caution-with-sql-servers-merge-statement/
Даже с этим «более простым» синтаксисом, я все же предпочитаю такой подход (для краткости обработка ошибок опущена):
Многие люди предложат этот способ:
Но все это завершается тем, что вам может понадобиться прочитать таблицу дважды, чтобы найти строки, которые нужно обновить. В первом примере вам нужно будет найти строку (и) только один раз. (В обоих случаях, если при первоначальном чтении не найдено ни одной строки, происходит вставка.)
Другие предложат этот способ:
Однако это проблематично, если ни по какой другой причине, кроме как позволить SQL Server перехватывать исключения, которые вы могли бы предотвратить в первую очередь, намного дороже, за исключением редкого сценария, когда происходит почти каждая вставка. Я докажу здесь столько же:
источник
UPDATE target SET col = tmp.col FROM target INNER JOIN #tmp ON <key clause>; INSERT target(...) SELECT ... FROM #tmp AS t WHERE NOT EXISTS (SELECT 1 FROM target WHERE key = t.key);
Редактировать:
Увы, даже к моему собственному ущербу, я должен признать, что решения, которые делают это без выбора, кажутся лучше, так как они выполняют задачу с одним меньшим шагом.
источник
Если вы хотите использовать UPSERT для более чем одной записи одновременно, вы можете использовать оператор ANSI SQL: 2003 DML MERGE.
Ознакомьтесь с Mimicking MERGE Statement в SQL Server 2005 .
источник
Хотя уже довольно поздно комментировать это, я хочу добавить более полный пример, используя MERGE.
Такие операторы Insert + Update обычно называются операторами «Upsert» и могут быть реализованы с использованием MERGE в SQL Server.
Очень хороший пример приводится здесь: http://weblogs.sqlteam.com/dang/archive/2009/01/31/UPSERT-Race-Condition-With-MERGE.aspx
Выше также объясняются сценарии блокировки и параллелизма.
Я буду цитировать то же самое для справки:
источник
Замените имена таблиц и полей на все, что вам нужно. Позаботьтесь об использовании условия ON . Затем установите соответствующее значение (и тип) для переменных в строке DECLARE.
Приветствия.
источник
Вы можете использовать
MERGE
Statement, этот оператор используется для вставки данных, если они не существуют, или обновления, если они существуют.источник
Если вы выполняете UPDATE if-no-row-updated, а затем INSERT, попробуйте сначала выполнить INSERT, чтобы предотвратить состояние гонки (при условии отсутствия промежуточного DELETE).
Помимо избежания состояния гонки, если в большинстве случаев запись уже существует, это приведет к сбою INSERT, что приведет к потере ЦП.
Использование MERGE, вероятно, предпочтительнее для SQL2008 и выше.
источник
Это зависит от модели использования. Нужно смотреть на общую картину использования, не теряясь в деталях. Например, если шаблон использования обновлений составляет 99% после создания записи, то «UPSERT» является лучшим решением.
После первой вставки (попадания) это будут все обновления одного оператора, без ifs или buts. Условие «где» на вставке необходимо, иначе оно вставит дубликаты, и вы не хотите иметь дело с блокировкой.
источник
В MS SQL Server 2008 представлен оператор MERGE, который, как я считаю, является частью стандарта SQL: 2003. Как показали многие, не так уж сложно обрабатывать случаи с одной строкой, но при работе с большими наборами данных нужен курсор со всеми возникающими проблемами производительности. Заявление MERGE будет очень полезным дополнением при работе с большими наборами данных.
источник
Прежде чем все перейдут на HOLDLOCK-ы из-за страха от этих злобных пользователей, запускающих ваши sprocs напрямую :-), позвольте мне отметить, что вы должны гарантировать уникальность новых PK-ов по дизайну (идентификационные ключи, генераторы последовательностей в Oracle, уникальные индексы для внешние идентификаторы, запросы покрываются индексами). Это альфа и омега вопроса. Если у вас этого нет, то никакие HOLDLOCK-ы вселенной не спасут вас, и если у вас это есть, вам не нужно ничего, кроме UPDLOCK, при первом выборе (или сначала использовать обновление).
Sprocs обычно работают в очень контролируемых условиях и в предположении доверенного абонента (средний уровень). Это означает, что если простой паттерн upsert (update + insert или merge) когда-либо увидит дублирующую PK, это означает ошибку в вашем среднем уровне или дизайне таблицы, и хорошо, что SQL в этом случае выкочит ошибку и отклонит запись. Размещение HOLDLOCK в этом случае равнозначно исключениям при приеме пищи и приему потенциально ошибочных данных, помимо снижения производительности.
Сказав, что, используя MERGE или UPDATE, INSERT проще на вашем сервере и менее подвержен ошибкам, так как вам не нужно добавлять (UPDLOCK) для первого выбора. Кроме того, если вы делаете вставки / обновления небольшими партиями, вам нужно знать свои данные, чтобы решить, подходит ли транзакция или нет. Это просто набор несвязанных записей, тогда дополнительная «обволакивающая» транзакция будет пагубной.
источник
Действительно ли условия гонки имеют значение, если вы сначала попробуете обновление, а затем вставку? Допустим, у вас есть два потока, которые хотят установить значение ключа key :
Поток 1: значение = 1
Поток 2: значение = 2
Пример сценария состояния гонки
Другой поток завершается с ошибкой вставки (с ошибкой дубликата ключа) - поток 2.
Но; в многопоточной среде планировщик ОС определяет порядок выполнения потока - в приведенном выше сценарии, где мы имеем это условие гонки, именно ОС определила последовательность выполнения. Т.е. неправильно говорить, что «поток 1» или «поток 2» был «первым» с точки зрения системы.
Когда время выполнения так близко для потока 1 и потока 2, результат условия гонки не имеет значения. Единственное требование должно состоять в том, чтобы один из потоков определял результирующее значение.
Для реализации: Если обновление, сопровождаемое вставкой, приводит к ошибке «дубликат ключа», это следует рассматривать как успех.
Кроме того, конечно, никогда не следует предполагать, что значение в базе данных совпадает со значением, которое вы написали последним.
источник
В SQL Server 2008 вы можете использовать инструкцию MERGE
источник
Я пробовал приведенное ниже решение, и оно работает для меня, когда происходит параллельный запрос на оператор вставки.
источник
Вы можете использовать этот запрос. Работа во всех выпусках SQL Server. Это просто и понятно. Но вам нужно использовать 2 запроса. Вы можете использовать, если вы не можете использовать MERGE
ПРИМЕЧАНИЕ: Пожалуйста, объясните отрицательные ответы
источник
Если вы используете ADO.NET, DataAdapter справится с этим.
Если вы хотите справиться с этим самостоятельно, это способ:
Убедитесь, что в столбце ключей есть ограничение первичного ключа.
Затем вы:
Вы также можете сделать это наоборот, т.е. сначала выполнить вставку и выполнить обновление в случае сбоя вставки. Обычно первый способ лучше, потому что обновления делаются чаще, чем вставки.
источник
Выполнение if существует ... else ... подразумевает выполнение как минимум двух запросов (один для проверки, один для выполнения действия). Следующий подход требует только одного, где запись существует, два, если требуется вставка:
источник
Я обычно делаю то, что сказали некоторые из других постеров, сначала проверяя, существует ли он, а затем делаю правильный путь. При этом следует помнить одну вещь: план выполнения, кэшируемый sql, может быть неоптимальным для одного или другого пути. Я считаю, что лучший способ сделать это - вызвать две разные хранимые процедуры.
Так вот, я не очень часто следую своему собственному совету, поэтому принимайте его с небольшим количеством соли.
источник
Сделайте выбор, если вы получите результат, обновите его, если нет, создайте его.
источник