Решения для вставки или обновления на SQL Server

Предположим, что структура таблицы MyTable(KEY, datafield1, datafield2...).

Часто я хочу либо обновить существующую запись, либо вставить новую запись, если она не существует.

По существу:

IF (key exists)
  run update command
ELSE
  run insert command

Как лучше всего написать это?

555
задан 19.05.2020, 15:14

11 ответов

не забывайте о транзакциях. Производительность хороша, но проста (ЕСЛИ СУЩЕСТВУЕТ..) подход очень опасен.
, Когда несколько потоков попытаются работать Вставлять-или-обновлять, можно легко получить нарушение первичного ключа.

Решения, предоставленные @Beau Crawford & @Esteban показывают общее представление, но подверженный ошибкам.

Для предотвращения мертвых блокировок и нарушений PK можно использовать что-то вроде этого:

begin tran
if exists (select * from table with (updlock,serializable) where key = @key)
begin
   update table set ...
   where key = @key
end
else
begin
   insert into table (key, ...)
   values (@key, ...)
end
commit tran

или

begin tran
   update table with (serializable) set ...
   where key = @key

   if @@rowcount = 0
   begin
      insert into table (key, ...) values (@key,..)
   end
commit tran
351
ответ дан 19.05.2020, 15:23
  • 1
    Несомненно, но если we' ре, собирающееся начинать говорить об устойчивости приложения there' s много других вещей думать о также. – Luke Bennett 19.05.2020, 15:24
  • 2
    Оба этих метода могут все еще перестать работать. Если два параллельных потока сделают то же на той же строке, то первый успешно выполнится, но вторая вставка перестанет работать из-за нарушения первичного ключа. Транзакция не гарантирует это, вставка успешно выполнится, даже если обновление перестало работать, потому что запись существовала. Гарантировать то любое количество параллельной транзакции успешно выполнится, НЕОБХОДИМО использовать блокировку. – Jean Vincent 19.05.2020, 15:24
  • 3
    @CashCow, последние победы, это - то, что ВСТАВЛЯЕТ, или ОБНОВЛЕНИЕ, как предполагается, делает: первый вставляет, вторые обновления запись. Добавление блокировки позволяет этому произойти в очень короткий период времени, предотвращая ошибку. – Jean Vincent 19.05.2020, 15:24
  • 4
    Вопрос, который задают для большей части производительного решения, а не самого безопасного. Пока транзакция добавляет безопасность к процессу, это также добавляет издержки. – Luke Bennett 19.05.2020, 15:24
  • 5
    @aku любая причина Вы использовали подсказки таблицы (" с (xxxx) ") в противоположность " УСТАНОВИТЕ УРОВЕНЬ ИЗОЛЯЦИИ ТРАНЗАКЦИИ SERIALIZABLE" незадолго до Вашего НАЧИНАТЬ TRAN? – EBarr 19.05.2020, 15:25

Сделайте UPSERT:

UPDATE MyTable SET FieldA=@FieldA WHERE Key=@Key

IF @@ROWCOUNT = 0
   INSERT INTO MyTable (FieldA) VALUES (@FieldA)

http://en.wikipedia.org/wiki/Upsert

160
ответ дан 19.05.2020, 15:15
  • 1
    You' корректное ре! Я полагал, что контрольная точка всегда была тем же. Но если контрольные точки масштабирования и вращения не являются тем же, то, конечно, there' s большая разница! – PFROLIM 23.01.2013, 20:27
  • 2
    Нарушения первичного ключа не должны происходить, если у Вас есть надлежащие примененные ограничения уникального индекса. Смысл ограничения должен предотвратить дублирующиеся строки от каждого случая. Это doesn' t имеют значение, сколько потоки пытаются вставить, база данных сериализирует по мере необходимости для осуществления ограничения... и если это doesn' t, тогда механизм бесполезен. Конечно, переносясь это в сериализированной транзакции сделало бы, это более корректное и менее восприимчивое к мертвым блокировкам или отказавший вставляет. – Triynko 19.05.2020, 15:15
  • 3
    @Triynko, я думаю, что @Sam Saffron подразумевала, что, если два + чередование потоков в правильной последовательности тогда SQL-сервер будет бросок , ошибка при указании на нарушение первичного ключа имела бы , произошел. Обертывание его в сериализуемой транзакции является корректным способом предотвратить ошибки в вышеупомянутом наборе операторов. – EBarr 19.05.2020, 15:16
  • 4
    Даже если у Вас будет первичный ключ, который является автоинкрементом, Ваше беспокойство тогда будет любыми ограничениями на уникальность данных, которые могли бы быть на таблице. – Seph 19.05.2020, 15:16
  • 5
    база данных должна заботиться о проблемах первичного ключа. То, что Вы говорите, - то, что, если сбои обновления и другой процесс становится там первым со вставкой, Ваша вставка перестанет работать. В этом случае у Вас есть состояние состязания так или иначе. Блокировка won' t изменяют то, что постусловие будет то, что один из процессов, который пытается писать, получит значение. – CashCow 19.05.2020, 15:16

Если Вы хотите к UPSERT больше чем одну запись за один раз, можно использовать СЛИЯНИЕ оператора ANSI SQL:2003 DML.

MERGE INTO table_name WITH (HOLDLOCK) USING table_name ON (condition)
WHEN MATCHED THEN UPDATE SET column1 = value1 [, column2 = value2 ...]
WHEN NOT MATCHED THEN INSERT (column1 [, column2 ...]) VALUES (value1 [, value2 ...])

Выезд Оператор СЛИЯНИЯ Имитации в SQL Server 2005 .

36
ответ дан 19.05.2020, 15:17
  • 1
    СЛИЯНИЕ восприимчиво к условиям состязания (см. weblogs.sqlteam.com/dang/archive/2009/01/31/… ), если Вы не заставляете его содержать блокировки certian. Кроме того, смотрите на MERGE' s производительность в SQL Profiler... я нахожу, что это обычно медленнее и генерирует больше чтений, чем альтернативные решения. – EBarr 19.05.2020, 15:17
  • 2
    В Oracle, выпуская оператор I MERGE думают блокировки таблица. То же происходит в SQL*Server? – Mike McAllister 19.05.2020, 15:18
  • 3
    @EBarr - Спасибо за ссылку на блокировки. Я обновил свой ответ для включения предложить подсказки блокировки. – Eric Weilnau 19.05.2020, 15:18
  • 4
    Также выезд mssqltips.com/sqlservertip/3074/… – Aaron Bertrand 19.05.2020, 15:18

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

DECLARE @RowExists bit
SET @RowExists = 0
UPDATE MyTable SET DataField1 = 'xxx', @RowExists = 1 WHERE Key = 123
IF @RowExists = 0
  INSERT INTO MyTable (Key, DataField1) VALUES (123, 'xxx')
-3
ответ дан 19.05.2020, 15:17

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

FirstSP:
If Exists
   Call SecondSP (UpdateProc)
Else
   Call ThirdSP (InsertProc)

Теперь, я не следую своему собственному совету очень часто, поэтому беру его с мелкой частицей соли.

-3
ответ дан 19.05.2020, 15:18
  • 1
    Это, возможно, было релевантно в древних версиях SQL Server, но современные версии имеют компиляцию уровня оператора. Ветвления и т.д. не являются проблемой, и использующий отдельные процедуры этих вещей не решает ни одной из проблем, свойственных от совершения выбора между обновлением и вставкой так или иначе... – Aaron Bertrand 19.05.2020, 15:18
IF EXISTS (SELECT * FROM [Table] WHERE ID = rowID)
UPDATE [Table] SET propertyOne = propOne, property2 . . .
ELSE
INSERT INTO [Table] (propOne, propTwo . . .)

Редактирование:

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

55
ответ дан 19.05.2020, 15:19
  • 1
    Мне все еще нравится этот лучше. upsert больше походит на программирование побочным эффектом, и я имею никогда замеченный piddly, мало кластерного индекса стремится того начального выбора вызвать проблемы производительности в реальной базе данных. – Eric Z Beard 19.05.2020, 15:20

В SQL Server 2008 можно использовать оператор

MERGE
2
ответ дан 19.05.2020, 15:21
  • 1
    большой , спасибо за быструю информацию. Возможно, взял меня дни или даже недели для понимания вещей. Спасибо за справку все! – Ziyan Junaideen 19.12.2019, 01:13
  • 2
    это - комментарий. в отсутствие любого фактического примера кода это точно так же, как много других комментариев к сайту. – swasheck 19.05.2020, 15:22

При использовании ADO.NET DataAdapter обрабатывает это.

, Если Вы хотите обработать его сами, это - путь:

Удостоверяются, что существует ограничение первичного ключа на Ваш столбец ключа.

Тогда Вы:

  1. Делают обновление
  2. , Если обновление перестало работать, потому что запись с ключом уже существует, сделайте вставку. Если обновление не перестало работать, Вы закончены.

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

-2
ответ дан 19.05.2020, 15:22

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

-6
ответ дан 19.05.2020, 15:22
  • 1
    It' s два вызова к DB that' s проблема, Вы заканчиваете удвоение количества распространений в прямом и обратном направлениях к DB. Если приложение совершает нападки, дб с большим количеством из вставляет/обновляет it' ll повреждают производительность. UPSERT является лучшей стратегией. – Kev 19.05.2020, 15:23
  • 2
    That' s два вызова к базе данных. – Chris Cudmore 19.05.2020, 15:23
  • 3
    Я don' t видят проблему с этим. – Clint Ecker 19.05.2020, 15:24

SQL Server MS 2008 представляет оператор MERGE, которому я верю, является частью стандарта SQL:2003. Поскольку многие показали, это не грандиозное предприятие обработать случаи строки, но при контакте с большими наборами данных, каждому нужен курсор со всеми проблемами производительности, которые приходят. Оператор MERGE будет очень одобренным дополнением при контакте с большими наборами данных.

2
ответ дан 19.05.2020, 15:25
  • 1
    Я никогда не должен был использовать курсор, чтобы сделать это с большими наборами данных. Вам просто нужно обновление, которое обновляет записи, которые соответствуют и вставка выбору вместо пункта значений, который уехал, соединяет с таблицей. – HLGEM 19.05.2020, 15:26

Посмотрите мой подробный ответ на очень похожий предыдущий вопрос

, @Beau Crawford является хорошим путем в SQL 2005 и ниже, хотя, если Вы предоставляете представителю, это должно перейти в первый парень к ТАК этому . Единственная проблема состоит в том, что для вставок это - все еще две операции IO.

мс Sql2008 представляет merge из стандарта SQL:2003:

merge tablename with(HOLDLOCK) as target
using (values ('new value', 'different value'))
    as source (field1, field2)
    on target.idfield = 7
when matched then
    update
    set field1 = source.field1,
        field2 = source.field2,
        ...
when not matched then
    insert ( idfield, field1, field2, ... )
    values ( 7,  source.field1, source.field2, ... )

Теперь это - действительно всего одна операция IO, но ужасный код:-(

369
ответ дан 19.05.2020, 15:28
  • 1
    Для пользователей с неанглийской Локалью, но парсингом американских дат, format = format.withLocale(Locale.US); иначе перестанет работать парсинг January.//Joda-новичок – Kennet 19.12.2019, 01:12
  • 2
    Для этого ответа действительно нужно обновленный для составления комментария Seph об этом не быть ориентированным на многопотоковое исполнение без HOLDLOCK. Согласно связанному сообщению, СЛИЯНИЕ неявно вынимает блокировку обновления, но выпускает его прежде, чем вставить строки, которые могут вызвать нарушения состояния состязания и первичного ключа на вставке. При помощи HOLDLOCK сохранены блокировки, пока вставка не происходит. – Triynko 19.05.2020, 15:28
  • 3
    @Seph that' s реальное удивление - своего рода сбой Microsoft там:-S я предполагаю, что означает, что Вам нужно HOLDLOCK для операций слияния в высоких ситуациях с параллелизмом. – Keith 19.05.2020, 15:28
  • 4
    @Ian Boyd - да, that' s SQL:2003 standard' s синтаксис, не upsert, что примерно все другие поставщики БД решили поддерживать вместо этого. upsert синтаксис является намного более хорошим способом сделать это, таким образом, по крайней мере MS должен был поддерживать его также - it' s не как it' s единственное нестандартное ключевое слово в T-SQL – Keith 19.05.2020, 15:29
  • 5
    какой-либо комментарий к блокировке подсказывает в других ответах? (скоро узнает, но если it' s рекомендуемый путь, я рекомендую добавить его на ответе), – eglasius 19.05.2020, 15:29
  • 6
    Посмотрите здесь weblogs.sqlteam.com/dang/archive/2009/01/31/… для ответа о том, как препятствовать тому, чтобы условия состязания вызвали ошибки, которые могут произойти даже когда с помощью MERGE синтаксис. – Seph 19.05.2020, 15:29

Теги

Похожие вопросы