Найти в Дзене
Postgres DBA

Влияние RLS на стоимость плана запроса

Как использование RLS влияет на стоимость плана выполнения SQL запроса для СУБД PostgreSQL при использовании в условии FROM соединений JOIN ? При использовании Row-Level Security (RLS) в запросах PostgreSQL с операторами JOIN влияние на стоимость плана выполнения становится более сложным и может проявляться в нескольких аспектах: RLS-политики применяются индивидуально к каждой таблице в запросе: SELECT * FROM orders JOIN customers ON orders.customer_id = customers.id; Если для обеих таблиц включены политики RLS, запрос неявно преобразуется в: SELECT * FROM orders JOIN customers ON orders.customer_id = customers.id WHERE orders.tenant_id = current_tenant() -- RLS для orders AND customers.active = true; -- RLS для customers Последствия: Оптимизатор может изменить последовательность JOIN из-за RLS: RLS может изменить выбор между Nested Loop, Hash Join и Merge Join: Пример: После применения RLS к orders осталось 5 строк → оптимизатор выбирает Nested Loop вместо Hash Join. Глав
Оглавление

Вопрос

Как использование RLS влияет на стоимость плана выполнения SQL запроса для СУБД PostgreSQL при использовании в условии FROM соединений JOIN ?

Ответ DeepSeek

При использовании Row-Level Security (RLS) в запросах PostgreSQL с операторами JOIN влияние на стоимость плана выполнения становится более сложным и может проявляться в нескольких аспектах:

1. Добавление предикатов к каждой таблице в JOIN

RLS-политики применяются индивидуально к каждой таблице в запросе:

SELECT *
FROM orders
JOIN customers ON orders.customer_id = customers.id;

Если для обеих таблиц включены политики RLS, запрос неявно преобразуется в:

SELECT *
FROM orders
JOIN customers ON orders.customer_id = customers.id
WHERE orders.tenant_id = current_tenant() -- RLS для orders
AND customers.active = true; -- RLS для customers

Последствия:

  • Условия добавляются до выполнения соединения, что может изменить порядок и метод JOIN
  • Резкое увеличение стоимости, если предикаты:
    Содержат функции (например,
    current_tenant())
    Используют неиндексируемые колонки
    Имеют низкую селективность

2. Изменение порядка соединения (Join Order)

Оптимизатор может изменить последовательность JOIN из-за RLS:

  • Пример: Без RLS план начинался с большой таблицы orders, но после применения RLS осталось 10 строк → выгоднее начать с orders, а затем JOIN с customers.
  • Риск: Если статистика не учитывает RLS-фильтры, оптимизатор может выбрать неоптимальный порядок.

3. Влияние на методы соединения (Join Type)

RLS может изменить выбор между Nested Loop, Hash Join и Merge Join:

  • Nested Loop Усиливается, если RLS сокращает размер внешней таблицы (особенно с индексами)
  • Hash Join Может замедлиться, если RLS-предикаты мешают эффективному построению хеша
  • Merge Join Редко используется, если RLS-условия нарушают порядок сортировки ключей

Пример: После применения RLS к orders осталось 5 строк → оптимизатор выбирает Nested Loop вместо Hash Join.

4. Проблемы с оценкой кардинальности

Главная проблема: Статистика PostgreSQL не учитывает RLS-фильтры.

  • До RLS: Таблица orders — 1M строк
  • После RLS: Фактически 100 строк (для текущего tenant_id)
  • План запроса: Оптимизатор ожидает 1M строк → выбирает Hash Join, хотя реально нужен Nested Loop.

Результат: Завышенные/заниженные оценки → неоптимальный план.

5. Блокировка оптимизаций

  • Параллельное выполнение: RLS-предикаты с функциями (например, current_user) могут запрещать параллельные операции (Gather в плане).
  • Инвалидация кеша планов: Планы для JOIN не кешируются, если RLS зависит от контекста (сессии, роли).

6. Каскадное влияние на подзапросы

RLS применяется ко всем производным таблицам:

SELECT *
FROM (SELECT * FROM orders) AS o -- Здесь тоже сработает RLS!
JOIN customers ON o.customer_id = customers.id;

Усложняет оптимизацию вложенных запросов.

Как минимизировать влияние на JOIN?

1.Составные индексы для колонок RLS + JOIN-ключей:

CREATE INDEX idx_orders_tenant_customer ON orders(tenant_id, customer_id);
CREATE INDEX idx_customers_active_id ON customers(active, id);

2. Расширенная статистика для корректной оценки:

CREATE STATISTICS tenant_orders_stats (dependencies)
ON tenant_id, customer_id FROM orders;

3. Анализ планов с помощью EXPLAIN:

EXPLAIN ANALYZE
SELECT * FROM orders JOIN customers ON ...;

4.Упрощение политик:
Избегать функций в RLS-условиях для JOIN-таблиц
Использовать
AND/OR вместо подзапросов

5.Контроль порядка JOIN через JOIN LATERAL:

SELECT *
FROM customers
JOIN LATERAL (
SELECT *
FROM orders
WHERE customer_id = customers.id
AND tenant_id = current_tenant() -- RLS внутри
) AS o ON true;

Итоговое влияние

  1. Простые RLS + индексы Нейтральное/улучшение (лучшая фильтрация)
  2. Сложные RLS + нет индексов Резкий рост стоимости (до 10-100x)
  3. Много таблиц с RLS Риск экспоненциального роста сложности
  4. Точная статистика Снижает риск неоптимальных планов

Рекомендация:

Всегда тестировать планы выполнения с EXPLAIN ANALYZE для реальных JOIN-запросов после включения RLS.