Перейти к основному содержимому

Блокировка строк в обертках сторонних данных

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

Одно из ключевых решений, которое необходимо принять — будет ли реализована ранняя или поздняя блокировка. При ранней блокировке строка блокируется, когда она впервые извлекается из базового хранилища, а при поздней блокировке строка блокируется только тогда, когда известно, что она должна быть заблокирована. (Разница возникает потому, что некоторые строки могут быть отброшены локально проверяемыми условиями ограничения или соединения). Ранняя блокировка намного проще и позволяет избежать дополнительных обращений к удаленному хранилищу, но она может вызвать блокировку строк, которые не должны были быть заблокированы, что приводит к снижению параллелизма или даже неожиданным тупикам. Кроме того, поздняя блокировка возможна только в том случае, если блокируемый ряд может быть однозначно идентифицирован позже. Предпочтительно, чтобы идентификатор строки идентифицировал конкретную версию строки, как это делают TID в PostgreSQL.

По умолчанию PostgreSQL игнорирует вопросы блокировки при взаимодействии с FDW, но FDW может выполнять раннюю блокировку без явной поддержки со стороны основного кода. Функции API, описанные в разделе «Подпрограммы FDW для блокировки строк», которые были добавлены в PostgreSQL 9.5, позволяют FDW использовать позднюю блокировку, если они того пожелают.

Дополнительное соображение заключается в том, что в режиме изоляции READ COMMITTED PostgreSQL может потребоваться перепроверить условия ограничения и присоединения к обновленной версии некоторого целевого кортежа. Перепроверка

Условия присоединения требуют повторного получения копий нецелевых строк, которые ранее были присоединены к целевому кортежу. При работе со стандартными таблицами PostgreSQL это делается путем включения TID нецелевых таблиц в список столбцов, проецируемый через соединение, а затем повторной выборки нецелевых строк, когда это необходимо. Такой подход позволяет сохранить компактность набора данных соединения, но требует недорогой возможности повторной выборки, а также TID, который может однозначно идентифицировать версию строки, подлежащую повторной выборке. Поэтому по умолчанию при работе с сторонними таблицами используется подход, при котором копия всей строки, извлеченной из сторонней таблицы, включается в список столбцов, проецируемый через объединение. Это не предъявляет особых требований к FDW, но может привести к снижению производительности объединений слияния и хэш-соединений. FDW, способный удовлетворить требования к повторной выборке, может выбрать первый способ.

Для UPDATE или DELETE сторонней таблицы рекомендуется, чтобы опера ForeignScan на целевой таблице выполняла раннюю блокировку строк, которые она получает, возможно, через эквивалент SELECT FOR UPDATE. FDW может определить, является ли таблица целью UPDATE/DELETE, во время планирования, сравнив ее relid с root->parse->resultRelation, или во время выполнения, используя ExecRelationIsTargetRelation(). Альтернативной возможностью является выполнение поздней блокировки внутри обратного вызова ExecForeignUpdate или ExecForeignDelete, но специальной поддержки для этого не предусмотрено.

Для сторонних таблиц, которые определены для блокировки командой SELECT FOR UPDATE/SHARE, операция ForeignScan может снова выполнить раннюю блокировку, получая кортежи с помощью эквивалента SELECT FOR UPDATE/SHARE. Чтобы вместо этого выполнить позднюю блокировку, предоставьте функции обратного вызова, описанные в разделе «Подпрограммы FDW для блокировки строк». В GetForeignRowMarkType выберите вариант маркировки строки ROW_MARK_EXCLUSIVE, ROW_MARK_NOKEYEXCLUSIVE, ROW_MARK_SHARE или ROW_MARK_KEYSHARE в зависимости от запрашиваемой силы блокировки. (Код ядра будет действовать одинаково независимо от того, какой из этих четырех вариантов будет выбран). В других случаях можно определить, была ли сторонняя таблица заблокирована командой такого типа, используя get_plan_rowmark во время планирования или ExecFindRowMark во время выполнения; необходимо проверить не только то, что возвращается не нулевая структура rowmark, но и то, что ее поле strength не является LCS_NONE.

Наконец, для сторонних таблиц, которые используются в команде UPDATE, DELETE или SELECT FOR UPDATE/SHARE, но не указаны для блокировки строк, необходимо отменить выбор по умолчанию для копирования строк entire, заставив GetForeignRowMarkType выбрать опцию ROW_MARK_REFERENCE, когда он видит силу блокировки LCS_NONE. Это приведет к вызову RefetchForeignRow с этим значением для markType; затем он должен повторно получить строку, не приобретая новой блокировки. (Если у вас есть функция GetForeignRowMarkType, но нельзя повторно получать незаблокированные строки, выберите опцию ROW_MARK_COPY для LCS_NONE).

Дополнительную информацию см. в файле src/include/nodes/lockoptions.h, комментарии к RowMarkType и PlanRowMark в файле src/include/nodes/plannodes.h, а также комментарии к ExecRowMark в файле src/include/nodes/execnodes.h.