Для чего СУБД использует намеренные блокировки при одновременной работе с блокировками строк и таблиц?
Намеренная блокировка на уровне таблицы сообщает, что транзакция собирается блокировать объекты ниже по иерархии, например отдельные строки. Благодаря этому СУБД может проверять совместимость блокировок на уровне таблицы, не просматривая все блокировки строк, и не допускать конфликтующую блокировку всей таблицы.
Блокировки можно накладывать на объекты разного масштаба: базу, таблицу, страницу или строку. Крупная блокировка дешевле для менеджера блокировок, но сильнее ограничивает параллелизм; мелкая повышает параллелизм, но увеличивает число служебных записей и стоимость проверки конфликтов.
Иерархическая схема блокировок появилась как способ совместить эти свойства. Она позволяет одной транзакции работать с отдельными строками, пока другая использует другие строки, и одновременно корректно согласовывать это с запросом, которому нужна блокировка всей таблицы.
Предположим, транзакция изменила одну строку и удерживает на ней исключительную блокировку. Другая транзакция хочет получить разделяемую блокировку всей таблицы, например для операции, которая должна быть совместима только с отсутствием изменений в таблице.
Если проверять только блокировку таблицы, конфликт можно пропустить: на самой таблице блокировки нет, хотя одна из строк уже изменяется. Если каждый раз просматривать все блокировки строк, проверка станет дорогой, особенно при большом количестве параллельных операций.
Перед блокировкой строки транзакция получает на таблицу намеренную блокировку. Для изменения строки это обычно намеренная исключительная блокировка IX, для чтения строки — намеренная разделяемая блокировка IS. Затем на самой строке устанавливается обычная блокировка нужного типа.
Намеренная блокировка не защищает данные строки сама по себе. Она обозначает наличие или намерение получить блокировки на дочерних объектах. Поэтому запрос, которому нужна разделяемая блокировка всей таблицы, видит на таблице IX и понимает, что такая блокировка несовместима с текущей работой на строках.
Например, схема выглядит так: транзакция A удерживает IX на таблице и X на строке 10. Транзакция B может запросить X на строке 20, если остальные условия совместимости это позволяют, но запрос B на S всей таблицы будет заблокирован. Иначе таблица одновременно считалась бы разделяемо заблокированной и содержащей изменяемую строку.
Типичная иерархия совместимости устроена так, что IS и IX на одном уровне могут сосуществовать: разные транзакции могут читать и изменять разные строки. Однако IX несовместима с полной S- или X-блокировкой таблицы, а X на таблице несовместима практически со всеми конкурентными блокировками. Конкретные названия режимов и детали матрицы зависят от СУБД.
Иерархические блокировки требуют согласованного протокола: сначала блокируется родительский объект в намеренном режиме, затем дочерний. Без этого транзакция могла бы получить блокировку строки, пока другая уже получила несовместимую блокировку всей таблицы.
Компромисс состоит в накладных расходах на поддержание большого числа блокировок. СУБД может применять укрупнение блокировок, заменяя множество блокировок строк блокировкой страницы или таблицы. Это уменьшает служебные расходы, но снижает конкурентность и может внезапно увеличить область блокирования.
Намеренные блокировки не устраняют взаимные блокировки, долгие ожидания или необходимость отката. Они лишь делают корректной и эффективной координацию блокировок разных гранулярностей.
В системе заказов длительная пакетная операция изменяет отдельные заказы, а параллельно отчётный запрос пытается получить согласованный доступ ко всей таблице. У каждой пакетной транзакции есть IX на таблице и X-блокировки только на затронутых строках. Поэтому другой пакет, работающий с непересекающимися строками, может продолжать выполнение, но операция, требующая полной S-блокировки таблицы, ожидает завершения изменений.
Вариант без намеренных блокировок потребовал бы проверять блокировки всех строк перед выдачей полной блокировки. Это сохраняет корректность только при тщательной реализации, но плохо масштабируется. Вариант с немедленной блокировкой всей таблицы проще, однако блокирует даже независимые заказы и резко снижает пропускную способность.
Третий вариант — использовать версионное чтение, если СУБД и требования отчёта это допускают. Оно может уменьшить ожидания читателей, но не всегда обеспечивает нужную семантику блокирующего чтения и не отменяет требования к согласованности снимка.
Практически выбирают иерархические блокировки с мелкой гранулярностью для коротких OLTP-операций, контролируют длительность транзакций и отдельно проверяют сценарии укрупнения блокировок. В результате независимые изменения выполняются параллельно, а операции, которым действительно нужен эксклюзивный доступ к таблице, корректно ждут.
Вопрос: Может ли намеренная блокировка строки сама по себе запретить другой транзакции изменить всю таблицу?
Ответ: Нет. Намеренная блокировка обычно устанавливается на родительском уровне, например на таблице, а блокировка данных — на строке. Именно конфликт режима IX или IS с запрошенной полной блокировкой таблицы позволяет менеджеру блокировок отклонить или отложить операцию. Если говорить только о блокировке строки, механизм иерархической проверки описан неполно.
Вопрос: Почему IX одной транзакции обычно совместима с IX другой транзакции?
Ответ: IX означает намерение изменять дочерние объекты, а не запретить любые изменения в таблице. Транзакции могут изменять разные строки, поэтому их намерения на уровне таблицы совместимы. Конфликт затем проверяется на конкретных строках: если обе выбрали одну строку, их X-блокировки несовместимы и одна транзакция будет ждать или завершится с ошибкой конфликта.
Вопрос: Что произойдёт с конкурентностью, если СУБД укрупнит множество блокировок строк до блокировки таблицы?
Ответ: Служебных затрат станет меньше: менеджеру блокировок нужно отслеживать один крупный объект вместо множества мелких. Но все операции, конфликтующие с таблицей, начнут ждать, включая те, которые работают с логически независимыми строками. Поэтому укрупнение полезно для снижения накладных расходов, но может привести к скачку задержек и падению параллелизма; его причины обычно ищут в размере транзакций, количестве затронутых строк и настройках или политике конкретной СУБД.