pAr [i] = 0; p>
p>
if (pAr) delete [] pAr; p>
fake.Commit (); p>
return
0; p>
) p>
Якщо відкомпілювати і запустити цей код, він
гарантовано призведе до помилки під час виконання, так як буде здійснений
вихід за кордон масиву в циклі. Чому? Тому що при створенні масиву
використовується значення незафіксованих даних, а в циклі - зафіксованих.
Ця проблема відома як проблема «брудного читання». Вона виникає, коли один
транзакція намагається прочитати дані, з якими працює інша паралельна
транзакцію. У такому випадку тимчасові, непідтверджені дані можуть не
задовольняти обмеженням цілісності або правилами. І, хоча до моменту фіксації
транзакції вони можуть бути приведені в «порядок», інша транзакція вже може скористатися
цими невірними даними, що приведе до порушення її роботи. p>
Для вирішення цієї проблеми вводиться новий рівень
ізоляції, на якому забороняється «брудну» читання. Ось такі зміни потрібно
внести в реалізацію класів CProxy і CObject для того, щоб програма
задовольняла другого рівня ізоляції: p>
class CObject p>
( p>
friend class CProxy; p>
public: p>
enum
(READ_UNCOMMITTED, READ_COMMITTED); p>
static CProxy &
GetObject (int level = -1); p>
~ CObject () p>
( p>
DeleteCriticalSection (& exclusive); p>
if (hShared)
CloseHandle (hShared); p>
if (hMutex)
CloseHandle (hMutex); p>
) p>
protected: p>
CProxy & BeginTran (int
level) p>
( p>
return * (new
CProxy (this, level )); p>
) p>
void RequestExclusive (int
level) p>
( p>
if (level> =
READ_UNCOMMITTED) p>
TestExclusive (); p>
) p>
p>
void RequestShared (int level) p>
( p>
if (level>
READ_UNCOMMITTED) p>
TestShared (level); p>
) p>
void RemoveShared (int level) p>
( p>
if (level == READ_COMMITTED) ( p>
RemoveSharedLock (); p>
) p>
) p>
void RemoveLocks () p>
( p>
RemoveAllLocks (); p>
) p>
private: p>
CObject () p>
( p>
value = 0; p>
InitializeCriticalSection (& exclusive); p>
hShared =
CreateEvent (NULL, FALSE, TRUE, NULL); p>
) p>
void TestShared (int level) p>
( p>
// Перевірка на монопольну блокування p>
EnterCriticalSection (& exclusive); p>
// Встановлюємо поділювану блокування p>
// тільки якщо не була встановлена
монопольна блокування p>
if (exclusive.RecursionCount == 1) p>
ResetEvent (hShared); p>
// Знімаємо монопольну блокування p>
LeaveCriticalSection (& exclusive); p>
) p>
void TestExclusive () p>
( p>
// Перевірка на поділювану блокування p>
WaitForSingleObject (hShared, INFINITE); p>
// Перевірка на монопольну блокування p>
EnterCriticalSection (& exclusive); p>
// Увійшли більше одного разу p>
if (exclusive.RecursionCount
> 1) p>
LeaveCriticalSection (& exclusive); p>
) p>
void RemoveSharedLock () p>
( p>
SetEvent (hShared); p>
) p>
void RemoveAllLocks () p>
( p>
RemoveSharedLock (); p>
// Якщо була встановлена монопольна
блокування - знімаємо p>
if (exclusive.OwningThread ==
(HANDLE) GetCurrentThreadId ()) p>
LeaveCriticalSection (& exclusive); p>
) p>
int value; p>
CRITICAL_SECTION exclusive; p>
HANDLE hShared; p>
static HANDLE hMutex; p>
); p>
Тепер, якщо змінити константу READ_UNCOMMITTED в
попередньому прикладі на READ_COMMITTED як параметр GetObject, все стане
на свої місця. При ініціалізації масиву головний потік перейде в стан
очікування до тих пір, поки другий потік не виконає рядок prx.Commit (); Розмір
масиву в головному потоці буде дорівнює 40 елементам. p>
Добре, чудово! Де там наступний рівень? :) Щоб
зрозуміти, навіщо потрібен наступний рівень ізоляції транзакцій «повторюване
читання », розглянемо такий приклад: p>
unsigned __stdcall thread_proc (void *) p>
( p>
( p>
// Початок транзакції p>
CProxy & prx =
CObject:: GetObject (CObject:: READ_COMMITTED); p>
prx.value = 20; p>
prx.Commit (); p>
) p>
// Емуліруем роботу p>
Sleep (500); p>
( p>
// Початок транзакції p>
CProxy & prx =
CObject:: GetObject (CObject:: READ_COMMITTED); p>
prx.value = 40; p>
prx.Commit (); p>
) p>
return 0; p>
) p>
int main (int argc, char * argv []) p>
( p>
// Початок сесії p>
_beginthreadex (0,0, thread_proc, 0,0,0); p>
// Емуліруем роботу p>
Sleep (100); p>
CProxy & fake =
CObject:: GetObject (CObject:: READ_COMMITTED); p>
// Створення масиву p>
int * pAr = new
int [fake.get_Value ()]; p>
p>
// Емуліруем роботу p>
Sleep (1000); p>
// Ініціалізація масиву p>
for (int i = 0; i <
fake.value; i ++) p>
pAr [i] = 0; p>
if (pAr) delete [] pAr; p>
fake.Commit (); p>
return 0; p>
) p>
Якщо запустити цей приклад, він, як і попередній,
призведе до помилки доступу до пам'яті. Справа в тому, що спочатку створюється масив
розміром в 20 елементів, а в циклі ініціалізації використовується значення 40, і на
21 елементі ми отримаємо помилку доступу. P>
Проблема повторного читання полягає в тому, що між
операціями читання в одній транзакції інші транзакції можуть безперешкодно
вносити будь-які зміни, так що повторне читання тихж дані призведе до
іншого результату. p>
Для підтримки третього рівня ізоляції в код змін
вносити не треба! :) Необхідно лише не знімати колективні блокування до кінця
транзакції. Так як метод, наведений нижче, знімає блокування тільки на
рівні READ_COMMITTED: p>
void RemoveShared (int level) p>
( p>
if (level == READ_COMMITTED) ( p>
RemoveSharedLock ();
p>
) p>
) p>
нам потрібно лише додати нову константу в перерахування
типів блокувань. p>
enum
(READ_UNCOMMITTED, READ_COMMITTED, REPEATABLE_READ); p>
Тепер, якщо в наведеному вище прикладі змінити
константу READ_COMMITTED на REPEATABLE_READ як параметр GetObject, код
запрацює правильно і без помилок. p>
ПРИМІТКА p>
Зовсім не обов'язково міняти
рівень ізоляції транзакцій в потоці thread_proc, робота прикладу не
зміниться, навіть якщо змінити рівень ізоляції на READ_UNCOMMITTED. p>
Тут ми ставимо блокування оновлення, якщо транзакція
читає дані з рівнем ізоляції REPEATABLE_READ. p>
На закінчення, перед тим як навести повністю код з
підтримкою перших трьох рівнів ізоляції, давайте поговоримо ось про що. Створений
код реалізує блокує модель, яка характерна для СУБД MS SQL Server
2000. Існує також версійність модель реалізації блокувань, яку
підтримує така відома СУБД, як Oracle. Чим відрізняються ці моделі?
Розглянемо такий код: p>
unsigned __stdcall thread_proc (void *) p>
( p>
// Print CObject:: value
variable p>
CProxy & fake =
CObject:: GetObject (); p>
printf ( "in second session:
% dn ", fake.value); p>
fake.Commit (); p>
return 0; p>
) p>
int main (int argc, char * argv []) p>
( p>
// Початок транзакції p>
CProxy & prx =
CObject:: GetObject (); p>
prx.value = 10; p>
// Початок нової сесії p>
_beginthreadex (0,0, thread_proc, 0,0,0); p>
// Емуліруем роботу p>
Sleep (100); p>
printf ( "in primary
session:% dn ", prx.value); p>
prx.Commit (); p>
return 0; p>
) p>
Тут у другій сесії (що виконується в окремому
потоці) ми просто читаємо дані і виводимо їх на консоль. Так як значення
змінної value ми змінили перед стартом другої сесії, цілком очевидно,
що на екран буде виведено p>
in
second session: 10 p>
in
primary session: 10 p>
Однак при використанні версійність моделі ми повинні
отримати p>
in
second session: 0 p>
in
primary session: 10 p>
Причина в тому, що для кожної транзакції зберігається своя
копія даних (snap-shot), яка синхронізується з основними даними тільки в
момент фіксування транзакції. p>
ПРИМІТКА p>
Oracle зберігає ці копії даних у
спеціальному сховищі, який називається rollback segment. p>
версійність модель характеризується тим, що в ній
відсутній нульовий рівень ізоляції транзакцій (READ UNCOMMITTED), і замість
нього вводиться новий рівень, що в наведеному далі коді я назвав
SNAP_SHOT. Він відрізняється від стандартного тим, що дозволяє читати
дійсні зафіксовані дані, навіть при наявності незавершені транзакцій
оновлення. p>
Ось кінцевий варіант класів CProxy і CObject, який
реалізує обидві моделі і, на додачу до цього, підтримує два «Хінта»: UPDLOCK і
XLOCK. Вони призначені для зміни рівня ізоляції безпосередньо при
роботі зі значенням змінної, а їхній зміст я поясню в наступних розділах. p>
# define MSSQL p>
// # define ORACLE p>
class CObject; p>
class CProxy p>
( p>
friend class CObject; p>
public: p>
__declspec (property (get = get_Value, put = put_Value))
int value; p>
int get_Value (int level = -1)
const; p>
void put_Value (int i); p>
void Commit (); p>
void Rollback (); p>
private: p>
int _level; p>
int _value; p>
bool fUpd; p>
CProxy (CObject * par, int level) p>
( p>
fUpd = false; p>
parent = par; p>
_level = level; p>
) p>
CObject * parent; p>
); p>
class CObject p>
( p>
friend class CProxy; p>
public: p>
enum ( p>
# ifdef MSSQL p>
READ_UNCOMMITTED, p>
# elif defined ORACLE p>
SNAP_SHOT, p>
# endif p>
READ_COMMITTED, REPEATABLE_READ, UPDLOCK, XLOCK); p>
static CProxy &
GetObject (int level = -1); p>
~ CObject () p>
( p>
DeleteCriticalSection (& exclusive); p>
DeleteCriticalSection (& update); p>
if (hShared)
CloseHandle (hShared); p>
if (hMutex)
CloseHandle (hMutex); p>
) p>
protected: p>
CProxy & BeginTran (int
level) p>
( p>
return * (new
CProxy (this, level )); p>
) p>
void RequestExclusive (int
level) p>
( p>
ATLASSERT (level <= REPEATABLE_READ); p>
# ifdef MSSQL p>
if (level> =
READ_UNCOMMITTED) p>
# elif defined ORACLE p>
if (level> = SNAP_SHOT) p>
# endif p>
TestExclusive (); p>
) p>
p>
void RequestShared (int level) p>
( p>
# ifdef MSSQL p>
if (level>
READ_UNCOMMITTED) p>
# elif defined ORACLE p>
if (level> SNAP_SHOT) p>
# endif p>
TestShared (level); p>
) p>
void RemoveShared (int level) p>
( p>
if (level == READ_COMMITTED) ( p>
RemoveSharedLock (); p>
) p>
) p>
void RemoveLocks () p>
( p>
RemoveAllLocks (); p>
) p>
private: p>
CObject () p>
( p>
value = 0; p>
InitializeCriticalSection (& update); p>
InitializeCriticalSection (& exclusive); p>
hShared =
CreateEvent (NULL, FALSE, TRUE, NULL); p>
) p>
void TestShared (int level) p>
( p>
// Перевірка на монопольну блокування p>
EnterCriticalSection (& exclusive); p>
// Встановлюємо блокування оновлення p>
if (level == UPDLOCK) ( p>
EnterCriticalSection (& update); p>
// Увійшли більше одного разу p>
if (update.RecursionCount
> 1) p>
LeaveCriticalSection (& update); p>
) p>
else if (level! = XLOCK) ( p>
p>
// Встановлюємо поділювану блокування p>
// тільки якщо не була встановлена
блокування оновлення або p>
// монопольна блокування p>
if (update.OwningThread! = (HANDLE) GetCurrentThreadId ()
& & p>
exclusive.RecursionCount ==
1) p>
ResetEvent (hShared); p>
// Знімаємо монопольну блокування p>
LeaveCriticalSection (& exclusive); p>
) p>
// Якщо вказаний XLOCK монопольна блокування залишається p>
) p>
void TestExclusive () p>
( p>
// Перевірка на поділювану блокування p>
WaitForSingleObject (hShared, INFINITE); p>
// Перевірка на блокування оновлення p>
EnterCriticalSection (& update); p>
// Перевірка на монопольну блокування p>
EnterCriticalSection (& exclusive); p>
// Знімаємо блокування оновлення p>
LeaveCriticalSection (& update); p>
// Увійшли більше одного разу p>
if (exclusive.RecursionCount
> 1) p>
LeaveCriticalSection (& exclusive); p>
) p>
void RemoveSharedLock () p>
( p>
SetEvent (hShared); p>
) p>
void RemoveAllLocks () p>
( p>
RemoveSharedLock (); p>
p>
// Якщо була встановлена блокування
оновлення - знімаємо p>
if (update.OwningThread ==
(HANDLE) GetCurrentThreadId ()) p>
LeaveCriticalSection (& update); p>
// Якщо була встановлена монопольна
блокування - знімаємо p>
if (exclusive.OwningThread ==
(HANDLE) GetCurrentThreadId ()) p>
LeaveCriticalSection (& exclusive); p>
) p>
int value; p>
CRITICAL_SECTION update; p>
CRITICAL_SECTION exclusive; p>
HANDLE hShared; p>
static HANDLE hMutex; p>
); p>
__declspec (selectany) HANDLE CObject:: hMutex = NULL; p>
CProxy & CObject:: GetObject (int level) p>
( p>
HANDLE hLocMutex =
CreateMutex (NULL, TRUE, _T ( "Guard-Lock-Mutex ")); p>
bool flg = GetLastError () ==
ERROR_ALREADY_EXISTS; p>
p>
if (flg)
WaitForSingleObject (hLocMutex, INFINITE); p>
else CObject:: hMutex =
hLocMutex; p>
static CObject obj; p>
ReleaseMutex (hLocMutex); p>
p>
if (flg)
CloseHandle (hLocMutex); p>
return obj.BeginTran (level); p>
) p>
void CProxy:: Commit () p>
( p>
# ifdef ORACLE p>
parent-> value = _value; p>
# endif p>
parent-> RemoveLocks (); p>
delete this; p>
) p>
void CProxy:: Rollback () p>
( p>
# ifdef MSSQL p>
if (fUpd) p>
parent-> value = _value; p>
# endif p>
parent-> RemoveLocks (); p>
delete this; p>
) p>
void CProxy:: put_Value (int i) p>
( p>
parent-> RequestExclusive (_level); p>
# ifdef MSSQL p>
if (! fUpd) p>
_value = parent-> value; p>
parent-> value = i; p>
# elif defined ORACLE p>
_value = i; p>
# endif p>
fUpd = true; p>
) p>
int CProxy:: get_Value (int level) const p>
( p>
if (level == -1) p>
level = _level; p>
parent-> RequestShared (level); p>
# ifdef MSSQL p>
int v = parent-> value; p>
parent-> RemoveShared (level); p>
return v; p>
# elif defined ORACLE p>
return _value; p>
# endif p>
) p>
З цих прикладів має бути зрозуміло, що блокування --
справа серйозна. :) Але, перш ніж перейти до розгляду їх реалізації в MS SQL
Server 2000, я наведу обіцяні на початку рівні визначення ізоляції
транзакцій. Кожен рівень включає в себе попередній з пред'явленням більше
жорстких вимог до ізоляції. p>
No trashing of data (заборона «забруднення» даних).
Забороняється зміна одних їх тих же даних двома і більше паралельними
транзакціями. Змінювати дані може тільки одна транзакція, якщо якась
інша транзакція спробує зробити це, вона повинна бути заблокована до закінчення
роботи першої транзакції. p>
No dirty read (заборона «брудного» читання). Якщо
дана транзакція змінює дані, іншим транзакцій забороняється читати ці
дані до тих пір, поки першим транзакція не завершиться. p>
No nonrepeatable read (заборона неповторюваних
читання). Якщо ця транзакція читає дані, забороняється змінювати ці дані
до тих пір, поки перша транзакція не завершить роботу. При цьому інші
транзакції можуть отримувати доступ на читання даних. p>
No phantom (заборона фантомів). Якщо дана
транзакція проводить вибірку даних, що відповідають будь-якому логічному
умові, інші транзакції не можуть ні змінити ці дані, ні вставляти нові
дані, які задовольняють того ж логічного умові. p>
Якщо ви не зовсім зрозуміли суть останнього рівня
ізоляції, не турбуйтеся. Я спеціально залишив його на потім, тому що в
даний момент немає можливості розглянути приклади, що описують проблему і
механізми її уникнення. p>
У таблиці 1 підводиться підсумок цього розділу і вивчення
рівнів ізоляції. p>
Рівні ізоляції p>
Забруднення даних p>
Брудне читання p>
неповторним читання p>
Фантоми p>
READ UNCOMMITTED p>
- p>
+ p>
+ p>
+ p>
READ COMMITTED p>
- p>
- p>
+ p>
+ p>
REPEATABLE READ p>
- p>
- p>
- p>
+ p>
SERIALIZABLE p>
- p>
- p>
- p>
- p>
Блокування
p>
Блокування в MS SQL Server 2000 (надалі просто
сервер) - це механізм реалізації вимоги ізольованості транзакцій. Вся
подальша інформація специфічна тільки для зазначеного сервера. p>
Існує три основних типи блокувань і безліч
специфічних. Сервер встановлює блокування автоматично в залежності від
поточного рівня ізоляції транзакції, однак при бажанні ви можете змінити тип
за допомогою спеціальних підказок - хинт. p>
При відкритті нової сесії за замовчуванням вибирається
рівень ізоляції READ COMMITTED. Ви можете змінити цей рівень для даного
з'єднання за допомогою команди: p>
SET TRANSACTION ISOLATION LEVEL p>
Більш докладно цю команду і хинт для операторів
T-SQL ми розглянемо в наступному розділі. Поки ж я хочу детально зупинитися
на типи блокувань. p>
Блокування застосовуються для захисту спільно
використовуваних ресурсів сервера. Як об'єкти блокувань можуть виступати
наступні суті: p>
База даних (позначається DB). При накладенні
блокування на базу даних блокуються всі вхідні в неї таблиці. p>
Таблиця (позначається TAB). При накладенні блокування
на таблицю блокуються всі екстенти даної таблиці, а також всі її індекси. p>
ПРИМІТКА p>
Екстент - це група з 8 сторінок. p>
Сторінка - мінімальна одиниця зберігання
даних у файлі бази даних. Розмір сторінки становить 8 Кб. P>
Екстент (позначається EXT). При накладенні блокування
на екстент блокуються всі сторінки, що входять в даний екстент. p>
Сторінка (позначається PAG). При накладенні блокування
на сторінку блокуються всі рядки даної сторінки. p>
Рядок (позначається RID). p>
Діапазон індексу (позначається KEY). Блокуються
дані, що відповідають діапазону індексу, на оновлення, вставку і видалення. p>
SQL Server сам вибирає найбільш оптимальний об'єкт
для блокування, проте користувач може змінити цю поведінку за допомогою тих
ж хинт. При автоматичному визначенні об'єкта блокування сервер повинен
вибрати найбільш відповідний з точки зору продуктивності та паралельної
роботи користувачів. Чим менше деталізація блокування (рядок - найвища
ступінь деталізації), тим нижче її вартість, але нижче і можливість паралельної
роботи користувачів. Якщо вибирати мінімальний ступінь деталізації, запити на
вибірку та оновлення даних будуть виконуватися дуже швидко, але інші
користувачі при цьому повинні будуть чекати завершення транзакції. Ступінь
паралелізму можна збільшити шляхом підвищення рівня деталізації, проте
блокування - цілком конкретний ресурс SQL Server'а, для її створення,
підтримки і видалення потрібен час і пам'ять. p>
ПРИМІТКА p>
Блокування займає 96 байт. [1] Загальна
кількість блокувань може варіюватися від 5000 до 2 147 483 647.
Конкретне значення можна задати за допомогою збереженої процедури sp_configure з
параметром locks. p>
SQL Server може приймати рішення про зменшення
ступеня деталізації, коли кількість блокованих ресурсів збільшується.
Цей процес називається ескалацією блокувань. P>
Взагалі кажучи, існує два методи управління
конкуренцією для забезпечення паралельної роботи багатьох користувачів --
оптимістичний і песимістичний. SQL Server використовує оптимістичну
конкуренцію тільки при використанні курсорів (cursors). Для звичайних запитів
на вибірку та оновлення використовується песимістична конкуренція. Розглянемо
докладніше, що вони є: p>
Оптимістичний метод управління характеризується тим, що
замість безпосереднього читання даних береться значення з буфері. Ніяких
блокувань при цьому не накладається. Інші транзакції можуть спокійно читати
або навіть змінювати дані. У момент фіксування транзакції система порівнює
попереднє (заздалегідь збережене) значення даних з поточним. Якщо вони співпадають,
виконуються операції блокування, оновлення та розблокування даних. Якщо ж
значення відрізняються, то система генерує помилку і відкочується транзакцію. Хоча
такий підхід не відповідає вимогам стандарту, він дозволяє в
певних випадках досягти кращої продуктивності, ніж песимістичний
підхід. Переваги цього режиму очевидні: система не втрачає часу на
установку блокувань і ресурсів для їх створення. Однак для систем з великою кількістю
користувачів, часто змінюють дані, такий режим використовувати не
рекомендується, тому що ціна відкату транзакції і її повторного виконання
значно вище установки блокування при читанні даних. p>
Песимістичний метод. У цьому випадку сервер завжди
блокує ресурси відповідно з поточним рівнем ізоляції. У прикладі
попереднього розділу використовувався саме цей метод управління конкуренцією,
однак зовсім не складно адаптувати його для підтримки оптимістичного режиму.
p>
Блокування - надзвичайно важливий і невід'ємний
механізм функціонування сервера. Вони застосовуються для кожного запиту на
читання або оновлення даних, а також у багатьох інших випадках (наприклад, при
створення нової сесії). Роботою з блокуваннями займається спеціальний модуль SQL
Server'а - менеджер блокувань (Lock Manager). У його завдання входить: p>
створення та встановлення блокувань; p>
зняття блокувань; p>
ескалація блокувань; p>
визначення сумісності блокувань; p>
усунення взаімоблокіровок (deadlocks) і багато
інше. p>
Коли користувач робить запит на оновлення або
читання даних, менеджер транзакцій передає управління менеджеру блокування для
того, щоб з'ясувати чи були блоковані запитувані ресурси, і, якщо так,
чи сумісна запитувана блокування з поточною. Якщо блокування несумісні,
виконання поточної транзакції відкладається до тих пір, поки дані не будуть
розблоковано. Як тільки дані стають доступні, менеджер блокувань
накладає запитувану блокування, і повертає керування менеджеру
транзакцій. p>
Момент зняття блокування сильно залежить від поточного
рівня ізоляції і типу запиту. Наприклад, при виконанні запиту на вибірку
даних при рівні ізоляції нижче REPEATABLE READ менеджер блокувань знімає
блокування відразу ж після отримання всіх даних з поточної сторінки. При цьому
відбувається встановлення блокування на наступну сторінку. p>
Прості блокування
p>
SQL Server підтримує три основні типи блокувань: p>
Колективна блокування (Shared Lock), позначається
латинською буквою S. Ця найпоширеніший тип блокування, який
використовується під час виконання операції читання даних. Гарантується що дані,
на які вона накладена, не будуть змінені інший транзакцією. Однак читання
даних можливе. p>
Монопольна блокування (Exclusive Lock), позначається латинською
буквою X. Цей тип застосовується при зміні даних. Якщо на ресурс встановлена
монопольна блокування, гарантується, що інші транзакції не можуть не тільки
змінювати дані, але навіть читати їх. p>
Блокування оновлення (Update Lock), позначається
латинською буквою U. Це блокування є проміжною між розділяється і
монопольної блокуванням. Так до?? до монопольна блокування не сумісна ні з
одним видом інших блокувань (є один виняток, про який пізніше), її
установка призводить до повного блокування ресурсу. Якщо транзакція хоче
оновити дані в якийсь найближчий момент часу, але не зараз, і, коли
цей момент прийде, не хоче чекати іншої транзакції, вона може запросити
блокування оновлення. У цьому випадку іншим транзакцій дозволяється
встановлювати колективні блокування, але не дозволяє встановлювати
монопольні. Іншими словами, якщо ця транзакція встановила на ресурс
блокування оновлення, ніяка інша транзакція не зможе отримати на цей же
ресурс монопольну блокування або блокування оновлення до тих пір, поки
встановила блокування транзакція не буде завершена. p>
Перш ніж іти далі, давайте розглянемо невеликий
приклад. Для перегляду поточних блокувань існує системна збережена функція
sp_lock. Вона повертає інформацію про блокування у форматі, описаному в таблиці
2. P>
Назва колонки p>
Опис p>
spid p>
Ідентифікатор процесу SQL
Server. P>
dbid p>
Ідентифікатор бази даних. p>
ObjId p>
Ідентифікатор об'єкта, на
який встановлена блокування. p>
IndId p>
Ідентифікатор індексу. p>
Type p>
Тип об'єкта. Може
приймати значення: DB, EXT, TAB, PAG, RID, KEY. p>
Resource p>
Вміст колонки
syslocksinfo.restext. Зазвичай це ідентифікатор рядка (для типу RID) або
ідентифікатор сторінки (для типу PAG). p>
Mode p>
Тип блокування. Може приймати значення: 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. Про ці значеннях буде сказано нижче. P>
Status p>
Статус процесу SQL Server.
Може приймати значення: GRANT, WAIT, CNVRT. P>
Ця процедура повертає дані про блокування з
системної таблиці syslockinfo, що знаходиться в базі даних master. p>
ПРИМІТКА p>
Інформація саме з цієї таблиці
використовується менеджером блокувань для визначення сумісності блокувань
при запиті ресурсів транзакціями. p>
У всіх прикладах використовується таблиця test, яка
створюється наступним скриптом: p>
create
table test (i int, n varchar (20)) p>
insert
into test values (1, 'alex') p>
insert
into test values (2, 'rosa') p>
insert
into test values (3, 'dima') p>
По-перше, давайте дійсно переконаємося, що при
читанні даних з рівнем ізоляції нижче REPEATABLE READ колективні блокування
знімаються відразу ж після отримання даних: p>
print
@ @ spid p>
begin
tran select * from test p>
Ми почали транзакцію, але залишили її відкритою. Для
того, щоб подивитися, які блокування накладені попереднім скриптом, викличемо
процедуру sp_lock (в іншій сесії) з параметром, виведеним print @ @ spid (у
мене це 54). p>
РАДА p>
Поточне значення ідентифікатора процесу
сервера можна побачити в рядку стану програми Query Analizer. p>
sp_lock 54 p>
Результат наведено в таблиці 3. p>
spdi p>
dbid p>
O