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

     

     

     

     

     

         
     
    Блокування в MS SQL Server 2000
         

     

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

    Блокування в MS SQL Server 2000

    Олексій Ширшов

    Введення

    Зазвичай блокування розглядають спільно з транзакціями. У цій статті наголос робиться в основному на механізм блокувань, його внутрішній устрій та використання в СУБД MS SQL Server 2000. Передбачається, що читач добре знайомий з транзакціями і їх властивостями. Давайте згадаємо коротко, якими властивостями повинні володіти транзакції в сучасних СУБД (ці вимоги носять назву ACID - Atomicity, Consistency, Isolation і Durability):

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

    Consistency (узгодженість). Після виконання транзакції всі дані повинні залишитися в узгодженому стані. Іншими словами, транзакція або не змінить даних, і вони залишаться в колишньому стані, або змінені дані будуть задовольняти обмеженням цілісності, правилами (rules) та іншими критеріями узгодженості даних.

    Isolation (ізольованість). Транзакції повинні виконаються автономно і незалежно від інших транзакцій. При одночасному виконання безлічі конкуруючих один з одним транзакцій, будь-яке оновлення певної транзакції буде приховано від інших до тих пір, поки ця транзакція не буде зафіксовано. Існують декілька рівнів ізольованості (ізоляції) транзакцій, які дозволяють вибрати найбільш оптимальне рішення з точки зору продуктивності та цілісності даних. Основним методом реалізації цих рівнів і є блокування, про які піде мова в цій статті.

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

    У даній статті розглядаються механізми реалізації рівнів ізольованості транзакції. Стандартом ANSI було визначено чотири рівня ізоляції транзакцій. Перший - це нульовий рівень ізоляції, друга -- перший рівень і так далі. Ці рівні допомагають вирішувати різні проблеми, які будуть розглядатися детально далі в процесі написання демонстраційної програми на С + +. Визначення рівнів будуть дані в кінці розділу.

    Отже, щоб краще зрозуміти проблеми ізоляції транзакцій, розглянемо їх спочатку з точки зору програмування на С + +. Так як наша програма буде оперувати простими даними (значення типу int), будемо вважати, що вимоги атомарності виконуються автоматично. Крім того, ми не будемо висувати жодних логічних обмежень на значення змінної і не будемо використовувати вкладених транзакцій, так що вимоги узгодженості і стійкості також будуть опущені.

    Наша програма містить лише два класи: CObject і CProxy. Клас CObject - це об'єкт-одинак (singleton), який містить змінну value (доступ до цієї змінної ми і будемо захищати), і певний набір службових функцій. Клас CProxy являє собою посередника для об'єкта CObject; саме з ним буде працювати клієнт. От первісний начерк (в класі CProxy використовується нестандартна конструкція __declspec (property), підтримувана тільки компіляторами від Microsoft):        

    class CObject;      

    class CProxy   

    (   

    friend class CObject;   

    public:   

    __declspec (property (get = get_Value, put = put_Value))   int value;   

    int get_Value (int level = -1)   const;   

    void put_Value (int i);      

    void Commit ();   

    void Rollback ();      

    private:   

    int _level;   

    int _value;      

    bool fUpd;      

    CProxy (CObject * par, int level)   

    (   

    fUpd = false;   

    parent = par;   

    _level = level;   

    )      

    CObject * parent;   

    );      

    class CObject   

    (   

    friend class CProxy;   

    public:      

    static CProxy &   GetObject (int level = -1);      

    ~ CObject ()   

    (   

    if (hMutex)   CloseHandle (hMutex);   

    )      

    protected:   

    CProxy & BeginTran (int   level)   

    (   

    return * (new   CProxy (this, level ));   

    )      

    void RequestExclusive (int   level)   

    (   

    )   

      

    void RequestShared (int level)   

    (   

    )      

    void RemoveShared (int level)   

    (   

    )      

    void RemoveLocks ()   

    (   

    )      

    private:   

    CObject ()   

    (   

    value = 0;   

    )      

    int value;   

    static HANDLE hMutex;   

    );      

    __declspec (selectany) HANDLE CObject:: hMutex = NULL;      

    CProxy & CObject:: GetObject (int level)   

    (   

    HANDLE hLocMutex =   CreateMutex (NULL, TRUE, _T ( "Guard-Lock-Mutex "));   

    bool flg = GetLastError () ==   ERROR_ALREADY_EXISTS;   

      

    if (flg)   WaitForSingleObject (hLocMutex, INFINITE);   

    else CObject:: hMutex =   hLocMutex;      

    static CObject obj;      

    ReleaseMutex (hLocMutex);   

      

    if (flg)   CloseHandle (hLocMutex);      

    return obj.BeginTran (level);      

    )      

    void CProxy:: Commit ()   

    (   

    parent-> RemoveLocks ();   

    delete this;   

    )      

    void CProxy:: Rollback ()   

    (   

    if (fUpd)   

    parent-> value = _value;   

    parent-> RemoveLocks ();   

    delete this;   

    )      

    void CProxy:: put_Value (int i)   

    (   

    parent-> RequestExclusive (_level);   

    if (! fUpd)   

    _value = parent-> value;   

    parent-> value = i;   

    fUpd = true;   

    )      

    int CProxy:: get_Value (int level) const   

    (   

    if (level == -1)   

    level = _level;      

    parent-> RequestShared (level);   

    int v = parent-> value;   

    parent-> RemoveShared (level);   

    return v;   

    )     

    Клієнт ніколи не має справи безпосередньо з екземпляром класу CObject. Примірники класу CProxy - представляють копію даних об'єкта CObject і делегують запити на читання і запис змінної value. Код вийшов трохи громіздким: до чого такі складнощі? Я заздалегідь визначив досить широкий інтерфейс, щоб потім менше виправляти. :)

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

    CProxy &   CObject:: GetObject (int level)   

    (   

    static CObject obj;   

    return obj.BeginTran (level);   

    )     

    Чим він поганий? Справа в тому, що якщо декілька потоків спробують одночасно викликати функцію GetObject, конструктор класу CObject може бути викликаний більше одного разу, тому що компілятор (можливо, це його помилка) не генерує безпечний код перевірки з використанням асемблерні інструкції cmpxchg. Хоча ймовірність виникнення такої ситуації досить низька, я рекомендую все-таки не ігнорувати її. Найпростіше рішення проблеми полягає у використанні недорогого ресурсу критичної секції, наприклад, так:        

    CProxy & CObject:: GetObject (int level)   

    (   

    :: EnterCriticalSection (& g_cs);   

    static CObject obj;   

    :: LeaveCriticalSection (& g_cs);   

    return obj.BeginTran (level);   

    )     

    Однак постає питання: де її ініціалізувати? Можна в конструкторі глобального об'єкту, але якщо в нас буде такий же глобальний клієнт, ми не зможемо гарантувати, що ініціалізація критичної секції відбудеться раніше виклику функції GetObject. Нам потрібно щось, що створюється, ініціалізується і захоплює ресурс безпосередньо у функції GetObject. У як цього «чогось» я вибрав об'єкт виконавчої системи «М'ютекс». Його використання і ви можете спостерігати в початковому коді.

    Тепер розглянемо приклад з використанням цих класів, який відразу розкриває першу проблему.        

    unsigned __stdcall thread_proc (void *)   

    (   

    // Початок транзакції   

    CProxy & prx =   CObject:: GetObject ();   

    prx.value = 20;   

    prx.Commit ();   

    return 0;   

    )      

    int main (int argc, char * argv [])   

    (   

    // Початок транзакції   

    CProxy & prx =   CObject:: GetObject ();   

    prx.value = 10;      

    // Початок нової сесії   

    _beginthreadex (0,0, thread_proc, 0,0,0);      

    // Емуліруем роботу   

    // Sleep (1000);   

    printf ( "% dn", prx.value);   

    prx.Commit ();   

    return 0;   

    )     

    Тут я в двох паралельних потоках змінюю значення змінної value об'єкта CObject: в одному - на 10, у другому - на 20. Що виведеться на консоль? Визначено сказати не можна: якщо розкоментувати рядок Sleep (1000), виведеться 20. З закоментовані рядком виводиться 10. Ця проблема носить назву «проблема втрати останньої зміни» (lost update problem) або проблема «брудної» запису. Вона полягає в тому, що при одночасному виконанні транзакцій, у яких виробляється зміна даних, неможливо сказати заздалегідь, яке кінцеве значення візьмуть дані після фіксування обох транзакцій. У разі «брудної» записи тільки одна з усіх паралельно що виконуються транзакцій буде працювати з дійсними даними, інші - ні. Іншими словами, хоча дані і знаходитимуться в узгодженому стані, логічна їх цілісність буде порушена.

    Для того, щоб наш об'єкт задовольняв першого рівня ізоляції транзакцій, на якому забороняється «забруднення» даних, перепишемо його таким чином (зміни стосуються тільки класу CObject):        

    class CObject   

    (   

    friend class CProxy;   

    public:      

    enum (READ_UNCOMMITTED);      

    static CProxy &   GetObject (int level = -1);      

    ~ CObject ()   

    (   

      DeleteCriticalSection (& exclusive);   

    if (hMutex)   CloseHandle (hMutex);   

    )      

    protected:   

    CProxy & BeginTran (int   level)   

    (   

    return * (new   CProxy (this, level ));   

    )      

    void RequestExclusive (int   level)   

    (   

    if (level> =   READ_UNCOMMITTED)   

    TestExclusive ();   

    )   

      

    void RequestShared (int level)   

    (   

    )      

    void RemoveShared (int level)   

    (   

    )      

    void RemoveLocks ()   

    (   

    RemoveAllLocks ();   

    )      

    private:   

    CObject ()   

    (   

    value = 0;   

      InitializeCriticalSection (& exclusive);   

    )      

    void TestExclusive ()   

    (   

    // Перевірка на монопольну блокування   

    EnterCriticalSection (& exclusive);      

    // Увійшли більше одного разу   

    if (exclusive.RecursionCount   > 1)   

      LeaveCriticalSection (& exclusive);   

    )      

    void RemoveAllLocks ()   

    (   

    // Якщо була встановлена монопольна   блокування - знімаємо   

    if (exclusive.OwningThread ==   (HANDLE) GetCurrentThreadId ())   

      LeaveCriticalSection (& exclusive);   

    )      

    int value;   

    CRITICAL_SECTION exclusive;   

    static HANDLE hMutex;   

    );     

    Доданий код виділено. Хочеться пояснити одну деталь: так як змінювати значення змінної value можна кілька разів, а Commit (або Rollback) викликати тільки раз, у наявності той факт, що функція EnterCriticalSection викликає більшу кількість разів, ніж LeaveCriticalSection. Це помилка - згідно з документацією кількість викликів функцій EnterCriticalSection і LeaveCriticalSection має збігатися. Тому після входу в критичну секцію я перевіряю поле RecursionCount, яке встановлює кількість повторних входів потоку в критичну секцію.        

    ПРИМІТКА   

    При роботі під ОС сімейства Windows 9x   це поле не використовується і завжди містить 0, так що приводиться тут і   далі код буде працювати тільки на операційних системах сімейства NT.     

    Тепер можна впевнено сказати, що виведеться на консоль в наступному прикладі:        

    unsigned __stdcall thread_proc (void *)   

    (   

    // Початок другого транзакції   

    CProxy & prx =   CObject:: GetObject (CObject:: READ_UNCOMMITTED);      

    // Тут потік буде очікувати приблизно 1 сек. До тих пір, поки   

    // в головному потоці не буде виконана   рядок prx.Commit ();   

    prx.value = 20;   

    prx.Commit ();   

    return 0;   

    )      

    int main (int argc, char * argv [])   

    (   

    // Початок транзакції з 0 рівнем ізоляції   

    CProxy & prx =   CObject:: GetObject (CObject:: READ_UNCOMMITTED);      

    // Зміна даних   

    prx.value = 10;      

    // Відкриваємо нову сесію   

    _beginthreadex (0,0, thread_proc, 0,0,0);      

    // Print CObject:: value variable   

    printf ( "% dn", prx.value);   

    prx.Commit ();   

    return 0;   

    )     

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

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

    unsigned   __stdcall thread_proc (void *)   

    (   

    CProxy & prx =   CObject:: GetObject (CObject:: READ_UNCOMMITTED);   

    prx.value = 20;      

    // Емуліруем роботу   

    Sleep (1000);   

    prx.value   = 40;   

    prx.Commit ();      

    //   Закриття сесії   

    return 0;   

    )         

    int   main (int argc, char * argv [])   

    (   

    // Відкриття сесії   

    _beginthreadex (0,0, thread_proc, 0,0,0);      

    // Емуліруем роботу   

    Sleep (100);   

    CProxy & fake =   CObject:: GetObject (CObject:: READ_UNCOMMITTED);      

    // В   цієї рядку відбувається читання «брудних даних»   

    //   fake.get_Value () повертає 20   

    int * pAr = new int [fake.get_Value ()];      

    // Емуліруем роботу   

    Sleep (1000);      

    // fake.value = 40   

    for (int i = 0; i   

    pAr [i] = 0;   

      

    if (pAr) delete [] pAr;   

    fake.Commit ();   

    return   0;   

    )     

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

    Для вирішення цієї проблеми вводиться новий рівень ізоляції, на якому забороняється «брудну» читання. Ось такі зміни потрібно внести в реалізацію класів CProxy і CObject для того, щоб програма задовольняла другого рівня ізоляції:        

    class CObject   

    (   

    friend class CProxy;   

    public:      

    enum   (READ_UNCOMMITTED, READ_COMMITTED);      

    static CProxy &   GetObject (int level = -1);      

    ~ CObject ()   

    (   

      DeleteCriticalSection (& exclusive);   

    if (hShared)   CloseHandle (hShared);   

    if (hMutex)   CloseHandle (hMutex);   

    )      

    protected:   

    CProxy & BeginTran (int   level)   

    (   

    return * (new   CProxy (this, level ));   

    )      

    void RequestExclusive (int   level)   

    (   

    if (level> =   READ_UNCOMMITTED)   

    TestExclusive ();   

    )   

      

    void RequestShared (int level)   

    (   

    if (level>   READ_UNCOMMITTED)   

    TestShared (level);   

    )      

    void RemoveShared (int level)   

    (   

    if (level == READ_COMMITTED) (   

    RemoveSharedLock ();   

    )   

    )      

    void RemoveLocks ()   

    (   

    RemoveAllLocks ();   

    )      

    private:   

    CObject ()   

    (   

    value = 0;   

      InitializeCriticalSection (& exclusive);   

    hShared =   CreateEvent (NULL, FALSE, TRUE, NULL);   

    )      

    void TestShared (int level)   

    (   

    // Перевірка на монопольну блокування   

    EnterCriticalSection (& exclusive);      

    // Встановлюємо поділювану блокування   

    // тільки якщо не була встановлена   монопольна блокування   

    if (exclusive.RecursionCount == 1)   

    ResetEvent (hShared);      

    // Знімаємо монопольну блокування   

      LeaveCriticalSection (& exclusive);   

    )      

    void TestExclusive ()   

    (   

    // Перевірка на поділювану блокування   

    WaitForSingleObject (hShared, INFINITE);      

    // Перевірка на монопольну блокування   

    EnterCriticalSection (& exclusive);      

    // Увійшли більше одного разу   

    if (exclusive.RecursionCount   > 1)   

      LeaveCriticalSection (& exclusive);   

    )      

    void RemoveSharedLock ()   

    (   

    SetEvent (hShared);   

    )      

    void RemoveAllLocks ()   

    (   

    RemoveSharedLock ();      

    // Якщо була встановлена монопольна   блокування - знімаємо   

    if (exclusive.OwningThread ==   (HANDLE) GetCurrentThreadId ())   

      LeaveCriticalSection (& exclusive);   

    )      

    int value;   

    CRITICAL_SECTION exclusive;   

    HANDLE hShared;   

    static HANDLE hMutex;   

    );     

    Тепер, якщо змінити константу READ_UNCOMMITTED в попередньому прикладі на READ_COMMITTED як параметр GetObject, все стане на свої місця. При ініціалізації масиву головний потік перейде в стан очікування до тих пір, поки другий потік не виконає рядок prx.Commit (); Розмір масиву в головному потоці буде дорівнює 40 елементам.

    Добре, чудово! Де там наступний рівень? :) Щоб зрозуміти, навіщо потрібен наступний рівень ізоляції транзакцій «повторюване читання », розглянемо такий приклад:        

    unsigned __stdcall thread_proc (void *)   

    (   

    (   

    // Початок транзакції   

    CProxy & prx =   CObject:: GetObject (CObject:: READ_COMMITTED);   

    prx.value = 20;   

    prx.Commit ();   

    )   

    // Емуліруем роботу   

    Sleep (500);   

    (   

    // Початок транзакції   

    CProxy & prx =   CObject:: GetObject (CObject:: READ_COMMITTED);   

    prx.value = 40;   

    prx.Commit ();   

    )   

    return 0;   

    )      

    int main (int argc, char * argv [])   

    (   

    // Початок сесії   

    _beginthreadex (0,0, thread_proc, 0,0,0);      

    // Емуліруем роботу   

    Sleep (100);      

    CProxy & fake =   CObject:: GetObject (CObject:: READ_COMMITTED);   

    // Створення масиву   

    int * pAr = new   int [fake.get_Value ()];   

      

    // Емуліруем роботу   

    Sleep (1000);      

    // Ініціалізація масиву   

    for (int i = 0; i <   fake.value; i ++)   

    pAr [i] = 0;      

    if (pAr) delete [] pAr;   

    fake.Commit ();   

    return 0;   

    )     

    Якщо запустити цей приклад, він, як і попередній, призведе до помилки доступу до пам'яті. Справа в тому, що спочатку створюється масив розміром в 20 елементів, а в циклі ініціалізації використовується значення 40, і на 21 елементі ми отримаємо помилку доступу.

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

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

    void RemoveShared (int level)   

    (   

    if (level == READ_COMMITTED) (   

    RemoveSharedLock ();      

    )   

    )     

    нам потрібно лише додати нову константу в перерахування типів блокувань.        

    enum   (READ_UNCOMMITTED, READ_COMMITTED, REPEATABLE_READ);     

    Тепер, якщо в наведеному вище прикладі змінити константу READ_COMMITTED на REPEATABLE_READ як параметр GetObject, код запрацює правильно і без помилок.        

    ПРИМІТКА   

    Зовсім не обов'язково міняти   рівень ізоляції транзакцій в потоці thread_proc, робота прикладу не   зміниться, навіть якщо змінити рівень ізоляції на READ_UNCOMMITTED.     

    Тут ми ставимо блокування оновлення, якщо транзакція читає дані з рівнем ізоляції REPEATABLE_READ.

    На закінчення, перед тим як навести повністю код з підтримкою перших трьох рівнів ізоляції, давайте поговоримо ось про що. Створений код реалізує блокує модель, яка характерна для СУБД MS SQL Server 2000. Існує також версійність модель реалізації блокувань, яку підтримує така відома СУБД, як Oracle. Чим відрізняються ці моделі? Розглянемо такий код:        

    unsigned __stdcall thread_proc (void *)   

    (   

    // Print CObject:: value   variable   

    CProxy & fake =   CObject:: GetObject ();   

    printf ( "in second session:   % dn ", fake.value);   

    fake.Commit ();   

    return 0;   

    )      

    int main (int argc, char * argv [])   

    (   

    // Початок транзакції   

    CProxy & prx =   CObject:: GetObject ();   

    prx.value = 10;      

    // Початок нової сесії   

    _beginthreadex (0,0, thread_proc, 0,0,0);      

    // Емуліруем роботу   

    Sleep (100);   

    printf ( "in primary   session:% dn ", prx.value);   

    prx.Commit ();   

    return 0;   

    )     

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

    in   second session: 10   

    in   primary session: 10     

    Однак при використанні версійність моделі ми повинні отримати        

    in   second session: 0   

    in   primary session: 10     

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

    ПРИМІТКА   

    Oracle зберігає ці копії даних у   спеціальному сховищі, який називається rollback segment.     

    версійність модель характеризується тим, що в ній відсутній нульовий рівень ізоляції транзакцій (READ UNCOMMITTED), і замість нього вводиться новий рівень, що в наведеному далі коді я назвав SNAP_SHOT. Він відрізняється від стандартного тим, що дозволяє читати дійсні зафіксовані дані, навіть при наявності незавершені транзакцій оновлення.

    Ось кінцевий варіант класів CProxy і CObject, який реалізує обидві моделі і, на додачу до цього, підтримує два «Хінта»: UPDLOCK і XLOCK. Вони призначені для зміни рівня ізоляції безпосередньо при роботі зі значенням змінної, а їхній зміст я поясню в наступних розділах.        

    # define MSSQL   

    // # define ORACLE      

    class CObject;      

    class CProxy   

    (   

    friend class CObject;   

    public:   

    __declspec (property (get = get_Value, put = put_Value))   int value;   

    int get_Value (int level = -1)   const;   

    void put_Value (int i);      

    void Commit ();   

    void Rollback ();      

    private:   

    int _level;   

    int _value;      

    bool fUpd;      

    CProxy (CObject * par, int level)   

    (   

    fUpd = false;   

    parent = par;   

    _level = level;   

    )      

    CObject * parent;   

    );      

    class CObject   

    (   

    friend class CProxy;   

    public:      

    enum (   

    # ifdef MSSQL   

    READ_UNCOMMITTED,   

    # elif defined ORACLE   

    SNAP_SHOT,   

    # endif   

      READ_COMMITTED, REPEATABLE_READ, UPDLOCK, XLOCK);      

    static CProxy &   GetObject (int level = -1);      

    ~ CObject ()   

    (   

      DeleteCriticalSection (& exclusive);   

      DeleteCriticalSection (& update);   

    if (hShared)   CloseHandle (hShared);   

    if (hMutex)   CloseHandle (hMutex);   

    )      

    protected:   

    CProxy & BeginTran (int   level)   

    (   

    return * (new   CProxy (this, level ));   

    )      

    void RequestExclusive (int   level)   

    (   

    ATLASSERT (level <= REPEATABLE_READ);   

    # ifdef MSSQL   

    if (level> =   READ_UNCOMMITTED)   

    # elif defined ORACLE   

    if (level> = SNAP_SHOT)   

    # endif   

    TestExclusive ();   

    )   

      

    void RequestShared (int level)   

    (      

    # ifdef MSSQL   

    if (level>   READ_UNCOMMITTED)   

    # elif defined ORACLE   

    if (level> SNAP_SHOT)   

    # endif   

    TestShared (level);   

    )      

    void RemoveShared (int level)   

    (   

    if (level == READ_COMMITTED) (   

    RemoveSharedLock ();   

    )   

    )      

    void RemoveLocks ()   

    (   

    RemoveAllLocks ();   

    )      

    private:   

    CObject ()   

    (   

    value = 0;   

    InitializeCriticalSection (& update);   

      InitializeCriticalSection (& exclusive);   

    hShared =   CreateEvent (NULL, FALSE, TRUE, NULL);   

    )      

    void TestShared (int level)   

    (   

    // Перевірка на монопольну блокування   

    EnterCriticalSection (& exclusive);      

    // Встановлюємо блокування оновлення   

    if (level == UPDLOCK) (   

    EnterCriticalSection (& update);      

    // Увійшли більше одного разу   

    if (update.RecursionCount   > 1)   

      LeaveCriticalSection (& update);   

    )   

    else if (level! = XLOCK) (   

      

    // Встановлюємо поділювану блокування   

    // тільки якщо не була встановлена   блокування оновлення або   

    // монопольна блокування   

    if (update.OwningThread! = (HANDLE) GetCurrentThreadId ()   & &   

    exclusive.RecursionCount ==   1)   

    ResetEvent (hShared);      

    // Знімаємо монопольну блокування   

    LeaveCriticalSection (& exclusive);   

    )   

    // Якщо вказаний XLOCK монопольна блокування залишається      

    )      

    void TestExclusive ()   

    (   

    // Перевірка на поділювану блокування   

    WaitForSingleObject (hShared, INFINITE);      

    // Перевірка на блокування оновлення   

    EnterCriticalSection (& update);      

    // Перевірка на монопольну блокування   

    EnterCriticalSection (& exclusive);      

    // Знімаємо блокування оновлення   

      LeaveCriticalSection (& update);      

    // Увійшли більше одного разу   

    if (exclusive.RecursionCount   > 1)   

      LeaveCriticalSection (& exclusive);   

    )      

    void RemoveSharedLock ()   

    (   

    SetEvent (hShared);   

    )      

    void RemoveAllLocks ()   

    (   

    RemoveSharedLock ();   

      

    // Якщо була встановлена блокування   оновлення - знімаємо   

    if (update.OwningThread ==   (HANDLE) GetCurrentThreadId ())   

    LeaveCriticalSection (& update);      

    // Якщо була встановлена монопольна   блокування - знімаємо   

    if (exclusive.OwningThread ==   (HANDLE) GetCurrentThreadId ())   

      LeaveCriticalSection (& exclusive);   

    )      

    int value;   

    CRITICAL_SECTION update;   

    CRITICAL_SECTION exclusive;   

    HANDLE hShared;   

    static HANDLE hMutex;   

    );      

    __declspec (selectany) HANDLE CObject:: hMutex = NULL;      

    CProxy & CObject:: GetObject (int level)   

    (   

    HANDLE hLocMutex =   CreateMutex (NULL, TRUE, _T ( "Guard-Lock-Mutex "));   

    bool flg = GetLastError () ==   ERROR_ALREADY_EXISTS;   

      

    if (flg)   WaitForSingleObject (hLocMutex, INFINITE);   

    else CObject:: hMutex =   hLocMutex;      

    static CObject obj;      

    ReleaseMutex (hLocMutex);   

      

    if (flg)   CloseHandle (hLocMutex);      

    return obj.BeginTran (level);      

    )      

    void CProxy:: Commit ()   

    (   

    # ifdef ORACLE   

    parent-> value = _value;   

    # endif   

    parent-> RemoveLocks ();   

    delete this;   

    )      

    void CProxy:: Rollback ()   

    (   

    # ifdef MSSQL   

    if (fUpd)   

    parent-> value = _value;   

    # endif   

    parent-> RemoveLocks ();   

    delete this;   

    )      

    void CProxy:: put_Value (int i)   

    (   

    parent-> RequestExclusive (_level);   

    # ifdef MSSQL   

    if (! fUpd)   

    _value = parent-> value;   

    parent-> value = i;   

    # elif defined ORACLE   

    _value = i;   

    # endif   

    fUpd = true;   

    )      

    int CProxy:: get_Value (int level) const   

    (   

    if (level == -1)   

    level = _level;      

    parent-> RequestShared (level);   

    # ifdef MSSQL   

    int v = parent-> value;   

    parent-> RemoveShared (level);   

    return v;   

    # elif defined ORACLE   

    return _value;   

    # endif   

    )     

    З цих прикладів має бути зрозуміло, що блокування -- справа серйозна. :) Але, перш ніж перейти до розгляду їх реалізації в MS SQL Server 2000, я наведу обіцяні на початку рівні визначення ізоляції транзакцій. Кожен рівень включає в себе попередній з пред'явленням більше жорстких вимог до ізоляції.

    No trashing of data (заборона «забруднення» даних). Забороняється зміна одних їх тих же даних двома і більше паралельними транзакціями. Змінювати дані може тільки одна транзакція, якщо якась інша транзакція спробує зробити це, вона повинна бути заблокована до закінчення роботи першої транзакції.

    No dirty read (заборона «брудного» читання). Якщо дана транзакція змінює дані, іншим транзакцій забороняється читати ці дані до тих пір, поки першим транзакція не завершиться.

    No nonrepeatable read (заборона неповторюваних читання). Якщо ця транзакція читає дані, забороняється змінювати ці дані до тих пір, поки перша транзакція не завершить роботу. При цьому інші транзакції можуть отримувати доступ на читання даних.

    No phantom (заборона фантомів). Якщо дана транзакція проводить вибірку даних, що відповідають будь-якому логічному умові, інші транзакції не можуть ні змінити ці дані, ні вставляти нові дані, які задовольняють того ж логічного умові.

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

    У таблиці 1 підводиться підсумок цього розділу і вивчення рівнів ізоляції.        

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

    Забруднення даних         

    Брудне читання         

    неповторним читання         

    Фантоми             

    READ UNCOMMITTED         

    -         

    +         

    +         

    +             

    READ COMMITTED         

    -         

    -         

    +         

    +             

    REPEATABLE READ         

    -         

    -         

    -         

    +             

    SERIALIZABLE         

    -         

    -         

    -         

    -     

    Блокування

    Блокування в MS SQL Server 2000 (надалі просто сервер) - це механізм реалізації вимоги ізольованості транзакцій. Вся подальша інформація специфічна тільки для зазначеного сервера.

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

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

    SET TRANSACTION ISOLATION LEVEL     

    Більш докладно цю команду і хинт для операторів T-SQL ми розглянемо в наступному розділі. Поки ж я хочу детально зупинитися на типи блокувань.

    Блокування застосовуються для захисту спільно використовуваних ресурсів сервера. Як об'єкти блокувань можуть виступати наступні суті:

    База даних (позначається DB). При накладенні блокування на базу даних блокуються всі вхідні в неї таблиці.

    Таблиця (позначається TAB). При накладенні блокування на таблицю блокуються всі екстенти даної таблиці, а також всі її індекси.        

    ПРИМІТКА   

    Екстент - це група з 8 сторінок.   

    Сторінка - мінімальна одиниця зберігання   даних у файлі бази даних. Розмір сторінки становить 8 Кб.     

    Екстент (позначається EXT). При накладенні блокування на екстент блокуються всі сторінки, що входять в даний екстент.

    Сторінка (позначається PAG). При накладенні блокування на сторінку блокуються всі рядки даної сторінки.

    Рядок (позначається RID).

    Діапазон індексу (позначається KEY). Блокуються дані, що відповідають діапазону індексу, на оновлення, вставку і видалення.

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

    ПРИМІТКА   

    Блокування займає 96 байт. [1] Загальна   кількість блокувань може варіюватися від 5000 до 2 147 483 647.   Конкретне значення можна задати за допомогою збереженої процедури sp_configure з   параметром locks.     

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

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

    Оптимістичний метод управління характеризується тим, що замість безпосереднього читання даних береться значення з буфері. Ніяких блокувань при цьому не накладається. Інші транзакції можуть спокійно читати або навіть змінювати дані. У момент фіксування транзакції система порівнює попереднє (заздалегідь збережене) значення даних з поточним. Якщо вони співпадають, виконуються операції блокування, оновлення та розблокування даних. Якщо ж значення відрізняються, то система генерує помилку і відкочується транзакцію. Хоча такий підхід не відповідає вимогам стандарту, він дозволяє в певних випадках досягти кращої продуктивності, ніж песимістичний підхід. Переваги цього режиму очевидні: система не втрачає часу на установку блокувань і ресурсів для їх створення. Однак для систем з великою кількістю користувачів, часто змінюють дані, такий режим використовувати не рекомендується, тому що ціна відкату транзакції і її повторного виконання значно вище установки блокування при читанні даних.

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

    Блокування - надзвичайно важливий і невід'ємний механізм функціонування сервера. Вони застосовуються для кожного запиту на читання або оновлення даних, а також у багатьох інших випадках (наприклад, при створення нової сесії). Роботою з блокуваннями займається спеціальний модуль SQL Server'а - менеджер блокувань (Lock Manager). У його завдання входить:

    створення та встановлення блокувань;

    зняття блокувань;

    ескалація блокувань;

    визначення сумісності блокувань;

    усунення взаімоблокіровок (deadlocks) і багато інше.

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

    Момент зняття блокування сильно залежить від поточного рівня ізоляції і типу запиту. Наприклад, при виконанні запиту на вибірку даних при рівні ізоляції нижче REPEATABLE READ менеджер блокувань знімає блокування відразу ж після отримання всіх даних з поточної сторінки. При цьому відбувається встановлення блокування на наступну сторінку.

    Прості блокування

    SQL Server підтримує три основні типи блокувань:

    Колективна блокування (Shared Lock), позначається латинською буквою S. Ця найпоширеніший тип блокування, який використовується під час виконання операції читання даних. Гарантується що дані, на які вона накладена, не будуть змінені інший транзакцією. Однак читання даних можливе.

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

    Блокування оновлення (Update Lock), позначається латинською буквою U. Це блокування є проміжною між розділяється і монопольної блокуванням. Так до?? до монопольна блокування не сумісна ні з одним видом інших блокувань (є один виняток, про який пізніше), її установка призводить до повного блокування ресурсу. Якщо транзакція хоче оновити дані в якийсь найближчий момент часу, але не зараз, і, коли цей момент прийде, не хоче чекати іншої транзакції, вона може запросити блокування оновлення. У цьому випадку іншим транзакцій дозволяється встановлювати колективні блокування, але не дозволяє встановлювати монопольні. Іншими словами, якщо ця транзакція встановила на ресурс блокування оновлення, ніяка інша транзакція не зможе отримати на цей же ресурс монопольну блокування або блокування оновлення до тих пір, поки встановила блокування транзакція не буде завершена.

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

    Назва колонки         

    Опис             

    spid         

    Ідентифікатор процесу SQL   Server.             

    dbid         

    Ідентифікатор бази даних.             

    ObjId         

    Ідентифікатор об'єкта, на   який встановлена блокування.             

    IndId         

    Ідентифікатор індексу.             

    Type         

    Тип об'єкта. Може   приймати значення: DB, EXT, TAB, PAG, RID, KEY.             

    Resource         

    Вміст колонки   syslocksinfo.restext. Зазвичай це ідентифікатор рядка (для типу RID) або   ідентифікатор сторінки (для типу PAG).             

    Mode         

    Тип блокування. Може приймати значення: Sch-S, Sch-M,   S, U, X, IS, IU, IX, SIU, SIX, UIX, BU, RangeS-S, RangeS-U, RangeIn-Null,   RangeIn-S, RangeIn-U, RangeIn-X, RangeX-S, RangeX-U, RangeX-X. Про ці значеннях буде сказано нижче.             

    Status         

    Статус процесу SQL Server.   Може приймати значення: GRANT, WAIT, CNVRT.     

    Ця процедура повертає дані про блокування з системної таблиці syslockinfo, що знаходиться в базі даних master.        

    ПРИМІТКА   

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

    У всіх прикладах використовується таблиця test, яка створюється наступним скриптом:        

    create   table test (i int, n varchar (20))   

    insert   into test values (1, 'alex')   

    insert   into test values (2, 'rosa')   

    insert   into test values (3, 'dima')     

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

    print   @ @ spid   

    begin   tran select * from test     

    Ми почали транзакцію, але залишили її відкритою. Для того, щоб подивитися, які блокування накладені попереднім скриптом, викличемо процедуру sp_lock (в іншій сесії) з параметром, виведеним print @ @ spid (у мене це 54).        

    РАДА   

    Поточне значення ідентифікатора процесу   сервера можна побачити в рядку стану програми Query Analizer.             

    sp_lock 54     

    Результат наведено в таблиці 3.        

    spdi         

    dbid         

    O

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

     

     

     

     

     

     

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