ПЕРЕЛІК ДИСЦИПЛІН:
  • Адміністративне право
  • Арбітражний процес
  • Архітектура
  • Астрологія
  • Астрономія
  • Банківська справа
  • Безпека життєдіяльності
  • Біографії
  • Біологія
  • Біологія і хімія
  • Ботаніка та сільське гос-во
  • Бухгалтерський облік і аудит
  • Валютні відносини
  • Ветеринарія
  • Військова кафедра
  • Географія
  • Геодезія
  • Геологія
  • Етика
  • Держава і право
  • Цивільне право і процес
  • Діловодство
  • Гроші та кредит
  • Природничі науки
  • Журналістика
  • Екологія
  • Видавнича справа та поліграфія
  • Інвестиції
  • Іноземна мова
  • Інформатика
  • Інформатика, програмування
  • Юрист по наследству
  • Історичні особистості
  • Історія
  • Історія техніки
  • Кибернетика
  • Комунікації і зв'язок
  • Комп'ютерні науки
  • Косметологія
  • Короткий зміст творів
  • Криміналістика
  • Кримінологія
  • Криптология
  • Кулінарія
  • Культура і мистецтво
  • Культурологія
  • Російська література
  • Література і російська мова
  • Логіка
  • Логістика
  • Маркетинг
  • Математика
  • Медицина, здоров'я
  • Медичні науки
  • Міжнародне публічне право
  • Міжнародне приватне право
  • Міжнародні відносини
  • Менеджмент
  • Металургія
  • Москвоведение
  • Мовознавство
  • Музика
  • Муніципальне право
  • Податки, оподаткування
  •  
    Бесплатные рефераты
     

     

     

     

     

     

         
     
    Deadlocks
         

     

    Інформатика, програмування

    Deadlocks

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

    Іван Бодягін

    Введення

    Проблема взаімоблокіровок в реальному додатку може призвести до псування досить великої кількості нервових клітин, і в той же час досить бідно описана. Мета цієї статті - хоча б частково заповнити цю прикрий пропуск і пояснити, що таке взаімоблокіровкі і як з ними боротися. Як піддослідній свинки обраний Microsoft SQL Server, однак теоретична частина також відноситься і до інших серверів баз даних, хоча б частково застосовують блокувальний механізм для забезпечення коректності паралельної обробки транзакцій, наприклад, DB2, Oracle, Informix і навіть Interbase.

    Основні поняття

    Розмова про взаімоблокіровках складно вести, не маючи в своєму розпорядженні деякої базової термінологією. Тут я спробую викласти необхідний мінімум основних понять і термінів. Ця частина жодним чином не претендує на повноту, тому в разі виникнення будь-яких питань рекомендую звернутися до літератури згаданої нижче. Ті ж, хто в блокування, як таких, розбирається в достатній мірі, можуть сміливо пропустити цей розділ.

    Блокування

    Дивно, але на форумах досить часто з'являються питання, з тексту яких стає ясно, що автор просто переплутав терміни «блокування» (lock) і «взаімоблокіровка» (deadlock). Щоб уникнути подібних непорозумінь почнемо з самого початку.

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

    Блокування не може бути накладено на кілька об'єктів одночасно. Тим накладенням блокувань на два різних об'єкта, теоретично, може відбутися що завгодно, навіть якщо ці об'єкти - два записи в однієї і тієї ж таблиці, розташовані поруч. За допомогою блокувань забезпечується синхронізація доступу до ресурсів. Під ресурсами або об'єктами тут і далі буде матися на увазі який-небудь об'єкт БД - запис, сторінка даних або таблиця. Синхронізація відбувається завдяки тому, що перш ніж Провести з об'єктом якісь дії (прочитати або змінити), на нього накладається блокування. Вона забороняє змінювати або навіть читати об'єкт іншим транзакціям до тих пір, поки транзакція, що наклали блокування, не завершить роботу з цим об'єктом. Синхронізація доступу потрібна для того, щоб не допустити впливу однієї транзакції на іншу при одночасному виконанні. Іншими словами, в ідеальному випадку, трансакція, навіть якщо їх одночасно виконується безліч, повинна дати такий же результат, як була б вона виконувалася одна, а інших транзакцій не було взагалі. Однак слід пам'ятати, що в більшості серверів такий ідеальний режим роботи паралельних транзакцій «За замовчуванням» не включено. Детальніше про це трохи нижче.        

    ПРИМІТКА   

    Варто згадати, що блокування --   аж ніяк не єдиний спосіб забезпечити вищезгадану синхронізацію. У   теорії існує більше десятка способів, як блокувальних, так і не   заснованих на блокування, а так само гібридних; версійність (multiversioning),   на тимчасові мітки (timestamp), спрямованих на аціклічних графах (DAG),   агресивні і консервативні їх варіанти, і т.д.     

    Типи блокувань

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

    Read Lock - блокування читання, вона ж "колективна», вона ж «Колективна». Сенс цього блокування в тому, що вона сумісна з точно такими ж блокуваннями. Іншими словами, на один і той же ресурс може бути накладено як завгодно багато колективних блокувань. У термінології MSSQL ця блокування називається Shared Lock, або скорочено S.

    Write Lock - блокування запису, вона ж "монопольна», вона ж «ексклюзивні». Це блокування не сумісна ні з Read Lock, ні сама з собою, ні з будь-яким іншим типом блокувань. Тобто в один момент часу на один об'єкт може бути накладено тільки одна монопольна блокування. Ця блокування в термінології MSSQL називається Exclusive Lock, або ж скорочено X.

    Update Lock - це проміжна блокування, блокування «Оновлення». Вона сумісна з Read Lock, але не сумісна з Write Lock і сама з собою. Іншими словами на один об'єкт можуть бути одночасно накладені одна блокування оновлення, жодної монопольної блокування і як завгодно багато колективних блокувань. Цей тип блокувань введений як раз для зниження ризику виникнення взаімоблокіровкі. Яким саме чином, буде пояснено нижче.

    Блокування наміри

    вищеописаними трьома типами можливі типи блокувань не обмежуються. Нас також будуть цікавити так звані «блокування наміри »(Intent Lock)

    Справа в тому, що об'єкти в БД шикуються в своєрідну ієрархію Таблиця-> Сторінка-> Record. Будь-яка з вищезгаданих блокувань може бути накладено на будь-який з цих об'єктів.

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

    З можливих типів блокувань нас цікавлять (тут і далі використовується термінологія Microsoft SQL Server):

    Intent Share - колективна блокування наміри, скорочено IS.

    Intent Exclusive - монопольна блокування наміри, скорочено IX.

    Всі згадані блокування разом утворюють так звану «Матрицю сумісності» (Compatibility Matrix)                 

    IS         

    S         

    U         

    IX         

    X             

    Intent Shared (IS)         

    Yes         

    Yes         

    Yes         

    Yes         

    No             

    Shared (S)         

    Yes         

    Yes         

    Yes         

    No         

    No             

    Update (U)         

    Yes         

    Yes         

    No         

    No         

    No             

    Intent Exclusive (IX)         

    Yes         

    No         

    No         

    Yes         

    No             

    Exclusive (X)         

    No         

    No         

    No         

    No         

    No     

    Таблиця 1

    У таблиці 1 "Yes" означає, що блокування сумісні, тобто можуть бути одночасно накладено на один і той самий об'єкт, а "No" означає, що не сумісні.

    Протокол двофазної блокування

    Важливу роль у забезпеченні коректної паралельної обробки транзакцій грає «протокол двофазної блокування» (Two Phase Locking - 2PL). Існує відповідна теорема, в якій доводиться, що якщо транзакція задовольняє протоколу двофазної блокування, то вона коректна, тобто результат її виконання не залежить від інших транзакцій.

    Суть 2PL в тому, що не можна зняти одного разу накладену блокування до тих пір, поки не накладені всі блокування, необхідні транзакції. Таким чином, робота з блокуваннями в транзакції ділиться на дві фази: фаза накладення блокувань і фаза зняття. У практичних реалізаціях, як правило, застосовується строгий протокол двофазної блокування - Strict 2PL. Його особливість в тому, що фаза зняття блокувань настає після фіксації транзакції.

    Рівні ізоляції

    Як вже говорилося, в ідеальному випадку результат виконання транзакції не повинен залежати від інших транзакцій, скільки б одночасно їх не виконувалося. Але, на жаль, цей ідеальний випадок накладає сильні обмеження на паралельну обробку, практично вибудовуючи транзакції в чергу. Однак у більшості випадків така суворість не потрібна, і тому було запроваджено так звані «рівні ізоляції» (Isolation Level), які визначають ступінь паралелізму виконання транзакцій. Чим нижче рівень ізоляції, тим вище ступінь паралелізму і тим більше ризик «неправильного» виконання транзакції.

    У стандарті ANSI SQL вводяться чотири рівні ізоляції. І за назвами, і по поведінці рівні ізоляції в Microsoft SQL Server повністю відповідають описаним у стандарті.

    У стандарті рівні ізоляції прив'язані до чотирьох «Феноменам» - порушень ізольованості транзакцій: брудна запис, брудні читання, неповторним читання та фантоми. Підвищення рівня ізоляції послідовно усуває ці аномалії одну за одною. Тепер по порядку.

    1. Read Uncommitted - найнижчий рівень ізоляції. Дозволяє читати "брудні" дані незафіксірованих транзакцій, звідси і назва феномену - «брудне читання» (Dirty Read). Суть феномену в тому, що якщо перший транзакція запише якісь дані, друга їх прочитає, а потім перший транзакція буде скасована, то вийде, що друга транзакція прочитала дані, які ніколи не існували.

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

    3. Repeatable Read - цей рівень вирішує попередню проблему, але при цьому можлива поява фантомів. Зміна одного разу прочитаних першого транзакцією даних іншими транзакціями (до фіксації перший) неможливо. Однак якщо перші транзакція зробила вибірку по якомусь умові, а потім другий транзакція додала нові дані, що задовольняють цій умові, і зафіксувалася, то повторна вибірка перший транзакцією за тим же умові поверне в тому числі і додані дані - фантоми.

    4. Serializable - при цьому рівні ізоляції ніякі фантоми неможливі в принципі, так само як і інші феномени, навіть такі, яких ще не придумали. Цей рівень ізоляції ні на які феномени не спирається, просто потрібно, щоб результат паралельного виконання транзакцій був таким само, як якби вони виконувалися послідовно.

    Особливості Microsoft SQL Server

    З огляду на те, що всі практичні експерименти будуть проводиться на Microsoft SQL Server 2000, необхідно також описати деякі особливості внутрішньої механіки цього сервера.

    Для роботи з блокуваннями сервер ідентифікує кожен об'єкт. Для цього використовуються ідентифікатори ресурсу (Resource) і об'єкта (ObjId). Для різних типів об'єктів ці ідентифікатори відрізняються. Нас будуть цікавити наступні об'єкти:

    TAB - таблиця. Ідентифікатор об'єкта "ціле число", наприклад 26173590. Оскільки таблиця - об'єкт чисто логічний, ідентифікатора ресурсу вона не має.

    PAG - сторінка даних. Віртуальна сторінка даних належить конкретної таблиці, тому ідентифікатор об'єкта співпадає з ідентифікатором таблиці, дані якої розміщені на цій сторінці. У той же час сторінка має фізичний еквівалент - сторінку даних у файлі, тому є також ідентифікатор ресурсу. Він являє собою комбінацію ідентифікатора файлу даних (fileId) і номера сторінки всередині файлу (pageId), наприклад, 1: 1723

    RID - запис. Віртуальна запис належить сторінці, тому так само, як і у випадку з сторінкою, ObjId збігається з сторінковим і, відповідно, табличним ідентифікаторами. Фізичний еквівалент також присутня - це слот у сторінці даних, відповідно, є й ідентифікатор ресурсу, який складається з ідентифікатора файлу даних, ідентифікатора сторінки даних і, нарешті, ідентифікатора запису всередині сторінки. Наприклад, 1:1723:2

    Щоб визначити, які блокування на що накладені певний момент часу, що існує спеціальна процедура зберігається sp_Lock, але користуватися нею не завжди зручно, оскільки видається багато зайвої інформації, і об'єкти виводяться без імен. Тому я вважаю за краще користуватися ось таким запитом:        

    SELECT   

    convert (smallint, req_spid)   As spid,   

    rsc_dbid As dbid,   

    rsc_objid As ObjId,   

    so.name as ObjName,   

    rsc_indid As IndId,   

    substring (v.name, 1, 4) As   Type,   

    substring (rsc_text, 1, 16) as   Resource,   

    substring (u.name, 1, 8) As   Mode,   

    substring (x.name, 1, 5) As   Status   

    FROM master.dbo.syslockinfo   sl   

    INNER JOIN   master.dbo.spt_values v ON sl.rsc_type = v.number   

    INNER JOIN   master.dbo.spt_values x ON sl.req_status = x.number   

    INNER JOIN   master.dbo.spt_values u ON sl.req_mode + 1 = u.number   

    LEFT JOIN sysobjects so ON   sl.rsc_objid = so.ID   

    WHERE v.type = 'LR' and x.type   = 'LS' and u.type = 'L' and so.Name = '<ім'я таблиці>'   

    ORDER BY spid     

    Цей запит поверне всі блокування, накладені на вказану таблицю, з ось такими даними: SPID - ідентифікатор користувача сесії, DBID - ідентифікатор бази даних, ObjID - ідентифікатор об'єкта, ObjName - ім'я об'єкта, Type - тип об'єкта, Resource - ідентифікатор ресурсу, Mode - тип блокування, Status - статус блокування.

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

    ПОПЕРЕДЖЕННЯ   

    Даний розділ є лише описом   необхідної термінології і ні в якому разі не претендує на повноту. За більш   повною інформацією рекомендую звернутися до джерел, згаданих у списку   літератури.     

    Взаімоблокіровка

    Більшість способів забезпечення паралелізму, хоча б почасти заснованих на блокування, схильне взаімоблокіровкам (deadlock). І хоча відомі досить дотепні алгоритми, що дозволяють не допускати подібних ситуацій в принципі, в комерційних додатках вони майже не зустрічаються. Microsoft SQL Server тут не є винятком, і також схильний до взаімоблокіровкам (вони ж «мертві блокування» або «тупикові ситуації »).

    Взаімоблокіровка, як можна зрозуміти з назви - це ситуація, коли транзакції блокують один одного таким чином, що подальше виконання неможливе. У силу протоколу двофазної блокування жодна з що беруть участь у взаімоблокіровке транзакцій не може відпустити вже захоплені їй ресурси до того, як накладе блокування на все, що їй необхідно. А одержати всі необхідні ресурси заважають вже накладені блокування. Таким чином, виходить замкнуте коло. Природно, і транзакцій, і об'єктів в загальному випадку може бути як завгодно багато. Розірвати таку блокування без зовнішнього втручання неможливо, і якщо не вживати спеціальних зусиль, то транзакції будуть перебувати в стані очікування нескінченно довго. Дозволити подібну ситуацію можна лише шляхом скасування хоча б однієї з транзакцій.

    Вбудовані способи визначення взаімоблокіровок

    Зрозуміло, що доводити до нескінченного очікування не стоїть, і, як правило, в СУБД, в тій чи іншій мірі підданої взаімоблокіровкам, присутні механізми визначення подібних тупикових ситуацій та їх усунення.

    Timeout based

    Найпростіший спосіб - це ввести деякий фіксований час очікування (timeout), і якщо транзакція виявилася заблокованій більше цього часу, то вважати, що вона увійшла в тупикову ситуацію і скасувати її. Недоліки цього способу очевидні - немає стовідсоткової гарантії, що скасована транзакція була однією з учасниць взаімоблокіровкі. Збільшення часу тайм-ауту збільшує цю вірогідність, але одночасно збільшує час виявлення дійсно намертво заблокованих транзакцій. А це, у свою чергу, веде до збільшення часу простою запитів, що не беруть участь у взаімоблокіровке, але очікують ресурсів, захоплених намертво заблокованими транзакціями.

    Wait-for graph based

    Існують і більш вдалий спосіб визначення взаімоблокіровок (хоча і більш трудомісткий). Для цього менеджер блокувань будує спрямований граф, який називається «графом очікування» (wait-for graph). В вершинах цього графа знаходяться транзакції, а в ребрах - залежності. Наприклад, ребро Ti-> Tj з'являється в тому випадку, якщо Ti чекає, поки Tj звільнить який-небудь об'єкт. Таким чином, якщо у графі очікування виникає цикл (T1-> T2-> ... -> Tn-> T1), то T1 чекає сама себе, як і всі інші n транзакцій в циклі, отже, транзакції за?? локіровани намертво. У даному випадку виявлення взаімоблокіровок зводиться до знаходження замкнутих циклів в графі очікування. Самі залежності в граф додаються і знищуються за міру отримання і зняття блокувань, технічно в цьому нічого складного немає. Складність лише в тому, як часто менеджер блокувань повинен перевіряти граф очікування на наявність циклів. Теоретично це можна робити кожного разу при додавання нової залежності, однак робити перевірки так часто занадто накладно, оскільки, як правило, кількість звичайних блокувань набагато вище мертвих, до того ж сама взаімоблокіровка нікуди не дінеться і дочекається, поки за нею прийдуть. Тому перевіряти наявність циклів можна або коли в граф додається якась фіксована кількість граней, або знову ж таки, за закінчення якогось таймауту. Але тут, на відміну від попереднього способу, гарантується, що буде знайдено саме мертва блокування, а також, що ми виявимо всі мертві блокування, а не тільки ті, які протрималися досить довго.

    До того ж тут сервер має куди більшою інформацією про тупикової ситуації, що дозволяє йому з меншими втратами розв'язати конфлікт. Розірвати взаімоблокіровку, як уже говорилося, можна лише скасувавши одну з транзакцій, що входять в замкнутий цикл. Сервер при виборі жертви може керуватися наступними міркуваннями:

    Об'єм роботи, виконаний транзакцією (вся ця робота буде втрачено в разі скасування).

    Кількість роботи, яке доведеться виконати, щоб призвести скасування транзакції. Менеджер повинен намагатися уникати скасування транзакції, що практично завершена.

    Кількість циклів, в яких бере участь транзакція. Теоретично транзакція може входити в кілька циклів в графі очікування, таким чином, скасування однієї транзакції може призвести до зняття кількох взаімоблокіровок.

    Одна і та ж транзакція може кілька разів поспіль увійти в контексте і бути скасованої, перезавантажте і знову потрапити в цикл. Щоб уникнути подібного циклічного рестарту, алгоритм вибору жертви повинен також враховувати, скільки разів транзакція була скасована через мертвих блокувань.

    Timestamp based

    Існують механізми, що дозволяють взагалі не допускати безвихідних ситуацій при використанні протоколу двофазної блокування, наприклад, на основі тимчасових міток (timestamp).        

    ПРИМІТКА   

    Тут головне - не заплутатися.   Існує як спосіб забезпечення паралелізму на основі тимчасових міток --   це одна з альтернатив протоколу двофазної блокування, так і спосіб   запобігання тупикових ситуацій. Це два абсолютно різних механізму, і   зараз ми обговорюємо саме спосіб запобігання взаімоблокіровок.     

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

    «очікування-загибель» (wait-die). Якщо транзакція T1 «Старший» Т2, тоді транзакції Т1 дозволяється перебувати в стані очікування на заблоковано. Якщо ж Т1 «молодше» T2, тоді Т1 відкочується.

    «поранення-очікування» (wound-wait). Якщо транзакція T1 «Старший» T2, тоді T1 «ранить» T2; поранення звичайно носить «смертельний» характер -- транзакція Т2 відкочується, якщо тільки до моменту отримання «поранення» T2 НЕ виявляється вже завершеною. У цьому випадку Т2 «виживає» і відкат не відбувається. Якщо ж Т1 «молодше» Т2, тоді Т1 дозволяється перебувати в стані очікування на блокування.

    Переваги цього способу полягають в першу чергу в тому, що взаімоблокіровкі не допускаються в принципі. Цей спосіб дещо простіше в реалізації, ніж побудова і відстеження графа очікування. І, нарешті, відсутня ймовірність циклічного рестарту скасованої транзакції, тому що при відкаті тимчасова мітка зберігається, і будь-яка транзакція з часом гарантовано стане найбільш «старшої», а значить, її не відкат.

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

    Реалізація в Microsoft SQL Server

    У Microsoft SQL Server використовується механізм усунення взаімоблокіровок на основі графа очікування. Граф будується при кожному запиті блокування. Після закінчення якогось тайм-ауту прокидається монітор блокувань, і якщо він виявляє, що якась транзакція чекає занадто довго, ініціюється процес знаходження замкнутого циклу в графі очікування. У разі виявлення мертвої блокування відбувається відкат однією з транзакцій, що беруть участь у циклі. «Жертва» обчислюється залежно від обсягу зробленої роботи, яка у свою чергу визначається по кількості записів у журналі транзакцій, які необхідно відкинути. Проте є можливість вказати серверу, яку транзакцію краще бачити в якості "жертви", з допомогою команди:        

    SET   DEADLOCK_PRIORITY (LOW | NORMAL | @ deadlock_var)     

    Тут @ deadlock_var - мінлива в діапазоні від 1 до 12, чим менше число, тим нижче пріоритет; LOW відповідає 3, а NORMAL - 6.

    Але як би сервер не старався, все, що він зможе зробити зі своєї ініціативи - це скасувати одну з транзакцій. Самостійно Microsoft SQL Server скасовану транзакцію заново не запускає, а повертає повідомлення про помилку. Тому в клієнтському додатку необхідно передбачити обробку даній ситуації і, можливо, перезапуск скасованої транзакції. Однак з ряду причин цілком покладатися на обробку подібних помилок в Додатку не слід, це останній рубіж оборони по захисту нервів кінцевого користувача. Недоліки від перекладання всієї відповідальності на клієнта в цьому випадку такі:

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

    Монітор, що відслідковує замкнуті цикли в графі очікування, не працює безперервно, таким чином, взаімоблокіровка НЕ виявляється миттєво. Це знижує продуктивність системи в цілому через того, що ні в чому неповинні транзакції змушені чекати, поки менеджер не скасує одну з намертво заблокованих.

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

    Можливі причини виникнення взаімоблокіровок

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

    Строго кажучи, всі випадки взаімоблокіровкі зводяться до порушення порядку доступу до об'єктів. Далі розберемо кілька прикладів транзакцій, потенційно здатних призвести до тупикової ситуації. Але перед цим, щоб приклади були більш наочними, треба вибрати базу для експериментів (наприклад стандартну Northwind) і створити в ній табличку для подальших дослідів. Для цього досить виконати в Query Analyzer'е ось такий скрипт:        

    --- Вибір тестової бази   

    USE Northwind   

    GO      

    --- Створення таблиці   

    if exists (select * from dbo.sysobjects where id =   object_id (N '[dbo]. [Tbl]')   

    and OBJECTPROPERTY (id,   N'IsUserTable ') = 1)   

    drop table [dbo]. [Tbl]   

    GO      

    CREATE TABLE [dbo]. [Tbl] (   

    [X] [int] NULL,   

    [Y] [int] NULL,   

    [value] [varchar] (50))   

    GO      

    --- Заповнення тестовими   даними   

    insert into Tbl (X, Y, Value) VALUES (1, 10, 'Алма-Ата')   

    insert into Tbl (X, Y, Value) VALUES (2, 9, 'Алушта')   

    insert into Tbl (X, Y, Value) VALUES (3, 8, 'Алупка')   

    insert into Tbl (X, Y, Value) VALUES (4, 7, 'Анкара')   

    insert into Tbl (X, Y, Value) VALUES (5, 6, 'Агра')   

    insert into Tbl (X, Y, Value) VALUES (6, 5, 'Анапа')   

    insert into Tbl (X, Y, Value) VALUES (7, 4, 'Альбукерке')   

    insert into Tbl (X, Y, Value) VALUES (8, 3, 'Алансон')   

    insert into Tbl (X, Y, Value) VALUES (9, 2, 'Абіньо')   

    insert into Tbl (X, Y, Value) VALUES (10, 1, 'Абакан')     

    Перший приклад

    Самий тривіальний випадок, порушення порядку доступу в чистому вигляді, являють собою дві транзакції приблизно такого змісту:

    Перша транзакція - T1.        

    BEGIN   TRAN   

    UPDATE Tbl SET X = 1 WHERE X = 1   

    UPDATE Tbl SET X = 3 WHERE X = 3   

    COMMIT TRAN     

    Друга транзакція - T2.        

    BEGIN   TRAN   

    UPDATE Tbl SET X = 3 WHERE X = 3   

    UPDATE Tbl SET X = 1 WHERE X = 1   

    COMMIT TRAN     

    Якщо ці транзакції стартують одночасно, то відбудеться взаімоблокіровка з причини очевидного порушення порядку доступу. T1 спочатку звертається до запису X = 1, а потім до запису X = 3. Т2 ж, навпаки, спочатку звертається до запису X = 3, а потім до X = 1.

    Малюнок 1. Порядок звернення до записів, що приводить до взаімоблокіровке.

    При одночасному старті Т1 захоплює запис X = 1, в цей час Т2 встигає захопити запис X = 3. Потім T1 хоче захопити запис X = 3, але вона вже захоплена T2, тому T1 очікує T2 на блокування, і в граф додається ребро T1-> T2. Приблизно в цей же час T2 хоче захопити запис X = 1, яка також вже захоплена T1. У графі очікування з'являється друге ребро T2-> T1 і він стає циклічним. Ну а оскільки подібна ситуація без грубого втручання нерозв'язна, то одна з транзакцій буде скасована, інша ж, користуючись тим, що блокування зникла, спокійно завершить свою роботу.

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

    Другий приклад

    Наступні приклади я постараюся розібрати більш докладно, емуліруя поведінку сервера в реальній ситуації.

    Дуже часто зустрічається приблизно така послідовність операторів в одній транзакції:        

    SET TRANSACTION ISOLATION LEVEL REPEATABLE READ   

    BEGIN TRAN   

    SELECT @ Var = Y FROM Tbl WHERE   X = 2   

    ---   

    --- тут виконуються які-небудь   обчислення над @ Var.   

    ---   

    UPDATE Tbl SET Y = @ Var WHERE   X = 2   

    COMMIT TRAN     

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

    Виконаємо вищенаведений T-SQL-код із транзакцій T1 і T2. Щоб імітувати можливий розвиток подій при паралельній роботі будемо виконувати транзакції по частинах. Спочатку половину T1, потім цілком T2, а потім частину, що залишилася T1. Ефект буде точно таким же, як якби в реальному додатку між двома операторами T1 встигла б пролізти транзакція T2. На Насправді для отримання взаімоблокіровкі достатньо, щоб між двома операторами T1 встиг втиснутися тільки перший оператор T2, подальший порядок операцій вже не має значення.

    Отже, виконаємо перша частина T1 в одному з вікон Query Analyser'а:        

    --- встановимо необхідний рівень   ізоляції   

    SET ISOLATION LEVEL REPEATABLE READ   

    BEGIN   TRANSACTION   

    SELECT * FROM Tbl WHERE X = 2     

    Всі виповнилося успішно, але транзакція все ще вважається активною, ми її не скасували і не зафіксували. Якщо в цей момент подивитися на блокування, накладені на таблицю Tbl, можна побачити приблизно наступну картину, з точністю до констант:        

    spid dbid   ObjId ObjName IndId Type   Resource Mode Status   

    ------   ------ ---------- ------- ----- ---- --------- ----- ---- -   

    54 6   2034106287 Tbl 0 PAG 1:17495 IS   GRANT   

    54 6   2034106287 Tbl 0 RID 1:17495:1 S GRANT   

    54   6 2034106287 Tbl 0 TAB   IS GRANT     

    Іншими словами, ми наклали колективну блокування (S) на певний запис (RID 1:17495:1), і дві колективні блокування наміри (IS) вище в ієрархії, на сторінку і таблицю. Відкриємо нове з'єднання з тією ж базою в новому вікні QA і спробуємо виконати цю ж транзакцію цілком:        

    --- встановимо необхідний рівень   ізоляції   

    SET ISOLATION LEVEL REPEATABLE READ   

    BEGIN   TRAN   

    SELECT * FROM Tbl WHERE X = 2   

    UPDATE Tbl SET Y = 3 WHERE X = 2   

    COMMIT TRAN     

    блокувань, природно, додалося:        

    spid dbid   ObjId ObjName IndId Type   Resource Mode Status   

    ------   ------ ----------- ------- ------ ---- ---------- ----- -- ------   

    54 6   2034106287 Tbl 0 PAG 1:17495 IS   GRANT   

    54 6   2034106287 Tbl 0 RID 1:17495:1 S   GRANT   

    54 6   2034106287 Tbl 0 TAB   IS GRANT      

    61 6   2034106287 Tbl 0 PAG 1:17495 IX   GRANT   

    61 6   2034106287 Tbl 0 RID 1:17495:1 X   CNVT   

    61 6   2034106287 Tbl 0 TAB   IX GRANT     

    Ті, що (в моєму випадку) від spid 54 - це накладені раніше, від першого транзакції, а ті, у яких spid 61 - від другої. З блокуваннями наміри все те ж саме, вони запитані й успішно отримані. А ось з ексклюзивними ситуація така: спочатку, виконуючи SELECT, ми отримали поділювану блокування на ту ж запис (RID 1:17495:1), що і перший транзакцію. Потім нам знадобилося туди ж записати, а для цього треба сконвертировать колективну блокування S до X. Проте зробити це не виходить, тому що заважає S-блокування на ту ж запис від перших транзакції. Що ми і бачимо в третьому рядку знизу, статус ексклюзивного блокування (X) CNVT - Конвертація. Тобто SELECT виконався, але до UPDATE справа не дійшла, T2 чекає, поки T1 звільнить запис X = 2, щоб накласти ексклюзивну блокування.

    Переключитися назад в першому вікно і спробуємо завершити T1:        

      UPDATE Tbl SET Y = 3 WHERE X = 2   

    COMMIT TRAN     

    Тепер і T1 буде чекати, поки T2 звільнить свою колективну блокування. Таким чином, транзакції будуть очікувати один одного, цикл в графі очікування замкнеться і, через якийсь час, коли менеджер блокувань це виявить, одна з транзакцій буде скасована. Програма, запустити її, отримає повідомлення про 1205 взаімоблокіровке (Transaction (Process ID 61) was deadlocked on lock resources with another process and has been chosen as the deadlock victim. Rerun the transaction), а інша транзакція завершиться успішно.

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

    Способи усунення

    Оскільки взаімоблокіровка сталася через те, що транзакції утримували колективні блокування і потім спробували їх підвищити до ексклюзивних, то, в принципі, допомогти уникнути неприємностей у даному випадку зможе зниження рівня ізоляції до READ COMMITED. При цьому колективна блокування не буде триматися до кінця транзакції, а зніметься відразу після завершення читання, а значить, оновити запису ніщо не завадить. Але тоді замість взаімоблокіровкі ми цілком можемо отримати невірні дані, оскільки між SELECT і UPDATE зможе втиснутися інша транзакція, яка змінить Y і дані, отримані SELECT 'на момент UPDATE, виявляться неактуальними, чого в деяких випадках допускати не можна.

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

    У загальному випадку найкращим виходом тут буде накладення при читанні проміжної блокування оновлення. Таке блокування сумісна з колективної, що дозволить переглядає його транзакцій звертатися кетім даними безперешкодно. А коли знадобиться їх оновити, то проблем бути не повинно, тому що блокування оновлення між собою несумісні, і значить, інші транзакції, які читають ці дані для подальшої зміни (і природно теж запросивши їх з блокуванням оновлення), будуть чекати, поки ці дані зміняться, нікому не заважаючи. Для цього необхідно змінити перший оператор транзакції приблизно таким чином:        

    SELECT   @ Var = Y FROM Tbl WITH (UPDLOCK) WHERE X = 2     

    Третій приклад

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

    Підготуємо дві транзакції в різних вікнах QA і, відповідно, в різних підключеннях.

    Перша транзакція: T1        

    - встановимо необхідний рівень ізоляції   

    SET   TRANSACTION ISOLATION LEVEL READ COMMITTED   

    BEGIN   TRAN   

    UPDATE Tbl SET X = 4 WHERE X = 4 - відновимо рядок X = 4   

    WAITFOR DELAY '00: 00:10 '   

    UPDATE Tbl SET X = 6 WHERE X = 6 - відновимо рядок X = 6   

    COMMIT TRAN     

    Варто зауважити, що рівень ізоляції в даному випадку відповідає рівню ізоляції, виставляється за замовчуванням, і робити його нижче вкрай не рекомендується. Оператор WAITFOR потрібен для того, щоб сервер виконував команди не відразу один за одним, а з розривом у 10 секунд, щоб ми встигли переключитися в друге вікно і стартувати другий транзакцію, імітуючи тим самим одночасність їх виконання.

    Друга транзакція: T2        

    --- встановимо необхідний рівень ізоляції   

    SET   TRANSACTION ISOLATION LEVEL READ COMMITTED BEGIN TRAN   

    UPDATE Tbl SET X = 2 WHERE X = 2 - відновимо рядок X = 2   

    COMMIT TRAN     

    Природно, зовсім не важливо, які значення ми запишемо в X в цих транзакції, в даному випадку важливо лише умова вибірки.

    Запустивши T1, а потім, зосередившись і запустив T2, ми отримаємо взаімоблокіровку. Зверніть увагу, що на перший погляд транзакції цілком нешкідливі. Більш того, умови ніяк не перетинаються за діапазонами, в першому випадку зачіпаються рядки X = 4 і X = 6, а в другому X = 2. Можна піти ще далі, і змінити в T2 умову таким чином:        

    UPDATE   Tbl SET Y = 10 WHERE Y = 10     

    Тоді умови вибірки не буде перетинатися навіть за полів! Але взаімоблокіровка все одно відбудеться.

    Як уже згадувалося вище, в реальній ситуації знайти винні транзакції буває досить проблематично, тому що за замовчуванням відомий тільки процес, що запустив транзакцію-«жертву» і, як правило, цього недостатньо.

    Визначення «винних» транзакцій

    Існує можливість змусити сервер видати більше повну інформацію про помилку. Однак не слід цією можливістю зловживати, тому що продуктивність сервера при цьому серйозно знижується. Для більш тонкого налаштування сервер підтримує прапори трасування (trace flags). Деякі з цих прапорів призначені для одержання більш повної інформації про помилки. Прапори встановлюються за допомогою команди DBCC TRACEON (flag, ...), а знімаються, відповідно за допомогою DBCC TRACEOFF (flag, ...).

    Ось короткий перелік прапорів, які можуть стати в нагоді при вилов взаімоблокіровок:

    1204 - збір розширеної інформації про взаімоблокіровке.

    3605 - видача інформації в EventLog.

    3406 - видача інформації у файл errorlog.

    -1 - збір інформації з усіх сесій.

    1206 - збір інформації не тільки про блокування, що беруть участь у тупиковій ситуації (що робить прапор 1204), але й про інші блокування, накладених заблокованими транзакціями.

    1200 - збір інформації про порядок накладення блокувань (недокументовані).

    Зараз нас цікавить прапор під номером 1204 - видача розширеної інформації про взаімоблокіровке, отримати ж інформацію при виставленому прапорі можна двома способами.

    Запустити SQL Profiler, спеціальну програму для відстеження роботи сервера, і налаштувати в ній перехоплення помилок (event class Errors and Warnings: Exception and Error Log), а потім виставити прапор трасування 3605. У цьому випадку вся додаткова інформація про роботу SQL-сервера буде скидатися в Event Log і перехоплюватися профайлером, де її надалі

         
     
         
    Реферат Банк
     
    Рефераты
     
    Бесплатные рефераты
     

     

     

     

     

     

     

     
     
     
      Все права защищены. Reff.net.ua - українські реферати ! DMCA.com Protection Status