Предположим, что структура таблицы MyTable(KEY, datafield1, datafield2...).
Часто я хочу либо обновить существующую запись, либо вставить новую, если она не существует.
По сути:
IF (key exists)
run update command
ELSE
run insert command
Как лучше всего это написать?
Рассмотрите возможность использования оператора EXCEPT, который был представлен в SQL Server 2005.


Сделайте выбор, если получите результат, обновите, если нет, создайте.
Это два обращения к базе данных.
Я не вижу в этом проблемы.
Проблема заключается в двух вызовах БД, вы удваиваете количество обращений к БД. Если приложение попадет в базу данных с большим количеством вставок / обновлений, это снизит производительность. UPSERT - лучшая стратегия.
он также создает состояние гонки нет?
IF EXISTS (SELECT * FROM [Table] WHERE ID = rowID)
UPDATE [Table] SET propertyOne = propOne, property2 . . .
ELSE
INSERT INTO [Table] (propOne, propTwo . . .)
Редактировать:
Увы, даже в ущерб себе, я должен признать, что решения, которые делают это без выбора, кажутся лучше, поскольку они выполняют задачу с одним шагом меньше.
Мне все еще больше нравится этот. Upsert больше похож на программирование с помощью побочного эффекта, и я видел, как никогда видел жалкий маленький поиск по кластеризованному индексу этого начального выбора, чтобы вызвать проблемы с производительностью в реальной базе данных.
Сделайте UPSERT:
UPDATE MyTable SET FieldA=@FieldA WHERE Key=@Key IF @@ROWCOUNT = 0 INSERT INTO MyTable (FieldA) VALUES (@FieldA)
http://en.wikipedia.org/wiki/Upsert
Нарушения первичного ключа не должны происходить, если к вам применены правильные ограничения уникальности индекса. Вся суть ограничения состоит в том, чтобы не допустить повторения повторяющихся строк. Неважно, сколько потоков пытается вставить, база данных будет сериализована по мере необходимости, чтобы обеспечить соблюдение ограничения ... а если нет, то движок бесполезен. Конечно, если обернуть это в сериализованную транзакцию, это сделает ее более правильной и менее уязвимой для взаимоблокировок или неудачных вставок.
@Triynko, я думаю, @Sam Saffron означал, что если два + потока чередуются в правильной последовательности, тогда sql server выдаст бросать ошибку, указывающую на нарушение первичного ключа имел бы. Оборачивание его в сериализуемую транзакцию - правильный способ предотвратить ошибки в приведенном выше наборе операторов.
Даже если у вас есть первичный ключ с автоматическим приращением, вас будут беспокоить любые уникальные ограничения, которые могут быть в таблице.
база данных должна позаботиться о проблемах с первичным ключом. Вы говорите, что если обновление не удастся, и другой процесс попадает туда первым с вставкой, ваша вставка не удастся. В этом случае у вас все равно есть состояние гонки. Блокировка не изменит того факта, что постусловие будет заключаться в том, что один из процессов, пытающихся записать, получит значение.
не забывайте о сделках. Производительность хорошая, но простой (ЕСЛИ СУЩЕСТВУЕТ ..) подход очень опасен. Когда несколько потоков будут пытаться выполнить вставку или обновление, вы можете легко получить нарушение первичного ключа.
Решения, предоставленные @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
Вопрос задан для наиболее эффективного решения, а не для самого безопасного. Хотя транзакция увеличивает безопасность процесса, она также увеличивает накладные расходы.
Конечно, но если мы собираемся начать говорить о стабильности приложения, есть много других вещей, о которых стоит подумать.
Оба эти метода все еще могут потерпеть неудачу. Если два параллельных потока делают то же самое в одной строке, первый будет успешным, но вторая вставка не удастся из-за нарушения первичного ключа. Транзакция не гарантирует, что вставка будет успешной, даже если обновление не удалось из-за существования записи. Чтобы гарантировать успешное выполнение любого количества одновременных транзакций, вы ДОЛЖНЫ использовать блокировку.
@aku по какой причине вы использовали подсказки таблицы ("with (xxxx)") вместо "SET TRANSACTION ISOLATION LEVEL SERIALIZABLE" непосредственно перед BEGIN TRAN?
@ Жан Винсент. Если два процесса пытаются установить разные значения для одной и той же записи, в любом случае возникает состояние гонки. По сути, вы не можете гарантировать, какое значение стоит в качестве постусловия.
@CashCow, последний выигрывает, это то, что должны делать INSERT или UPDATE: первый вставляет, второй обновляет запись. Добавление блокировки позволяет сделать это в очень короткие сроки, предотвращая ошибку.
Также см. Ответ @zvolkov на этот вопрос stackoverflow.com/questions/1488355/…
Я всегда думал, что использовать подсказки по блокировкам - это плохо, и мы должны позволить внутреннему механизму Microsoft определять блокировки. Является ли это явным исключением из правил?
Выполнение if exists ... else ... включает выполнение минимум двух запросов (один для проверки, один для выполнения действий). Следующий подход требует только одного, если запись существует, и двух, если требуется вставка:
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')
Обычно я делаю то, что сказано на некоторых других плакатах относительно того, что сначала проверяю, существует ли он, а затем делаю то, что является правильным путем. Одна вещь, которую вы должны помнить при этом, заключается в том, что план выполнения, кэшированный sql, может быть неоптимальным для того или иного пути. Я считаю, что лучший способ сделать это - вызвать две разные хранимые процедуры.
FirstSP: If Exists Call SecondSP (UpdateProc) Else Call ThirdSP (InsertProc)
Я не очень часто следую своему собственному совету, так что относитесь к нему с недоверием.
Это могло быть актуально в старых версиях SQL Server, но в современных версиях есть компиляция на уровне операторов. Форки и т. д. Не являются проблемой, и использование отдельных процедур для этих вещей в любом случае не решает никаких проблем, связанных с выбором между обновлением и вставкой ...
Если вы используете ADO.NET, DataAdapter справится с этим.
Если вы хотите справиться с этим самостоятельно, вот способ:
Убедитесь, что для ключевого столбца существует ограничение первичного ключа.
Затем вы:
Вы также можете сделать это наоборот, то есть сначала выполнить вставку и выполнить обновление, если вставка не удалась. Обычно первый способ лучше, потому что обновления выполняются чаще, чем вставки.
... и выполнение вставки в первую очередь (зная, что иногда это не удается) дорого обходится для SQL Server. sqlperformance.com/2012/08/t-sql-queries/error-handling
В SQL Server 2008 вы можете использовать оператор MERGE
это комментарий. в отсутствие каких-либо реальных примеров кода это точно так же, как и многие другие комментарии на сайте.
Очень старый, но было бы неплохо привести пример.
Если вы хотите выполнить UPSERT более одной записи за раз, вы можете использовать оператор ANSI SQL: 2003 DML MERGE.
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 ...])
Проверьте Имитация инструкции MERGE в SQL Server 2005.
В Oracle выполнение оператора MERGE I считать блокирует таблицу. То же самое происходит в SQL * Server?
MERGE восприимчив к условиям гонки (см. weblogs.sqlteam.com/dang/archive/2009/01/31/…), если вы не заставите его удерживать блокировки сертификатов. Кроме того, взгляните на производительность MERGE в SQL Profiler ... я обнаружил, что он обычно медленнее и генерирует больше операций чтения, чем альтернативные решения.
@EBarr - Спасибо за ссылку на замки. Я обновил свой ответ, включив в него подсказку о блокировке.
Также проверьте mssqltips.com/sqlservertip/3074/…
MS SQL Server 2008 представляет оператор MERGE, который, как я считаю, является частью стандарта SQL: 2003. Как показали многие, обработка случаев с одной строкой не представляет большого труда, но при работе с большими наборами данных необходим курсор со всеми возникающими проблемами производительности. Оператор MERGE будет очень желанным дополнением при работе с большими наборами данных.
Мне никогда не приходилось использовать курсор для этого с большими наборами данных. Вам просто нужно обновление, которое обновляет совпадающие записи, и вставку с выбором вместо предложения значений, которое оставило присоединение к таблице.
Смотрите мой подробный ответ на очень похожий предыдущий вопрос
@ Бо Кроуфорд - хороший способ в SQL 2005 и ниже, хотя, если вы предоставляете репутацию, он должен перейти к первый парень ТАК это. Единственная проблема в том, что для вставок это еще две операции ввода-вывода.
MS 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, ... )
Теперь это действительно всего одна операция ввода-вывода, но ужасный код :-(
@Ian Boyd - да, это синтаксис стандарта SQL: 2003, а не upsert, который решили поддерживать почти все другие поставщики баз данных. Синтаксис upsert - гораздо лучший способ сделать это, поэтому, по крайней мере, MS должна была его поддерживать - это не единственное нестандартное ключевое слово в T-SQL.
документация не показывает предложение where как допустимый синтаксис: technet.microsoft.com/en-us/library/bb510625.aspx
какие-либо комментарии к подсказке о блокировке в других ответах? (скоро узнаю, но если это рекомендуемый способ, рекомендую добавить его в ответ)
@eglasius это должно быть заключено в транзакцию, но это происходит как чтение для любого SQL insert или update. Использование merge - это одна операция, поэтому нет никаких шансов столкнуться с другим действием.
См. Здесь weblogs.sqlteam.com/dang/archive/2009/01/31/… для ответа о том, как предотвратить условия гонки, вызывающие ошибки, которые могут возникнуть даже при использовании синтаксиса MERGE.
@Seph, это настоящий сюрприз - что-то вроде провала Microsoft: -S Думаю, это означает, что вам нужен HOLDLOCK для операций слияния в ситуациях с высоким уровнем параллелизма.
@Keith Я не согласен с вашим утверждением, что это уродливый код :)
@theycallmemorty необходимо поддерживать ТРИ отдельных списка полей, это ненужный кошмар обслуживания, и это делает его уродливым. :-)
@moodboom - не только это; как UPSERT, так и UPDATE имеют поля в парах Set [field] = @value. INSERT имеет список полей и список значений, подходящих для пары полей, но не беда, когда у вас много полей.
@Keith yep, MERGE просто отвратительно, когда вы просто хотите апсерта, который настолько прост и распространен. Не уверен, что думали дизайнеры ANSI и MSSQL. Может, через три-четыре года ... вздох ...
Этот ответ действительно нуждается в обновлении, чтобы учесть комментарий Seph о том, что он не является потокобезопасным без HOLDLOCK. Согласно связанному сообщению, MERGE неявно снимает блокировку обновления, но снимает ее перед вставкой строк, что может вызвать состояние гонки и нарушения первичного ключа при вставке. При использовании HOLDLOCK блокировки сохраняются до тех пор, пока не произойдет вставка.
Редактор действительно добавил директиву HOLDLOCK, но OP откатил изменения.
@palswim они сделали, но они также переформатировали его и добавили другие вещи. Я принципиально склонен откатывать такие изменения - извините, я пропустил там одно стоящее изменение. Если бы они просто добавили отсутствующую директиву with, я бы оставил ее, и я сделал это сейчас.
Хотя довольно поздно комментировать это, я хочу добавить более полный пример с использованием MERGE.
Такие операторы Insert + Update обычно называются операторами «Upsert» и могут быть реализованы с помощью MERGE в SQL Server.
Здесь приведен очень хороший пример: http://weblogs.sqlteam.com/dang/archive/2009/01/31/UPSERT-Race-Condition-With-MERGE.aspx
Выше также описаны сценарии блокировки и параллелизма.
Я процитирую то же самое для справки:
ALTER PROCEDURE dbo.Merge_Foo2
@ID int
AS
SET NOCOUNT, XACT_ABORT ON;
MERGE dbo.Foo2 WITH (HOLDLOCK) AS f
USING (SELECT @ID AS ID) AS new_foo
ON f.ID = new_foo.ID
WHEN MATCHED THEN
UPDATE
SET f.UpdateSpid = @@SPID,
UpdateTime = SYSDATETIME()
WHEN NOT MATCHED THEN
INSERT
(
ID,
InsertSpid,
InsertTime
)
VALUES
(
new_foo.ID,
@@SPID,
SYSDATETIME()
);
RETURN @@ERROR;
Есть и другие вещи, о которых нужно беспокоиться с MERGE: mssqltips.com/sqlservertip/3074/…
Прежде чем все перейдут к HOLDLOCK-s из-за страха перед этими назойливыми пользователями, запускающими ваши sprocs напрямую :-), позвольте мне указать, что Вы должны гарантировать уникальность новых ПК по дизайну (ключи идентификации, генераторы последовательностей в Oracle, уникальные индексы для внешних идентификаторов, запросы, покрытые индексами). Это альфа и омега проблемы. Если у вас его нет, никакие HOLDLOCK-ы вселенной не спасут вас, и если у вас есть это, вам не нужно ничего, кроме UPDLOCK при первом выборе (или для использования обновления сначала).
Sprocs обычно работают в очень контролируемых условиях и с предположением о доверенном вызывающем абоненте (средний уровень). Это означает, что если простой шаблон upsert (обновление + вставка или слияние) когда-либо обнаруживает дублирующийся PK, это означает ошибку в вашем среднем уровне или дизайне таблицы, и хорошо, что SQL в таком случае выдаст ошибку и отклонит запись. Размещение HOLDLOCK в этом случае равносильно поглощению исключений и принятию потенциально ошибочных данных, помимо снижения производительности.
Сказав это, использование MERGE или UPDATE, затем INSERT проще на вашем сервере и менее подвержено ошибкам, поскольку вам не нужно помнить о добавлении (UPDLOCK) для первого выбора. Кроме того, если вы выполняете вставку / обновление небольшими партиями, вам необходимо знать свои данные, чтобы решить, подходит ли транзакция или нет. Если это всего лишь набор несвязанных записей, дополнительная «обволакивающая» транзакция будет вредна.
Если вы просто выполняете обновление, а затем вставляете его без какой-либо блокировки или повышенной изоляции, тогда два пользователя могут попытаться передать одни и те же данные обратно (я бы не стал считать это ошибкой на среднем уровне, если два пользователя попытались отправить точно такую же информацию на в то же время - во многом зависит от контекста, не так ли?). Они оба входят в обновление, которое возвращает 0 строк для обоих, затем они оба пытаются вставить. Один выигрывает, другой - исключение. Это то, чего люди обычно стараются избегать.
Имеют ли значение условия гонки, если вы сначала попробуете обновление, а затем вставку? Допустим, у вас есть два потока, которые хотят установить значение для ключа ключ:
Поток 1: значение = 1
Поток 2: значение = 2
Пример сценария состояния гонки
Другой поток не выполняет вставку (с ключом дублирования ошибки) - поток 2.
Но; в многопоточной среде планировщик ОС принимает решение о порядке выполнения потока - в приведенном выше сценарии, где у нас есть это состояние гонки, именно ОС определяла последовательность выполнения. То есть: неправильно говорить, что «поток 1» или «поток 2» был «первым» с точки зрения системы.
Когда время выполнения настолько близко для потока 1 и потока 2, результат состояния гонки не имеет значения. Единственное требование должно заключаться в том, чтобы один из потоков определял результирующее значение.
Для реализации: если обновление, за которым следует вставка, приводит к ошибке «дубликат ключа», это следует рассматривать как успешное.
Кроме того, конечно, никогда не следует предполагать, что значение в базе данных совпадает со значением, которое вы написали последним.
Если вы переходите по маршруту UPDATE if-no-rows-updated, а затем INSERT, подумайте о том, чтобы сначала выполнить INSERT, чтобы предотвратить состояние гонки (при условии отсутствия промежуточного DELETE)
INSERT INTO MyTable (Key, FieldA)
SELECT @Key, @FieldA
WHERE NOT EXISTS
(
SELECT *
FROM MyTable
WHERE Key = @Key
)
IF @@ROWCOUNT = 0
BEGIN
UPDATE MyTable
SET FieldA=@FieldA
WHERE Key=@Key
IF @@ROWCOUNT = 0
... record was deleted, consider looping to re-run the INSERT, or RAISERROR ...
END
Помимо предотвращения состояния гонки, если в большинстве случаев запись уже существует, это приведет к сбою INSERT, что приводит к потере ресурсов ЦП.
Использование MERGE, вероятно, предпочтительнее для SQL2008 и более поздних версий.
Интересная идея, но неверный синтаксис. Для SELECT требуется FROM <table_source> и TOP 1 (если только выбранный table_source не имеет только 1 строку).
Спасибо. Я изменил его на НЕ СУЩЕСТВУЕТ. Будет только одна совпадающая строка из-за теста на «ключ» согласно O / P (хотя, возможно, это должен быть ключ, состоящий из нескольких частей :))
Вы можете использовать этот запрос. Работает во всех редакциях SQL Server. Все просто и понятно. Но вам нужно использовать 2 запроса. Вы можете использовать, если не можете использовать MERGE
BEGIN TRAN
UPDATE table
SET Id = @ID, Description = @Description
WHERE Id = @Id
INSERT INTO table(Id, Description)
SELECT @Id, @Description
WHERE NOT EXISTS (SELECT NULL FROM table WHERE Id = @Id)
COMMIT TRAN
ПРИМЕЧАНИЕ. Объясните, пожалуйста, отрицательные ответы.
Я предполагаю отсутствие блокировки?
Нет недостатка в блокировке ... Я использую "TRAN". У транзакций sql-сервера по умолчанию есть блокировка.
Многие люди предложат вам использовать MERGE, но я предостерегаю вас от этого. По умолчанию он не защищает вас от условий параллелизма и гонки не больше, чем несколько операторов, и представляет другие опасности:
Даже при наличии этого «более простого» синтаксиса я все же предпочитаю этот подход (обработка ошибок для краткости опущена):
BEGIN TRANSACTION;
UPDATE dbo.table WITH (UPDLOCK, SERIALIZABLE)
SET ... WHERE PK = @PK;
IF @@ROWCOUNT = 0
BEGIN
INSERT dbo.table(PK, ...) SELECT @PK, ...;
END
COMMIT TRANSACTION;
Многие предложат такой способ:
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
BEGIN TRANSACTION;
IF EXISTS (SELECT 1 FROM dbo.table WHERE PK = @PK)
BEGIN
UPDATE ...
END
ELSE
BEGIN
INSERT ...
END
COMMIT TRANSACTION;
Но все это гарантирует, что вам может потребоваться дважды прочитать таблицу, чтобы найти строки, которые нужно обновить. В первом примере вам нужно будет найти строку (строки) только один раз. (В обоих случаях, если при начальном чтении не найдено ни одной строки, выполняется вставка.)
Другие предложат такой способ:
BEGIN TRY
INSERT ...
END TRY
BEGIN CATCH
IF ERROR_NUMBER() = 2627
UPDATE ...
END CATCH
Однако это проблематично, если только по той причине, что позволить SQL Server перехватывать исключения, которые вы могли бы предотвратить в первую очередь, намного дороже, за исключением редкого сценария, когда почти каждая вставка не выполняется. Я доказываю это здесь:
Как насчет вставки / обновления ИЗ временной таблицы, которая вставляет / обновляет много записей?
@ user960567 ну 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);
приятно ответил спустя более 2 лет :)
@ user960567 Извините, я не всегда получаю уведомления о комментариях в режиме реального времени.
@AaronBertrand Спасибо за это. Re: 2020-09-23 edit, ваш предпочтительный подход, описанный выше, обертывает транзакцию с помощью "ISOLATION LEVEL SERIALIZABLE", без подсказки таблицы UPDLOCK. связанная статья от 02.09.2020 использует две подсказки таблицы «UPDATE dbo.t WITH (UPDLOCK, SERIALIZABLE)» и не «ISOLATION LEVEL SERIALIZABLE» (что имеет смысл). Извините за глупый вопрос, но они эквивалентны или один подход лучше другого? Я предполагаю, что ваша последняя статья представляет ваш текущий предпочтительный подход. Спасибо.
@iokevins Я не могу придумать никакой разницы. Я на самом деле разорван в плане предпочтений, хотя я предпочитаю иметь подсказку на уровне запроса, я предпочитаю обратное, когда мы говорим, скажем, о применении подсказок NOLOCK к каждой таблице в запросе (в этом случае я предпочитаю один оператор SET, который будет исправлен позже).
/*
CREATE TABLE ApplicationsDesSocietes (
id INT IDENTITY(0,1) NOT NULL,
applicationId INT NOT NULL,
societeId INT NOT NULL,
suppression BIT NULL,
CONSTRAINT PK_APPLICATIONSDESSOCIETES PRIMARY KEY (id)
)
GO
--*/
DECLARE @applicationId INT = 81, @societeId INT = 43, @suppression BIT = 0
MERGE dbo.ApplicationsDesSocietes WITH (HOLDLOCK) AS target
--set the SOURCE table one row
USING (VALUES (@applicationId, @societeId, @suppression))
AS source (applicationId, societeId, suppression)
--here goes the ON join condition
ON target.applicationId = source.applicationId and target.societeId = source.societeId
WHEN MATCHED THEN
UPDATE
--place your list of SET here
SET target.suppression = source.suppression
WHEN NOT MATCHED THEN
--insert a new line with the SOURCE table one row
INSERT (applicationId, societeId, suppression)
VALUES (source.applicationId, source.societeId, source.suppression);
GO
Замените имена таблиц и полей на все, что вам нужно. Позаботьтесь о состоянии с помощью ON. Затем установите соответствующее значение (и тип) для переменных в строке DECLARE.
Ваше здоровье.
Я пробовал решение ниже, и оно работает для меня, когда возникает одновременный запрос на вставку.
begin tran
if exists (select * from table with (updlock,serializable) where key = @key)
begin
update table set ...
where key = @key
end
else
begin
insert table (key, ...)
values (@key, ...)
end
commit tran
Это зависит от модели использования. Нужно смотреть на общую картину использования, не теряясь в деталях. Например, если шаблон использования - это 99% обновлений после создания записи, то «UPSERT» - лучшее решение.
После первой вставки (попадания) это будут все обновления одного оператора, без каких-либо «если» или «но». Условие «где» для вставки необходимо, иначе она будет вставлять дубликаты, и вы не хотите иметь дело с блокировкой.
UPDATE <tableName> SET <field>=@field WHERE key=@key;
IF @@ROWCOUNT = 0
BEGIN
INSERT INTO <tableName> (field)
SELECT @field
WHERE NOT EXISTS (select * from tableName where key = @key);
END
Вы можете использовать оператор MERGE. Этот оператор используется для вставки данных, если они не существуют, или обновления, если они существуют.
MERGE INTO Employee AS e
using EmployeeUpdate AS eu
ON e.EmployeeID = eu.EmployeeID`
@RamenChef Я не понимаю. Где предложения WHEN MATCHED?
@likejudo Я этого не писал; Я только переделал. Спросите пользователя, написавшего сообщение.
Предполагая, что вы хотите вставить / обновить одну строку, наиболее оптимальный подход - использовать уровень изоляции транзакции REPEATABLE READ SQL Server:
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
BEGIN TRANSACTION
IF (EXISTS (SELECT * FROM myTable WHERE key=@key)
UPDATE myTable SET ...
WHERE key=@key
ELSE
INSERT INTO myTable (key, ...)
VALUES (@key, ...)
COMMIT TRANSACTION
Этот уровень изоляции будет предотвращать / блокировать последующие повторяющиеся транзакции чтения от доступа к той же строке (WHERE key=@key), пока текущая транзакция открыта.
С другой стороны, операции с другой строкой не будут заблокированы (WHERE key=@key2).
Для тех, кто впервые сталкивается с этим вопросом - обязательно прочтите все ответы и их комментарии. Возраст может иногда приводить к неверной информации ...