Если у меня есть оператор UPDATE, который на самом деле не изменяет какие-либо данные (поскольку данные уже находятся в обновленном состоянии), есть ли какое-либо преимущество в производительности, если поставить проверку в предложении where для предотвращения обновления?
Конечно, может быть, так как есть небольшая разница в производительности из-за ОБНОВЛЕНИЯ 1 :
- на самом деле не обновлять какие-либо строки (следовательно, нечего записывать на диск, даже минимальную активность журнала), и
- удаление менее строгих блокировок, чем требуется для фактического обновления (следовательно, лучше для параллелизма) ( см. раздел «Обновление» в конце )
Тем не менее, насколько велика разница, вы должны измерить в своей системе с помощью своей схемы, данных и нагрузки на систему. Существует несколько факторов, влияющих на то, какое влияние оказывает обновление без обновления:
- количество раздоров на обновляемой таблице
- количество обновляемых строк
- если есть триггеры ОБНОВЛЕНИЯ в обновляемой таблице (как отмечено Марком в комментарии к Вопросу). Если вы выполняете
UPDATE TableName SET Field1 = Field1
, запускается триггер обновления, который указывает, что поле было обновлено (если вы проверяете с помощью функций UPDATE () или COLUMNS_UPDATED ), и что поля в таблицах INSERTED
и в обоих DELETED
таблицах имеют одно и то же значение.
Кроме того, следующий сводный раздел можно найти в статье Пола Уайта « Влияние необновленных обновлений» (как отметил @spaghettidba в комментарии к своему ответу):
SQL Server содержит ряд оптимизаций, позволяющих избежать ненужного ведения журнала или сброса страниц при обработке операции UPDATE, которая не приведет к каким-либо изменениям в постоянной базе данных.
- Необновляющиеся обновления в кластеризованной таблице обычно избегают дополнительной регистрации и очистки страниц, если только операция обновления не влияет на столбец, который формирует (часть) ключа кластера.
- Если какая-либо часть ключа кластера «обновляется» до того же значения, операция записывается в журнал, как если бы данные были изменены, и затронутые страницы помечаются как грязные в пуле буферов. Это является следствием преобразования операции UPDATE в операцию удаления, а затем вставки.
- Таблицы кучи ведут себя так же, как кластерные таблицы, за исключением того, что у них нет ключа кластера, который может вызвать дополнительную запись в журнал или сброс страницы. Это сохраняется даже в том случае, когда в куче существует некластерный первичный ключ. Поэтому обновления без обновления кучи обычно избегают дополнительной регистрации и очистки (но см. Ниже).
- Как кучи, так и кластерные таблицы будут подвергаться дополнительному ведению журнала и сбросу для любой строки, в которой столбец больших объектов, содержащий более 8000 байтов данных, обновляется до того же значения, используя любой синтаксис, кроме «SET column_name = column_name».
- Простое включение любого уровня изоляции версий строк в базе данных всегда приводит к дополнительному ведению журнала и сбросу. Это происходит независимо от уровня изоляции, действующего для транзакции обновления.
Пожалуйста, имейте в виду (особенно если вы не переходите по ссылке, чтобы увидеть полную статью Пола), следующие два пункта:
Необновляющиеся обновления все еще имеют некоторую активность журнала, показывая, что транзакция начинается и заканчивается. Просто изменение данных не происходит (что все еще является хорошей экономией).
Как я уже говорил выше, вам необходимо протестировать свою систему. Используйте те же исследовательские запросы, которые использует Пол, и посмотрите, получите ли вы те же результаты. Я вижу немного отличающиеся результаты в моей системе, чем то, что показано в статье. Все еще нет грязных страниц, которые нужно написать, но немного больше активности журнала.
... Мне нужно, чтобы количество строк включало неизмененную строку, поэтому я знаю, нужно ли делать вставку, если идентификатор не существует. ... возможно ли получить количество строк, которое мне нужно?
Проще говоря, если вы имеете дело только с одной строкой, вы можете сделать следующее:
UPDATE MyTable
SET Value = 2
WHERE ID = 2
AND Value <> 2;
IF (@@ROWCOUNT = 0)
BEGIN
IF (NOT EXISTS(
SELECT *
FROM MyTable
WHERE ID = 2 -- or Value = 2 depending on the scenario
)
)
BEGIN
INSERT INTO MyTable (ID, Value) -- or leave out ID if it is an IDENTITY
VALUES (2, 2);
END;
END;
Для нескольких строк вы можете получить информацию, необходимую для принятия этого решения, используя OUTPUT
предложение. Собирая точно, какие строки были обновлены, вы можете сузить элементы, чтобы найти разницу между отсутствием обновлений строк, которые не существуют, и не обновлять строки, которые существуют, но не нуждаются в обновлении.
Я показываю основную реализацию в следующем ответе:
Как избежать использования запроса Merge при загрузке нескольких данных с использованием параметра xml?
Метод, показанный в этом ответе, не отфильтровывает строки, которые существуют, но не нуждаются в обновлении. Эту часть можно добавить, но сначала вам нужно будет точно указать, где вы получаете свой набор данных, в который вы сливаетесь MyTable
. Они приходят с временного стола? Табличный параметр (TVP)?
ОБНОВЛЕНИЕ 1:
Я наконец смог провести некоторое тестирование, и вот что я нашел относительно журнала транзакций и блокировки. Сначала схема для таблицы:
CREATE TABLE [dbo].[Test]
(
[ID] [int] NOT NULL CONSTRAINT [PK_Test] PRIMARY KEY CLUSTERED,
[StringField] [varchar](500) NULL
);
Затем тест обновляет поле до значения, которое оно уже имеет:
UPDATE rt
SET rt.StringField = '04CF508B-B78E-4264-B9EE-E87DC4AD237A'
FROM dbo.Test rt
WHERE rt.ID = 4082117
Полученные результаты:
-- Transaction Log (2 entries):
Operation
----------------------------
LOP_BEGIN_XACT
LOP_COMMIT_XACT
-- SQL Profiler (3 Lock:Acquired events):
Mode Type
--------------------------------------
8 - IX 5 - OBJECT
8 - IX 6 - PAGE
5 - X 7 - KEY
Наконец, тест, отфильтровывающий обновление из-за неизменности значения:
UPDATE rt
SET rt.StringField = '04CF508B-B78E-4264-B9EE-E87DC4AD237A'
FROM dbo.Test rt
WHERE rt.ID = 4082117
AND rt.StringField <> '04CF508B-B78E-4264-B9EE-E87DC4AD237A';
Полученные результаты:
-- Transaction Log (0 entries):
Operation
----------------------------
-- SQL Profiler (3 Lock:Acquired events):
Mode Type
--------------------------------------
8 - IX 5 - OBJECT
7 - IU 6 - PAGE
4 - U 7 - KEY
Как видите, при фильтрации строки ничего не записывается в журнал транзакций, в отличие от двух записей, отмечающих начало и конец транзакции. И хотя это правда, что эти две записи почти ничто, они все еще что-то.
Кроме того, блокировка ресурсов PAGE и KEY менее ограничена при фильтрации строк, которые не изменились. Если никакие другие процессы не взаимодействуют с этой таблицей, то это, вероятно, не проблема (но насколько это вероятно, правда?). Имейте в виду, что тестирование, показанное в любом из связанных блогов (и даже мое тестирование) неявно предполагает, что в таблице нет разногласий, поскольку оно никогда не является частью тестов. Сказать, что не обновляющиеся обновления настолько легки, что не нужно платить за фильтрацию, потому что тестирование проводится более или менее в вакууме. Но в Production эта таблица, скорее всего, не является изолированной. Конечно, вполне может быть, что небольшая регистрация и более строгие блокировки не приведут к снижению эффективности. Таким образом, самый надежный источник информации, чтобы ответить на этот вопрос? SQL Server. В частности:ваш SQL Server. Он покажет вам, какой метод лучше для вашей системы :-).
ОБНОВЛЕНИЕ 2:
Если в операциях, в которых новое значение совпадает с текущим значением (т. Е. Без обновления), из числа операций, в которых новое значение отличается, и обновление необходимо, то следующий шаблон может оказаться еще лучше, особенно если на столе много споров. Идея состоит в том, чтобы сначала просто SELECT
получить текущее значение. Если вы не получите значение, то у вас есть ответ относительно INSERT
. Если у вас есть значение, вы можете сделать простое IF
и выдать UPDATE
только, если это необходимо.
DECLARE @CurrentValue VARCHAR(500) = NULL,
@NewValue VARCHAR(500) = '04CF508B-B78E-4264-B9EE-E87DC4AD237A',
@ID INT = 4082117;
SELECT @CurrentValue = rt.StringField
FROM dbo.Test rt
WHERE rt.ID = @ID;
IF (@CurrentValue IS NULL) -- if NULL is valid, use @@ROWCOUNT = 0
BEGIN
-- row does not exist
INSERT INTO dbo.Test (ID, StringField)
VALUES (@ID, @NewValue);
END;
ELSE
BEGIN
-- row exists, so check value to see if it is different
IF (@CurrentValue <> @NewValue)
BEGIN
-- value is different, so do the update
UPDATE rt
SET rt.StringField = @NewValue
FROM dbo.Test rt
WHERE rt.ID = @ID;
END;
END;
Полученные результаты:
-- Transaction Log (0 entries):
Operation
----------------------------
-- SQL Profiler (2 Lock:Acquired events):
Mode Type
--------------------------------------
6 - IS 5 - OBJECT
6 - IS 6 - PAGE
Таким образом, вместо 3 получено только 2 блокировки, и обе эти блокировки являются Intent Shared, а не Intent eXclusive или Intent Update ( Lock Compatibility ). Помня о том, что каждая полученная блокировка также будет освобождена, каждая блокировка в действительности представляет собой 2 операции, поэтому этот новый метод представляет собой всего 4 операции вместо 6 операций в первоначально предложенном методе. Учитывая, что эта операция выполняется один раз каждые 15 мс (приблизительно, как указано в OP), то есть примерно 66 раз в секунду. Таким образом, первоначальное предложение составляет 396 операций блокировки / разблокировки в секунду, в то время как этот новый метод составляет только 264 операции блокировки / разблокировки в секунду даже для более легких блокировок. Это не гарантия потрясающей производительности, но, безусловно, стоит протестировать :-).