Я использую Eclipselink, Spring Data и Postgresql. В моем проекте я заметил, что при использовании постраничных результатов, предоставляемых репозиториями SpringData, есть такие запросы, как:
SELECT COUNT(id)
FROM table
WHERE [part generated according to specification]
где "id" является первичным ключом "таблицы". Копаясь с объяснением, я заметил, что COUNT (id) примерно в 10 раз медленнее, чем COUNT() для очень большой таблицы (count (id) ищет ненулевые значения в столбце "id", а count() просто возвращает количество строк, совпадающих критерии), также count (*) может использовать индексы, в то время как count (id) - not.
Я проследил класс базового репозитория SpringData, и, похоже, для генерации этого запроса отвечает только реализация JPA.
любая помощь оценена
- [edit] -
есть таблица:
\d ord_order
Table "public.ord_order"
Column | Type | Modificators
-------------------------+--------------------------+----------------------------------------------------------
id | integer | NOT NULL DEFAULT nextval('ord_order_id_seq'::regclass)
test_order | boolean | DEFAULT false
...
Indexes:
"pk_order" PRIMARY KEY, btree (id)
"idx_test_order" btree (test_order)
# explain SELECT COUNT(*) FROM ord_order WHERE (test_order = false);
QUERY PLAN
--------------------------------------------------------------------------
Aggregate (cost=89898.79..89898.80 rows=1 width=0)
-> Index Only Scan using idx_test_order on ord_order (cost=0.43..85375.37 rows=1809366 width=0)
Index Cond: (test_order = false)
Filter: (NOT test_order)
(4 wiersze)
# explain SELECT COUNT(id) FROM ord_order WHERE (test_order = false);
QUERY PLAN
--------------------------------------------------------------------------
Aggregate (cost=712924.52..712924.53 rows=1 width=4)
-> Seq Scan on ord_order (cost=0.00..708401.10 rows=1809366 width=4)
Filter: (NOT test_order)
(3 wiersze)
теперь разница составляет ~ 90k против ~ 713k и индексное сканирование против полного сканирования
Мне удалось выполнить обычную реализацию базового класса репозитория Spring и factory с использованием этой реализации. В результате сгенерированные запросы счетчика теперь имеют форму:
SELECT COUNT(1) FROM table
который имеет тот же план, что и COUNT (*). Это кажется прекрасным решением и работает по всему миру для всех определенных хранилищ в приложении.
Я не знал, как генерировать COUNT (*), COUNT (1) было намного проще, поскольку функция COUNT ожидает некоторые выражения в качестве параметров, и я могу поставить статическое значение - 1
count(*)
может использовать индекс, потому что в запросе указывается только один столбец (test_order
). count(id)
ссылается на два столбца, и для публикации результата Postgres должен выбрать столбец id
столбца и test_order
.
Как я уже говорил, некоторые люди считают, что count(id)
быстрее, чем count(*)
- когда ограничений на запрос нет. Миф, который никогда не был прав для любой СУБД с достойным оптимизатором. Я предполагаю, что ваш слой обфускации использует count(id)
вместо count(*)
.
Предполагая, что вы не хотите избавляться от ORM (чтобы снова получить контроль над SQL, используемым вашим приложением), единственным обходным решением, которое я вижу, является создание частичного индекса, который может использовать Postgres:
create index on ord_order (id)
where test_order = false;
id
действительно является первичным ключом таблицы, в это трудно поверить. Обычный миф состоит в том, чтоcount(id)
быстрее, чемcount(*)
поэтому, возможно, разработчики EclipseLink поверили в этот миф (я никогда не видел, чтобы это было правдой)