ОНОВЛЕННЯ продуктивність, коли дані не змінюються


31

Якщо у мене є UPDATEтвердження, яке фактично не змінює жодних даних (тому що дані вже в оновленому стані). Чи є якась користь від продуктивності, якщо ви ставите чек у WHEREпункті, щоб запобігти оновленню?

Наприклад, чи буде різниця у швидкості виконання між UPDATE 1 та UPDATE 2 у наступному:

CREATE TABLE MyTable (ID int PRIMARY KEY, Value int);
INSERT INTO MyTable (ID, Value)
VALUES
    (1, 1),
    (2, 2),
    (3, 3);

-- UPDATE 1
UPDATE MyTable
SET
    Value = 2
WHERE
    ID = 2
    AND Value <> 2;
SELECT @@ROWCOUNT;

-- UPDATE 2
UPDATE MyTable
SET
    Value = 2
WHERE
    ID = 2;
SELECT @@ROWCOUNT;

DROP TABLE MyTable;

Причина, яку я запитую, полягає в тому, що мені потрібно, щоб кількість рядків включала незмінний рядок, тому я знаю, чи потрібно вставляти, якщо ідентифікатор не існує. Як такий я використав форму UPDATE 2. Якщо є корисність для використання форми UPDATE 1, чи можна отримати кількість рядків, яка мені потрібна?


Див. Sqlperformance.com/2012/10/t-sql-queries/conditional-updates (хоча я не профілював випадок, коли значення не змінюються).
Аарон Бертран

Відповіді:


24

Якщо у мене є операція 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'.
  • Просто включення будь-якого типу рівня ізоляції версій версій у базі даних завжди спричиняє додатковий журнал та промивання. Це відбувається незалежно від рівня ізоляції, що діє для транзакції з оновленням.

Майте на увазі (особливо якщо ви не переходите за посиланням, щоб переглянути повну статтю Павла), наступні два пункти:

  1. У оновленнях оновлень все ще є деяка активність журналу, що показує, що транзакція починається і закінчується. Просто не відбувається модифікація даних (що все-таки є хорошою економією).

  2. Як я вже говорив вище, вам потрібно протестувати свою систему. Скористайтеся тими самими дослідницькими запитами, якими користується Пол, і подивіться, чи отримаєте ви ті самі результати. Я бачу дещо інші результати в своїй системі, ніж ті, що показані в статті. Ще немає брудних сторінок для запису, але трохи більше журнальної активності.


... Мені потрібно, щоб кількість рядків включала незмінний рядок, тому я знаю, чи потрібно вставляти, якщо ідентифікатор не існує. ... чи можна отримати підрахунок рядків, який мені потрібен якось?

Простіше кажучи, якщо ви просто маєте справу з одним рядком, ви можете зробити наступне:

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 операції блокування / розблокування в секунду навіть легших ваг замків. Це не є гарантією дивовижної продуктивності, але, безумовно, варто перевірити :-).


14

Трохи зменшіть масштаб і подумайте про більшу картину. У реальному житті справді буде виглядати ваше повідомлення про оновлення таким чином:

UPDATE MyTable
  SET Value = 2
WHERE
     ID = 2
     AND Value <> 2;

Або це буде виглядати більше так:

UPDATE Customers
  SET AddressLine1 = '123 Main St',
      AddressLine2 = 'Apt 24',
      City = 'Chicago',
      State = 'IL',
      (and a couple dozen more fields)
WHERE
     ID = 2
     AND (AddressLine1 <> '123 Main St'
     OR AddressLine2 <> 'Apt 24'
     OR City <> 'Chicago'
     OR State <> 'IL'
      (and a couple dozen more fields))

Тому що в реальному світі таблиці мають багато стовпців. Це означає, що вам потрібно буде генерувати багато складної логіки динамічної програми, щоб створити динамічні рядки, АБО вам доведеться кожного разу вказувати вміст кожного поля перед і після.

Якщо ви будуєте ці оператори оновлення динамічно для кожної таблиці, лише проходячи в оновлюваних полях, ви можете швидко зіткнутися з проблемою забруднення кешу плану, схожою на проблему розмірів параметрів NHibernate за кілька років тому. Ще гірше, якщо ви будуєте операції оновлення в SQL Server (як, наприклад, у збережених процедурах), тоді ви будете записувати дорогоцінні цикли процесора, оскільки SQL Server не надзвичайно ефективний для об'єднання рядків у масштабі.

Через такі складності, зазвичай не має сенсу робити таке порівняння по черзі, по полю, коли ви робите оновлення. Подумайте замість цього на основі операцій.


1
Мій приклад із реального світу такий же простий, але його називають багато. Моя оцінка - один раз кожні 15 м у пік. Мені було цікаво, чи достатньо чіткий SQL Server, щоб не писати на диск, коли цього не потрібно.
Мартін Браун

3

Ви можете побачити підвищення продуктивності у пропусканні рядків, які не потрібно оновлювати лише тоді, коли кількість рядків велика (менше журналів, менше брудних сторінок для запису на диск).

Якщо мати справу з оновленнями для одного ряду, як у вашому випадку, різниця в продуктивності абсолютно незначна. Якщо оновлення рядків у всіх випадках полегшує вам, зробіть це.

Для отримання додаткової інформації з цієї теми див. Неоновлення оновлень Пола Уайта


3

Ви можете об'єднати оновлення та вставити в одне твердження. На SQL Server ви можете використовувати оператор MERGE, щоб зробити як оновлення, так і вставити, якщо його не знайдено. Для MySQL ви можете використовувати INSERT ON DUPLICATE KEY UPDATE .


1

Замість того, щоб перевіряти значення всіх полів, чи не можна отримати хеш-значення, використовуючи цікаві вам стовпці, а потім порівняйте їх із хешем, збереженим проти рядка в таблиці?

IF EXISTS (Select 1 from Table where ID =@ID AND HashValue=Sha256(column1+column2))
GOTO EXIT
ELSE
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.