Декомпозиция

Декомпозиция — это разделение большого запроса на несколько запросов поменьше.

Возьмем для примера такой запрос:

SELECT
    *
FROM (
    SELECT *
    FROM dm_ap2.dct_available_resources
    WHERE cur = True
    AND is_appointment_to_self = True
) ar
JOIN (
    SELECT
        toString(procedure_id) as procedure_id,
        available_resource_id,
        cur
    FROM
        dm_ap2.crs_available_resource_to_procedure
    LIMIT 5000
) arp
ON arp.available_resource_id = ar.available_resource_id

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

Но нам не нужно чтение всей левой таблицы, нам достаточно прочитать только те available_resource_id, которые есть в правой таблице. Какими способами можно достичь этого?

Временные таблицы

Можно использовать временные таблицы. Тогда наш запрос будет выглядить так:

-- временная таблица 1, содержащая правую часть запроса
CREATE temporary table t1 as
SELECT
    toString(procedure_id) as procedure_id,
    available_resource_id,
    cur
FROM
    dm_ap2.crs_available_resource_to_procedure
LIMIT 5000;

-- временная таблица 2, содержащая левую часть запроса
CREATE temporary table t2 as
SELECT *
FROM 
    dm_ap2.dct_available_resources
WHERE available_resource_id in (
    SELECT available_resource_id from t1 -- фильтруем id
    )
    AND cur = True
    AND is_appointment_to_self = True;

SELECT *
FROM t1
JOIN t2
ON t1.available_resource_id = t2.available_resource_id

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

Временные таблицы — фича ClickHouse, позволяющая создавать таблицы, которые живут в оперативной памяти компьютера. Они существуют только в контексте и перестают существовать после завершения запроса.

CTE

Второй способ — использование CTE (Common Table Expressions) для фильтрации левой таблицы перед джойном:

WITH arp AS (
    SELECT
        toString(procedure_id) AS procedure_id,
        available_resource_id,
        cur
    FROM dm_ap2.crs_available_resource_to_procedure
    LIMIT 5000
),
ar AS (
    SELECT *
    FROM dm_ap2.dct_available_resources
    WHERE available_resource_id IN (
        SELECT available_resource_id FROM arp # фильтруем здесь
        )
    AND cur = True
    AND is_appointment_to_self = True
)
SELECT *
FROM ar
JOIN arp
ON arp.available_resource_id = ar.available_resource_id;

Такой подход имеет смысл, если таблица dct_available_resources очень большая, и фильтрация по available_resource_id значительно уменьшает объём данных. Если таблица не очень большая, лучше положиться на внутреннюю оптимизацию JOIN в ClickHouse.

Выводы

  • Если в запросе несколько подзапросов задумайтесь о декомпозиции. Особенно если в запросе несколько операторов FROM.

  • Если вы понимаете, что можно сократить количество строк, читаемых с диска — сделайте это.

  • Применяйте декомпозицию осторожно — не забывайте о читабельности кода и о логике запроса — она не должна нарушаться.

Last updated