PostgreSQL - maksymalna liczba parametrów w klauzuli „IN”?

147

W Postgres możesz określić klauzulę IN, na przykład:

SELECT * FROM user WHERE id IN (1000, 1001, 1002)

Czy ktoś wie, jaka jest maksymalna liczba parametrów, które możesz przekazać do IN?

Alex Riley
źródło

Odpowiedzi:

83

Zgodnie z kodem źródłowym znajdującym się tutaj, począwszy od wiersza 850, PostgreSQL nie ogranicza jawnie liczby argumentów.

Poniżej znajduje się komentarz do kodu z linii 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.
 */
Jordan S. Jones
źródło
56

Nie jest to tak naprawdę odpowiedź na obecne pytanie, jednak może pomóc również innym.

Przynajmniej mogę powiedzieć, że istnieje ograniczenie techniczne wynoszące 32767 wartości (= Short.MAX_VALUE) dopuszczalnych dla zaplecza PostgreSQL, przy użyciu sterownika JDBC Posgresql 9.1.

To jest test „usuń z x, gdzie id in (... 100k wartości ...)” ze sterownikiem 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)
nimai
źródło
6
OP zapytał o ograniczenie silnika DB, ale szukając ograniczenia JDBC przyszedłem tutaj i właśnie tego szukałem. Jest więc ograniczenie, dość wysokie.
9ilsdx 9rvj 0lo
36
explain select * from test where id in (values (1), (2));

PLAN ZAPYTANIA

 Seq Scan on test  (cost=0.00..1.38 rows=2 width=208)
   Filter: (id = ANY ('{1,2}'::bigint[]))

Ale jeśli spróbujesz drugiego zapytania:

explain select * from test where id = any (values (1), (2));

PLAN ZAPYTANIA

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)

Widzimy, że postgres buduje tabelę tymczasową i łączy się z nią

hacker13ua
źródło
Ale to, co słyszałem, oba postgres-9.3 + wydają się być takie same. datadoghq.com/blog/…
PiyusG
18

Nie ma ograniczeń co do liczby elementów, które przekazujesz do klauzuli IN. Jeśli będzie więcej elementów, uzna je za tablicę, a następnie przy każdym skanowaniu w bazie danych sprawdzi, czy znajduje się w tablicy, czy nie. To podejście nie jest tak skalowalne. Zamiast używać klauzuli IN spróbuj użyć INNER JOIN z tabelą tymczasową. Więcej informacji można znaleźć pod adresem http://www.xaprb.com/blog/2006/06/28/why-large-in-clauses-are-problematic/ . Używając skal INNER JOIN, a także optymalizatora zapytań, można wykorzystać łączenie hash i inną optymalizację. Podczas gdy w przypadku klauzuli IN optymalizator nie ma możliwości optymalizacji zapytania. Zauważyłem przyspieszenie co najmniej 2x przy tej zmianie.

Prasanth Jayachandran
źródło
2
Odnośnik, do którego się odnosisz, nie mówi, o którym DBMS mówi. Chociaż mogę potwierdzić, że w Oracle DB użycie tabel tymczasowych daje ogromny wzrost wydajności w porównaniu z łączeniem zapytań ORi INklauzulami ze względu na duży narzut związany z analizowaniem i planowaniem takich zapytań, nie mogłem potwierdzić problemu z Postgres 9.5, zobacz tę odpowiedź .
blubb
17

Jako osoba bardziej doświadczona w Oracle DB, byłem również zaniepokojony tym limitem. Przeprowadziłem test wydajności dla zapytania z ~ 10 IN000 parametrami na liście -list, pobierając liczby pierwsze do 100 000 z tabeli zawierającej pierwsze 100 000 liczb całkowitych , wymieniając wszystkie liczby pierwsze jako parametry zapytania .

Moje wyniki wskazują, że nie musisz się martwić o przeciążenie optymalizatora planu zapytań lub uzyskanie planów bez użycia indeksu , ponieważ przekształci to zapytanie w miejsce, w = ANY({...}::integer[])którym może wykorzystać indeksy zgodnie z oczekiwaniami:

-- 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);

Jednak ten (dość stary) wątek na liście mailingowej pgsql-hackers wskazuje, że planowanie takich zapytań nadal wiąże się z niemałymi kosztami, więc wierz mi na słowo z przymrużeniem oka.

blubb
źródło
3

Jeśli masz zapytanie takie jak:

SELECT * FROM user WHERE id IN (1, 2, 3, 4 -- and thousands of another keys)

możesz zwiększyć wydajność, jeśli przepiszesz zapytanie na przykład:

SELECT * FROM user WHERE id = ANY(VALUES (1), (2), (3), (4) -- and thousands of another keys)
hacker13ua
źródło
10
PostgreSQL EXPLAINmówi, że wewnętrznie przepisuje mój IN (...)as ANY ('{...}'::integer[]).
Kiran Jonnalagadda
4
W każdym razie @KiranJonnalagadda, zwiększa wydajność (być może pomijalną), jeśli nie jest potrzebna żadna praca wewnętrzna.
Rodrigo
1

Po prostu spróbowałem. odpowiedź brzmi -> liczba całkowita spoza zakresu jako wartość 2-bajtowa: 32768

Andrzej
źródło
0

Możesz rozważyć refaktoryzację tego zapytania zamiast dodawać dowolnie długą listę identyfikatorów ... Możesz użyć zakresu, jeśli identyfikatory rzeczywiście są zgodne ze wzorcem z Twojego przykładu:

SELECT * FROM user WHERE id >= minValue AND id <= maxValue;

Inną opcją jest dodanie wewnętrznego wyboru:

SELECT * 
FROM user 
WHERE id IN (
    SELECT userId
    FROM ForumThreads ft
    WHERE ft.id = X
);
PatrikAkerstrand
źródło