MySQL: как эффективно повторно использовать результаты запроса в других запросах?

0

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

В среднем он возвращает около 2000 строк, но может быть от 0 (в этом случае я пропускаю остальные три) ко всем. Основная таблица имеет около 300 000 строк, она растет примерно на 800 в день, строки никогда не удаляются, а тысячи строк обновляются в течение дня, много раз.

Я просмотрел кеш запросов, но не похоже, что у него светлое будущее:
отключено по умолчанию, так как MySQL 5.6/MariaDB 10.1.7
амортизируется с MySQL 5.7.20
удалено в MySQL 8.0

Я рассматривал использование GROUP_CONCAT с IN, но почему-то я сомневаюсь, что это будет работать очень хорошо (если вообще) с большими запросами.

Это в библиотеке, которую я использую для форматирования результатов для других скриптов, поэтому исходный запрос может быть почти любым. Обычно он находится на индексированных столбцах, но может быть ужасно сложным с использованием хранимых функций и занимает несколько минут. Он всегда включает основную таблицу, но может также объединяться в другие таблицы (но только для фильтрации результатов из основной таблицы).

Я использую Perl 5.16 и MariaDB 10.1.32 (скоро обновится до 10.2) в CentOS 7. Я использую prepare_cached и заполнители. Пользователь этой библиотеки работает как имеет доступ только SELECT к таблицам плюс EXECUTE на пару сохраненных функций, но я могу изменить это при необходимости.

Я минимизировал это как можно больше и использовал метасинтатические переменные (внутри угловых скобок) как можно больше, чтобы сделать логику понятной. id составляет 16 байт и первичный ключ главной таблицы (помечен ниже). a

Я принимаю три параметра в качестве входных данных. <tables> всегда включает a и может включать в себя объединение как a join b on a.id=b.id <where> может быть простым, как e=3 или ужасно сложным. Я также получаю массив данных для заполнителей, но я оставил это из нижеследующего, потому что это не влияет на логику.

<search> = FROM <tables> WHERE (<where>)

<foo> = k < NOW() - INTERVAL 3 HOUR
<bar> = j IS NOT NULL OR <foo>
<baz> = j IS NULL AND k > NOW() - INTERVAL 3 HOUR
so <baz> is !<bar>.  Every row should match one or the other

<where> often includes 1 or more of foo/bar/baz

SELECT a.id, b, c, d, <foo> x <search> ORDER BY e, id

SELECT COUNT(*) <search> AND <baz>
I really only need to know if any of the above rows match <baz>

SELECT c, COUNT(*) t, SUM(<bar>) o FROM a WHERE c IN (SELECT c <search> GROUP BY c) GROUP BY c

SELECT d, COUNT(*) t, SUM(<bar>) o FROM a WHERE d IN (SELECT d <search> GROUP BY d) GROUP BY d

Последние два получают список всех уникальных c или d из строк исходного запроса, а затем подсчитывают, сколько полных строк (а не только те, что в исходном запросе) имеют соответствующие c или d и сколько из них соответствуют <bar>, Эти результаты сбрасываются в хэши, поэтому я могу найти эти подсчеты, пока я повторяю строки из исходного запроса. Я думаю, что запуск этих двух запросов один раз более эффективен, чем выполнение двух меньших запросов для каждой строки.

Спасибо.

Отредактировано для добавления решения:

Временная таблица была ответом, но не совсем так, как предложил Раймонд. Использование EXPLAIN в моих запросах указывает на то, что MariaDB уже использовал временную таблицу для каждого и удалял ее, когда все было завершено.

Внутреннее соединение возвращает только строки, которые существуют в обеих таблицах. Поэтому, создав временную таблицу идентификаторов, которая соответствует моему первому SELECT, а затем присоединив его к первичной таблице для других SELECT, я получаю только данные, которые я хочу, без необходимости копировать все эти данные во временную таблицу.

"Чтобы создать временную таблицу, вы должны иметь привилегию CREATE TEMPORARY TABLES. После того, как сеанс создал временную таблицу, сервер не выполняет никаких дополнительных проверок привилегий в таблице. Сеанс создания может выполнять любую операцию в таблице, такую как DROP TABLE, INSERT, UPDATE или SELECT. " - https://dev.mysql.com/doc/refman/5.7/en/create-temporary-table.html

Я также выяснил, что GROUP BY сортирует по умолчанию, и вы можете получить более высокую производительность, если вам не нужны данные, отсортированные, не сообщая им об этом.

DROP TEMPORARY TABLE IF EXISTS 'temp';
CREATE TEMPORARY TABLE temp AS ( SELECT a.id FROM <tables> WHERE <where> );
SELECT a.id, b, c, d, <foo> x FROM a JOIN temp ON a.id=temp.id ORDER BY e, id;
SELECT COUNT(*) FROM a JOIN temp WHERE <baz>;
SELECT c, COUNT(*) t, SUM(<bar>) o FROM a WHERE c IN (SELECT c FROM a JOIN temp GROUP BY c ORDER BY NULL) GROUP BY c ORDER BY NULL;
SELECT d, COUNT(*) t, SUM(<bar>) o FROM a WHERE d IN (SELECT d FROM a JOIN temp GROUP BY d ORDER BY NULL) GROUP BY d ORDER BY NULL;
DROP TEMPORARY TABLE IF EXISTS 'temp';
Теги:
mariadb

1 ответ

1
Лучший ответ

Лучшее, о чем я мог думать, - это использовать ВРЕМЕННУЮ таблицу.

ps iám, используя действующий код MySQL SQL, смешанный с тем же псевдо-кодом, что и тема

CREATE TEMPORARY TABLE <name> AS ( SELECT FROM <tables> WHERE (<where>) )

<foo> = k < NOW() - INTERVAL 3 HOUR
<bar> = j IS NOT NULL OR <foo>
<baz> = j IS NULL AND k > NOW() - INTERVAL 3 HOUR
so <baz> is !<bar>.  Every row should match one or the other

<where> often includes 1 or more of foo/bar/baz

SELECT a.id, b, c, d, <foo> x FROM <name> ORDER BY e, id

SELECT COUNT(*) FROM <name> WHERE <baz>

SELECT c, COUNT(*) t, SUM(<bar>) o FROM a WHERE c IN (SELECT c FROM <name> GROUP BY c) GROUP BY c

SELECT d, COUNT(*) t, SUM(<bar>) o FROM a WHERE d IN (SELECT d FROM <name> GROUP BY d) GROUP BY d

Ещё вопросы

Сообщество Overcoder
Наверх
Меню