Що таке deadlock і як з ним боротися

Кузьменко Дмитро
Epsylon Technologies

Почнемо з того, що буквальний переклад слова deadlock означає "мертва блокування". При роботі з BDE (Delphi, C ++ Builder.) З клієнтської частини і в IB Database з тригерами і збереженими процедурами ми маємо два випад появи повідомлення deadlock - при читанні і при оновленні. До дійсно "мертвого" блокування справа не доходить, оскільки IB SQL Link стартує будь-яку транзакцію з параметром NO WAIT (тобто не очікувати вирішення конфлікту).

Deadlock при оновленні

Дві транзакції, ще не завершилися, але намагаються оновити одні й ті-ж записи, вважаються конкуруючими. Існує два режими обробки deadlock - wait і no wait (з очікуванням і без очікування). У BDE для будь-яких транзакцій IB використовується режим без очікування, і режим з очікуванням можна встановити тільки при прямій роботі з IB API (наприклад через FreeIBComponents).

"Невдачливих", природно, вважається транзакція, яка отримала повідомлення про deadlock. Це означає, що одне з дій, що проводяться в транзакції, не може бути виконано. Отже, така транзакція повинна бути скасована (rollback). Слід уникати тривалих транзакцій, які можуть потрапити в таку ситуацію - єдиним виходом з неї буде спроба почати транзакцію знову і повторити всі дії.

Зменшити число можливих конфліктів поновлення можна скоротивши брешемо виконання транзакції. Наприклад, спочатку приймаються дані від користувача, і якщо він підтверджує введену інформацію, додаток стартує транзакцію, швидко передає дані на сервер, і завершується. Чим швидше пройде транзакція, тим більше у неї шансів завершитися успішно. Саме для цього в BDE 3.x був введений режим Cached Updates (кешированниє зміни). При Cached Updates зміни накопичуються на клієнтської частини програми, потім при виклику методу ApplyUpdates зміни "вистрілюють" на сервер. У будь-якому випадку, навіть якщо ви не можете позбутися від тривалих оновлюють транзакцій, потрібно продумати логіку програми та обов'язково передбачати в додатку обробку виникаючих конфліктів.

Deadlock при читанні

Всі описувані нижче проблеми виправлені в BDE 4.01. При цьому параметр DRIVER FLAGS вказувати не потрібно.

Deadlock при читанні виникає в основному в SQL-серверах, які використовують сторінкові блокування при читанні або модифікації даних (MS SQL і Sybase). Здається дивним, що при роботі з IB, в якому блокування взагалі відсутні (конфлікт поновлення блокуванням не рахується - це не блокування а саме конфлікт), іноді все-таки виникає deadlock при читанні даних.

Причина ось у чому: транзакції рівня ізоляції Read Committed мають в IB два режими - NO RECORD VERSION і RECORD VERSION. У першому випадку, якщо при читанні записи ядро ​​IB виявляє наявність непідтвердженою (uncommitted) версії цього запису, то повертає повідомлення про deadlock. Це ніби сигналізує додатком, що скоро цей запис можливо буде оновлено (адже в ReadCommitted чужі зміни будуть помітні відразу після їх підтвердження (commit)). У режимі RECORD VERSION наявність непідтверджених версій записів ігнорується, і завжди повертається стара версія запису.

Здавалося-б, так чомусь б BDE Не працювати за замовчуванням в режимі RECORD VERSION. На жаль, так спочатку було закладено - транзакція Read Committed в BDE запускається з параметром NO RECORD VERSION - але до версії Delphi 2.0 цього незручності майже ніхто не помітив. А ось чому не помітили, читаємо далі.

Рівень ізоляції в AUTOCOMMIT в різних версіях BDE

Залежно від версії BDE змінювалися рівні ізоляції за замовчуванням. Коли ви явно не використовуєте методи управління транзакціями (Database.StartTransaction, Commit і Rollback), то за вас це робить BDE. Не вірите. Подивіться в SQL Monitor. Найголовніше, що тип транзакції за замовчуванням "зашитий" в SQL Link. І навіть якщо ви помістили на форму компонент TDatabase, і змінили у нього властивість tiTransIsolation, то це не впливає на BDE, поки ви не викличете метод Database.StartTransaction.

Отже, будь-ж транзакції стартує за замовчуванням BDE.

  • Delphi 1.0, BDE 2.52 - Repeatable Read
  • Delphi 2.0, BDE 3.x - Read Committed
  • Delphi 3.0, BDE 4.0 - Read Committed
  • Delphi 3.01, BDE 4.01, 4.51 - Read Committed з параметром RECORD VERSION - deadlock-і при читанні відсутні.

Отже, deadlock-і при читанні помітили тільки в Delphi 2.0, саме тому що транзакція за замовчуванням змінилася на Read Committed. До речі, в READLINK.TXT для Delphi 2.x і 3.0 написано, що якщо потрібно змінити транзакцію за замовчуванням на Repeatable Read, то слід встановити в параметрах драйвера DRIVER FLAGS = 512. Тобто фактично "забезпечити сумісність" поведін додатків, перенесених в Delphi 2 з Delphi 1.

Не встановлюйте DRIVER FLAGS не прочитавши попередньо READLINK.TXT. Справа в тому, що наприклад у версії 4.0 кількість прапорів збільшилася:

  • 0 Read Committed, негайне підтвердження будь-яких змін (може викликати перечитування курсорів TQuery)
  • 512 Repeatable Read, негайне підтвердження будь-яких змін (може викликати перечитування курсорів TQuery)
  • 4096 Read committed, підтвердження змін виконується як COMMIT RETAIN, зберігаючи контекст курсорів TQuery
  • 4608 Repeatable read, підтвердження змін виконується як COMMIT RETAIN, зберігаючи контекст курсорів TQuery

Схожі статті