61.4. Рассмотрение блокировки индекса#
61.4. Рассмотрение блокировки индекса #
Методы доступа к индексам должны обрабатывать одновременные обновления индекса несколькими процессами.
Основная система Tantor BE получает блокировку AccessShareLock
на индексе во время сканирования индекса и блокировку RowExclusiveLock
при обновлении индекса (включая обычную команду VACUUM
). Поскольку эти типы блокировок не конфликтуют, метод доступа отвечает за обработку любых мелкозернистых блокировок, которые ему могут понадобиться.
Блокировка ACCESS EXCLUSIVE
на весь индекс будет установлена только во время создания, удаления индекса или команды REINDEX
(вместо этого будет установлена блокировка SHARE UPDATE EXCLUSIVE
с опцией CONCURRENTLY
).
Создание типа индекса, поддерживающего параллельные обновления, обычно требует тщательного и сложного анализа необходимого поведения. Для типов индексов b-tree и hash вы можете прочитать о принятых решениях в дизайне в файлах src/backend/access/nbtree/README
и src/backend/access/hash/README
.
Помимо собственных требований к внутренней согласованности индекса, одновременные обновления создают проблемы согласованности между родительской таблицей (кучей) и индексом. Поскольку Tantor BEразделяет доступы и обновления кучи от доступов и обновлений индекса, существуют окна, в которых индекс может быть несогласованным с кучей. Мы решаем эту проблему с помощью следующих правил:
Перед созданием индексных записей создается новая запись в куче. (Поэтому вероятно, что параллельное сканирование индекса не сможет увидеть запись в куче. Это нормально, потому что процесс чтения индекса будет пропускать неподтвержденные строки. Но см. Раздел 61.5).
Когда запись кучи должна быть удалена (с помощью
VACUUM
), сначала необходимо удалить все ее индексные записи.Индексное сканирование должно поддерживать фиксацию на странице индекса, на которой находится элемент, возвращенный последней функцией
amgettuple
, иambulkdelete
не может удалять записи с страниц, которые заблокированы другими процессами. Необходимость этого правила объясняется ниже.
Без третьего правила возможно, что читатель индекса увидит запись индекса, прямо перед тем, как она будет удалена командой VACUUM
, и затем обратится к соответствующей записи кучи после того, как она была удалена командой VACUUM
.
Это не создает серьезных проблем, если номер этого элемента все еще не используется, когда читатель достигает его, поскольку пустой слот элемента будет проигнорирован функцией heap_fetch()
. Но что, если третий фоновый процесс уже повторно использовал слот элемента для чего-то другого?
При использовании снимка, совместимого с MVCC, проблем нет, потому что новый обладатель слота обязательно будет слишком новым, чтобы пройти проверку снимка. Однако, при использовании снимка, несовместимого с MVCC (например, SnapshotAny
), возможно принятие и возврат строки, которая на самом деле не соответствует ключам сканирования. Мы могли бы защититься от этого сценария, требуя повторной проверки ключей сканирования по строке кучи во всех случаях, но это слишком дорого. Вместо этого мы используем фиксацию на странице индекса в качестве прокси, чтобы указать, что читатель может все еще находиться в процессе перехода от записи индекса к соответствующей записи кучи. Блокировка ambulkdelete
на такой фиксации гарантирует, что VACUUM
не сможет удалить запись кучи, пока читатель не закончит работу с ней. Это решение практически не влияет на время выполнения и добавляет блокировочные издержки только в редких случаях, когда действительно возникает конфликт.
Это решение требует, чтобы сканирование индекса было “синхронным”: мы должны извлекать каждый кортеж кучи непосредственно после сканирования соответствующей записи индекса. Это дорого по нескольким причинам. Асинхронное сканирование, при котором мы собираем много TID из индекса и посещаем кортежи кучи только в какой-то момент позже, требует гораздо меньше издержек на блокировку индекса и может позволить более эффективный доступ к куче. Согласно вышеуказанному анализу, мы должны использовать синхронный подход для снимков, несовместимых с MVCC, но асинхронное сканирование подходит для запроса с использованием снимка MVCC.
В индексном сканировании amgetbitmap
метод доступа не сохраняет закрепление индекса на любой из возвращенных кортежей. Поэтому использование таких сканирований безопасно только с снимками, совместимыми с MVCC.
Когда флаг ampredlocks
не установлен, любое сканирование, использующее этот метод доступа к индексу в пределах сериализуемой транзакции, будет получать неблокирующую предикатную блокировку на весь индекс. Это приведет к конфликту чтения-записи с вставкой любой кортежи в этот индекс с помощью параллельной сериализуемой транзакции. Если обнаружены определенные шаблоны конфликтов чтения-записи среди набора параллельных сериализуемых транзакций, одна из этих транзакций может быть отменена для защиты целостности данных. Когда флаг установлен, это указывает, что метод доступа к индексу реализует более тонкую предикатную блокировку, что, как правило, снижает частоту таких отмен транзакций.