Как стать автором
Обновить

Подсчет количества найденных записей в PostgreSQL

Время на прочтение 3 мин
Количество просмотров 42K
На работе в новом проекте используется СУБД PostgreSQL. Так как до сих пор я работал с MySQL, сейчас приходится изучать и открывать для себя Постгри. Первая проблема, которая меня заинтересовала — замена мускулевского SQL_CALC_FOUND_ROWS. При использовании этой константы в MySQL можно получить количество всех найденных по запросу записей, даже если запрос с limit'ом — это незаменимо при постраничном выводе поисковых результатов, когда используются «тяжелые» запросы.
Сходу готового решения найти не удалось. На форумах просто констатировали, что SQL_CALC_FOUND_ROWS в Постгри нет. Некоторые писали, что надо юзать count(*). И больше никакой информации. Но еще из MySQL мне было известно, что поиск с count()-запросом работает почти в 2 раза медленнее, чем с SQL_CALC_FOUND_ROWS. Я консультировался у тех, кто пользуется PostgreSQL, день мучал google и в результате получил 4 варианта замены SQL_CALC_FOUND_ROWS в PostgreSQL, один из которых вполне приемлимый по скорости.

Итак, сразу представлю те четыре варианта, о которых пойдет речь. Наш целевой запрос ищет в таблице записи, в которых встречается текст adf в поле `text`. Выбираем id 20 записей начиная от 180.000 по порядку и количество найденных всего.
Вариант 1. Взят из phpPgAdmin. Я просто заглянул в код этого клиента для PostgreSQL и посмотрел как подсчет сделан у них при просмотре данных таблицы. Используется 2 запроса с подзапросами. Удобство в том, что не надо парсить и менять исходный запрос, чтобы подсчитать количество записей, найденных им.
select count(id) from (select id from testing where text like '%adf%') as sub;
select * from (select id from testing where text like '%adf%') as sub limit 20 offset 180000

Вариант 2. Самый простой вариант, который обычно юзают новички как в MySQL, так и в Postgres и других СУБД. 2 запроса.
select count(id) from testing where text like '%adf%';
select id from testing where text like '%adf%' limit 20 offset 180000

Вариант 3. max_posedon. Это попытка эмуляции мускулевского SQL_CALC_FOUND_ROWS в Postgres по логике. Правда работает только при сортировке по id (в данном случае). Здесь подставляется id последней записи в выборке, т.е. записи под номером 180.000 + 20.
select id from testing where text like '%adf%' limit 20 offset 180000;
select count(id) from testing where text like '%adf%' and id > 132629;

Вариант 4. По советам пользователей irc.freenode.org, опять же max_posedon‘а, и этого ответа на форуме PostgreSQL, который прятался глубоко в гугле. Используется курсор.
DECLARE curs CURSOR FOR select id from testing where text like '%adf%';
MOVE FORWARD 180000 IN curs;
FETCH 20 FROM curs;
MOVE FORWARD ALL IN curs;

+ фунция PQcmdTuples() API Postgres (или $count = pg_cmdtuples($result); в PHP).
Обратите внимание, что все 4 варианта запросов следует выполнять в одной транзакции, тогда они работают быстрее. 4й вариант вовсе не будет работать, если не использовать одну транзакцию: теряется курсор.
Теперь о скоростях. Я провел тестирование скорости работы этих четырех вариантов. Вобщем-то тесты подтвердили ожидания. Но отмечу важный факт. Все запросы запускались на конфигурации PostgreSQL по умолчанию, которая не является оптимизированной на производительность. У меня под рукой просто не было оптимизированного сервера. Так что цифры могут немного корректироваться при запуске с “хорошим” конфигом. Однако суть не изменится.
Тестовые запуски проводились в PHP по 20 повторов 2 раза на каждый вариант. Доступен php-скрипт, который запускал тесты. Кому интересно, есть полная статистика выборок в Excel. Здесь опубликую лишь сводную таблицу:
Вар 1 Вар 2 Вар 3 Вар 4
Ср. время (мс) 647,41 648,25 450,64 370,67
Отношение к вар 4 1,75 1,75 1,22

Для сравнения время запросов без использования транзакции:
  • Вар 1: 1204 мс,
  • Вар 2: 689 мс,
  • Вар 3: 560 мс,
  • Вар 4 работает только в пределах транзакции.

Итоги. Самый быстрый вариант 4 с использованием курсора. Его скорость обусловлена тем, что “тяжелый” поисковый запрос выполняется только один раз. Далее проводятся операции с курсором. Аналогично работает и SQL_CALC_FOUND_ROWS в MySQL. На 20% от него отстает вариант 3 — попытка эмуляции работы SQL_CALC_FOUND_ROWS в PostgreSQL. Варианты 1 и 2 работат примерно с одинаковой скоростью и на 75% (более чем на 2/3!) уступают по скорости запросу с курсором.
P.S. для pg-гуру. Во-первых, если для вас эта информация покажется очевидной, то поверьте мне — для начинающих пользователей PostgreSQL это совсем не очевидно, и найти эту информацию не так просто. Во-вторых, жду от вас комментариев о тестах на тюнингованной на производительность конфигурации постгри или о вашем опыте, и о других вариантах подсчета.
Теги:
Хабы:
+33
Комментарии 43
Комментарии Комментарии 43

Публикации

Истории

Ближайшие события

PG Bootcamp 2024
Дата 16 апреля
Время 09:30 – 21:00
Место
Минск Онлайн
EvaConf 2024
Дата 16 апреля
Время 11:00 – 16:00
Место
Москва Онлайн
Weekend Offer в AliExpress
Дата 20 – 21 апреля
Время 10:00 – 20:00
Место
Онлайн