В Postgres вы можете указать предложение IN, например:
SELECT * FROM user WHERE id IN (1000, 1001, 1002)
Кто-нибудь знает, какое максимальное количество параметров вы можете передать в IN?
В Postgres вы можете указать предложение IN, например:
SELECT * FROM user WHERE id IN (1000, 1001, 1002)
Кто-нибудь знает, какое максимальное количество параметров вы можете передать в IN?
В соответствии с исходным кодом, расположенным здесь, начиная с строки 850, PostgreSQL явно не ограничивает количество аргументов.
Ниже приведен кодовый комментарий к строке 870:
/*
* We try to generate a ScalarArrayOpExpr from IN/NOT IN, but this is only
* possible if the inputs are all scalars (no RowExprs) and there is a
* suitable array type available. If not, we fall back to a boolean
* condition tree with multiple copies of the lefthand expression.
* Also, any IN-list items that contain Vars are handled as separate
* boolean conditions, because that gives the planner more scope for
* optimization on such clauses.
*
* First step: transform all the inputs, and detect whether any are
* RowExprs or contain Vars.
*/
На самом деле это не ответ на данный вопрос, но он может помочь и другим.
По крайней мере, я могу сказать, что существует технический предел 32767 значений (= Short.MAX_VALUE), совместимых с бэкэнд PostgreSQL, используя драйвер PosGresql JDBC 9.1.
Это тест "delete from x where id in (... 100k values ...)" с драйвером postgresql jdbc:
Caused by: java.io.IOException: Tried to send an out-of-range integer as a 2-byte value: 100000
at org.postgresql.core.PGStream.SendInteger2(PGStream.java:201)
explain select * from test where id in (values (1), (2));
Seq Scan on test (cost=0.00..1.38 rows=2 width=208)
Filter: (id = ANY ('{1,2}'::bigint[]))
Но если попробуйте второй запрос:
explain select * from test where id = any (values (1), (2));
Hash Semi Join (cost=0.05..1.45 rows=2 width=208)
Hash Cond: (test.id = "*VALUES*".column1)
-> Seq Scan on test (cost=0.00..1.30 rows=30 width=208)
-> Hash (cost=0.03..0.03 rows=2 width=4)
-> Values Scan on "*VALUES*" (cost=0.00..0.03 rows=2 width=4)
Мы можем видеть, что postgres build temp table и присоединиться к ней
Нет ограничений на количество элементов, которые вы передаете в подразделение IN. Если есть больше элементов, он будет рассматривать его как массив, а затем для каждого сканирования в базе данных он проверяет, содержится ли он в массиве или нет. Этот подход не настолько масштабируемо. Вместо использования предложения IN попробуйте использовать INNER JOIN с временной таблицей. Для получения дополнительной информации см. http://www.xaprb.com/blog/2006/06/28/why-large-in-clauses-are-problematic/. Использование INNER JOIN масштабируется так же, как оптимизатор запросов может использовать хэш-соединение и другую оптимизацию. В то время как с предложением IN оптимизатор оптимизации не может оптимизировать запрос. Я заметил ускорение не менее 2x с этим изменением.
Как человек с большим опытом работы с БД Oracle, я тоже был обеспокоен этим ограничением. Я выполнил тест производительности для запроса с ~ 10 000 параметров в IN
-list, выбирая простые числа до 100 000 из таблицы с первыми 100 000 целыми числами, фактически перечисляя все простые числа как параметры запроса.
Мои результаты показывают, что вам не нужно беспокоиться о перегрузке оптимизатора плана запросов или получении планов без использования индекса, поскольку он преобразует запрос в использование = ANY({...}::integer[])
, где он может использовать индексы, как и ожидалось:
-- prepare statement, runs instantaneous:
PREPARE hugeplan (integer, integer, integer, ...) AS
SELECT *
FROM primes
WHERE n IN ($1, $2, $3, ..., $9592);
-- fetch the prime numbers:
EXECUTE hugeplan(2, 3, 5, ..., 99991);
-- EXPLAIN ANALYZE output for the EXECUTE:
"Index Scan using n_idx on primes (cost=0.42..9750.77 rows=9592 width=5) (actual time=0.024..15.268 rows=9592 loops=1)"
" Index Cond: (n = ANY ('{2,3,5,7, (...)"
"Execution time: 16.063 ms"
-- setup, should you care:
CREATE TABLE public.primes
(
n integer NOT NULL,
prime boolean,
CONSTRAINT n_idx PRIMARY KEY (n)
)
WITH (
OIDS=FALSE
);
ALTER TABLE public.primes
OWNER TO postgres;
INSERT INTO public.primes
SELECT generate_series(1,100000);
Тем не менее, этот (довольно старый) поток в списке рассылки pgsql-hackers указывает на то, что при планировании таких запросов все еще существуют незначительные затраты, так что поверьте мне на слово.
Если у вас есть запрос типа:
SELECT * FROM user WHERE id IN (1, 2, 3, 4 -- and thousands of another keys)
вы можете увеличить производительность, если переписать свой запрос следующим образом:
SELECT * FROM user WHERE id = ANY(VALUES (1), (2), (3), (4) -- and thousands of another keys)
Возможно, вам стоит рассмотреть возможность реорганизации этого запроса вместо добавления произвольно длинного списка идентификаторов... Вы можете использовать диапазон, если идентификаторы действительно соответствуют шаблону в вашем примере:
SELECT * FROM user WHERE id >= minValue AND id <= maxValue;
Другой вариант - добавить внутренний выбор:
SELECT *
FROM user
WHERE id IN (
SELECT userId
FROM ForumThreads ft
WHERE ft.id = X
);