Блокировки транзакций

 

Практически во всех ведущих СУБД для обработки параллельных транзакций применяется довольно сложный механизм блокировки. Однако принципы блокировки транзакций довольно просты. На, нижеследующем рисунке изображена схема простой блокировки, устраняющей конфликты между двумя параллельными транзакциями.

Когда транзакция А обращается к базе данных, СУБД автоматически блокирует все части базы данных, в которых транзакция осуществляет выборку или изменение. Транзакция B выполняется параллельно, и СУБД также блокирует те части базы данных, к которым она обращается. Если транзакция B обращается к той части базы данных, которая заблокирована транзакцией А, то СУБД приостанавливает выполнение транзакции B, заставляя ее ждать до тех пор, пока данные не будут разблокированы. СУБД снимает блокировку, вызванную транзакцией А, только после того, как в этой транзакции встретится инструкция COMMIT или ROLLBACK. Затем СУБД позволяет продолжить выполнение транзакции B. Теперь транзакция B блокирует эту же часть базы данных, защищая ее от других транзакций.

 

Рисунок Блокировка при одновременном выполнении

двух транзакций

 

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

Уровни блокировки.В базе данных блокировка может быть реализована на различных уровнях. Самой грубой формой блокировки является блокировка всей базы данных. Этот вид блокировки легко реализовать, но при этом в каждый момент времени можно будет выполнять только одну транзакцию. Если транзакция затрачивает некоторое время “на размышления” (например, время обсуждения заказа с клиентом), то доступ всех остальных пользователей к базе данных будет при этом заблокирован, что приводит к слишком низкой производительности.

Другой формой блокировки является блокировка на уровне таблиц. В этом случае СУБД блокирует только те таблицы, к которым обращается транзакция. Остальные транзакции в это время могут обращаться к другим таблицам. Этот вид блокировки предпочтительнее, чем блокировка базы данных, поскольку он позволяет проводить параллельную обработку транзакций. Но в таких приложениях, как программы для ввода заказов, в которых несколько пользователей одновременно обращаются к одним и тем же таблицам, данный вид блокировки также приводит к слишком низкой производительности.

Во многих СУБД реализована блокировка на уровне страниц. В этом случае СУБД блокирует отдельные блоки данных (“страницы”) на диске. Остальным транзакциям запрещается доступ к заблокированным страницам, но они могут обращаться к другим страницам данных (и блокировать их для себя). Обычно используются страницы размером 2, 4 и 16 Кб. Поскольку большая таблица состоит из множества страниц, две транзакции, обращающиеся к двум различным строкам таблицы, как правило, обращаются к различным страницам; в результате обе транзакции выполняются параллельно.

В большинстве ведущих СУБД реализована блокировка на уровне строк. Она допускает параллельное выполнение транзакций, которые обращаются к двум различным строкам таблицы, даже если эти строки находятся на одной странице. Хотя такая возможность кажется несущественной, в случае таблиц, содержащих небольшое число строк, она может играть важную роль.

Блокировка на уровне строк обеспечивает параллельное выполнение большого количества транзакций. К сожалению, осуществить блокировку строк различной длины гораздо сложнее, чем блокировку страниц фиксированного размера, поэтому повышение параллелизма работы обеспечивается за счет усложнения логики блокирования и роста накладных расходов.

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

Жесткая и нежесткая блокировки. В большинстве коммерческих СУБД для повышения степени параллельности доступа нескольких пользователей к одной базе данных используются следующие типы блокировки:

- нежесткая блокировка, когда текущая транзакция извлекает информацию из базы данных и другие транзакции, выполняемые параллельно, могут извлекать те же данные.

- жесткая блокировка, когда текущая транзакция обновляет данные в базе, а другие транзакции не могут обращаться к ним ни для выборки, ни для записи.

На рисунке изображены допустимые комбинации блокировок для двух параллельно выполняемых транзакций. Следует отметить, что транзакция может применять для данных жесткую блокировку только в том случае, если ни одна другая транзакция не блокирует эти данные. Если транзакция пытается осуществить блокировку, не разрешенную правилами, ее выполнение приостанавливается до тех пор, пока другие транзакции не разблокируют необходимые ей данные.

 

Рисунок Правила применения жесткой и нежесткой блокировок

Тупиковые ситуации. К сожалению, применение любого механизма блокировки для поддержки параллельного выполнения транзакций приводит к возникновению проблемы, которая называется тупиковой ситуацией. На следующем рисунке изображена типичная тупиковая ситуация. Программа А обновляет таблицу ORDERS и поэтому блокирует часть этой таблицы. Тем временем программа B обновляет таблицу PRODUCTS и блокирует ее часть. Затем программа А пытается обновить таблицу PRODUCTS, а программа B пытается обновить таблицу ORDERS, причем каждая пытается обновить ту часть таблицы, которая заблокирована другой программой.

 

Рисунок Тупиковая ситуация

 

Без внешнего вмешательства обе программы будут бесконечно ожидать, пока другая программа завершит транзакцию и разблокирует данные. Но могут возникнуть и более сложные ситуации, когда три, четыре или даже больше программ попадают в “цикл” блокировок и каждая из них ожидает освобождения данных, заблокированных другой программой.

Как правило, для устранения тупиковых ситуаций СУБД периодически (например, каждые пять секунд) проверяет блокировки, удерживаемые различными транзакциями. Если СУБД обнаруживает тупик, то произвольно выбирает одну транзакцию в качестве “проигравшей” и отменяет ее. Это освобождает данные, удерживаемые проигравшей транзакцией, позволяя “победителю” продолжить работу. Проигравшей программе посылается код ошибки, показывающий, что она проиграла в тупиковой ситуации и её текущая транзакция была отменена.

Такая схема устранения тупиков означает, что любая инструкция SQL может вернуть код ошибки “проигрыша в тупиковой ситуации”, хотя сама по себе инструкция будет правильной. Транзакция, пытающаяся выполнить эту инструкцию, отменяется не по вине последней, а из-за параллельной работы с базой данных многих пользователей. Такая схема может показаться несправедливой, но на практике она лучше двух возможных альтернатив: “зависания” или нарушения целостности данных. Если ошибка “проигрыша в тупиковой ситуации” происходит в интерактивном режиме, пользователь может просто повторно ввести инструкции (одну или несколько). В программном SQL прикладная программа сама должна обрабатывать код подобной ошибки и либо выдавать предупреждающее сообщение пользователю, либо пытаться выполнить транзакцию повторно.

Вероятность возникновения тупиков можно резко уменьшить, если тщательно планировать обновления базы данных. Все программы, которые в своих транзакциях обновляют несколько таблиц, по возможности должны всегда обновлять таблицы в одном и том же порядке. Это приводит к плавному перемещению блокировок по таблицам и сводит к минимуму возможность возникновения тупиков. Кроме того, для дальнейшего уменьшения количества возникающих тупиков можно использовать средства явной блокировки.

Явная блокировка.Если программа обращается к таблице многократно, затраты, связанные с блокировкой большого количества маленьких участков таблицы, могут оказаться значительными. Например, программа пакетного обновления, которая обращается к каждой строке таблицы, в процессе работы будет блокировать таблицу по частям. Для более быстрого выполнения транзакции такого типа программе следует явно заблокировать всю таблицу, выполнить все необходимые обновления, а затем разблокировать ее. Блокировка всей таблицы имеет три преимущества:

- устраняет затраты, связанные с блокировкой строк (страниц);

- исключает возможность того, что другая транзакция заблокирует часть таблицы, вынуждая программу пакетного обновления находиться в состоянии ожидания;

- исключает возможность того, что другая транзакция заблокирует часть таблицы и “запрёт в тупик” программу пакетного обновления, вынуждая ее начать выполнение повторно.

Недостаток явной блокировки таблицы в том, что все остальные транзакции, которые пытаются обратиться к таблице, должны ждать окончания её обновления. Но поскольку транзакция, выполняющие пакетное обновление, обычно работает гораздо быстрее остальных, общая производительность СУБД при явной блокировке таблиц может даже увеличиться.

Для явной блокировки целой таблицы применяется инструкция LOCK TABLE, синтаксическая диаграмма которой изображена на рисунке.

Рисунок Синтаксическая диаграмма инструкции LOCK TABLE

 

С помощью этой инструкции можно установить один из двух режимов блокировки:

- В режиме EXCLUSIVE осуществляется жесткая блокировка всей таблицы. В это время ни одна из других транзакций не имеет доступа ни к одной части таблицы. Этот режим следует использовать, если транзакция выполняет пакетное обновление всех строк таблицы.

- В режиме SHARE осуществляется нежесткая блокировка таблицы. Другие транзакции могут извлекать данные из таблицы, но не могут обновлять ее. Для того чтобы обновить часть таблицы, транзакция, выполнившая инструкцию LOCK TABLE, должна жестко заблокировать требуемую часть таблицы. Этот режим блокировки следует использовать также в том случае, если требуется получить “мгновенный снимок” таблицы в какой-то определенный момент времени.