Кузьменко Дмитро
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