Якщо у мене є операція UPDATE, яка фактично не змінює жодних даних (оскільки дані вже перебувають у оновленому стані), чи є якась користь від продуктивності у встановленні перевірки в пункті де для запобігання оновлення?
Звичайно, це може бути, оскільки є невелика різниця в продуктивності завдяки ОНОВЛЕННЮ 1 :
- фактично не оновлюючи жодних рядків (отже, нічого не записувати на диск, навіть мінімальну активність журналу), і
- зняття менш обмежувальних блокувань, ніж те, що потрібно для актуального оновлення (отже, краще для одночасності) ( Будь ласка, дивіться розділ Оновлення до кінця )
Однак, скільки різниці існує, потрібно було б виміряти вашою системою за допомогою вашої схеми, даних та завантаження системи. Є декілька факторів, які впливають на те, який вплив має оновлення ОНОВЛЕННЯ:
- кількість суперечок щодо таблиці, що оновлюється
- кількість рядків, що оновлюються
- якщо в оновлюваній таблиці є триггери UPDATE (як зазначив Марк у коментарі до питання). Якщо ви виконаєте
UPDATE TableName SET Field1 = Field1
, то тригер оновлення буде спрацьовувати і вкаже, що поле було оновлено (якщо ви перевірите за допомогою функцій UPDATE () або COLUMNS_UPDATED ), і що поле в обох INSERTED
та DELETED
таблицях має однакове значення.
Також наступний підсумковий розділ міститься у статті Пола Уайта, «Вплив оновлення оновлень» (як зазначив @spaghettidba у коментарі до своєї відповіді):
SQL Server містить ряд оптимізацій, щоб уникнути непотрібного входу в журнал чи перемивання сторінки при обробці операції UPDATE, що не призведе до будь-яких змін стійкої бази даних.
- Неоновлення оновлень до кластеризованої таблиці зазвичай уникає додаткового ведення журналів та прошивання сторінок, якщо операція оновлення не впливає на стовпець, що утворює (частину) кластерного ключа.
- Якщо будь-яка частина кластерного ключа «оновлена» до того самого значення, операція записується так, як якщо б дані змінилися, а постраждалі сторінки в буферному пулі позначаються як брудні. Це є наслідком перетворення UPDATE в операцію видалення та вставлення.
- Таблиці купи поводяться так само, як і кластеризовані таблиці, за винятком того, що вони не мають кластерного ключа, щоб викликати додаткові журнали чи перемикання сторінок. Це залишається в тому випадку, коли на купі існує некластеризований первинний ключ. Тому не оновлюючи оновлення для купи, таким чином, як правило, уникайте зайвих журналів та промивань (але див. Нижче)
- І купи, і кластеризовані таблиці зазнаватимуть додаткового журналу та обробці для будь-якого рядка, коли стовпець LOB, що містить більше 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
пункт. Захоплюючи саме те, які рядки були оновлені, ви можете звузити елементи, щоб шукати різницю між не оновленими рядками, які не існують, на відміну від не оновлених рядків, які існують, але не потребують оновлення.
Я показую основну реалізацію в наступній відповіді:
Як уникнути використання запиту на об'єднання під час введення кількох даних за допомогою параметра 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 є менш обмежуючим при фільтруванні рядків, які не змінилися. Якщо жодна інша процедура не взаємодіє з цією таблицею, то це, мабуть, не проблема (але наскільки це ймовірно?). Майте на увазі, що тестування, показане в будь-якому з пов’язаних блогів (і навіть моє тестування), явно передбачає, що в таблиці немає суперечок, оскільки воно ніколи не є частиною тестів. Скажімо, що оновлення оновлення настільки малі, що не потрібно платити за фільтрування, потрібно брати зерно солі, оскільки тестування було проведено у вакуумі, більш-менш. Але у виробництві ця таблиця, швидше за все, не є ізольованою. Звичайно, цілком може бути так, що невелика кількість журналів та більш обмежені блокування не призводять до меншої ефективності. Тож найнадійніше джерело інформації для відповіді на це питання? 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
Таким чином, є лише 2 блокування, придбані замість 3, і обидва ці блокування мають намір спільного використання, а не намір eXclusive або оновлення намірів ( сумісність блокування ). Маючи на увазі, що кожен придбаний замок також буде звільнений, кожен замок - це дійсно 2 операції, тому цей новий метод - це загалом 4 операції замість 6 операцій у запропонованому спочатку методі. Враховуючи, що ця операція виконується один раз кожні 15 мс (приблизно, як заявлено в ОП), тобто приблизно 66 разів за секунду. Таким чином, оригінальна пропозиція становить 396 операцій блокування / розблокування в секунду, тоді як цей новий метод становить лише 264 операції блокування / розблокування в секунду навіть легших ваг замків. Це не є гарантією дивовижної продуктивності, але, безумовно, варто перевірити :-).