PostgreSQL - maximale Anzahl von Parametern in der IN-Klausel?


147

In Postgres können Sie eine IN-Klausel wie folgt angeben:

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

Weiß jemand, wie viele Parameter Sie maximal an IN übergeben können?

Antworten:


83

Gemäß dem hier befindlichen Quellcode , der in Zeile 850 beginnt, begrenzt PostgreSQL die Anzahl der Argumente nicht explizit.

Das Folgende ist ein Codekommentar aus Zeile 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.
 */

56

Dies ist keine wirkliche Antwort auf die vorliegende Frage, könnte aber auch anderen helfen.

Zumindest kann ich feststellen, dass es eine technische Grenze von 32767 Werten (= Short.MAX_VALUE) gibt, die mit dem JDBC-Treiber 9.1 von Posgresql an das PostgreSQL-Backend übergeben werden können.

Dies ist ein Test von "Aus x löschen, wobei ID in (... 100k-Werte ...)" mit dem Postgresql-JDBC-Treiber:

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)

6
Das OP hat nach der DB-Engine-Einschränkung gefragt, aber auf der Suche nach der JDBC-Einschränkung bin ich hierher gekommen und habe danach gesucht. Es gibt also eine ziemlich hohe Einschränkung.
9ilsdx 9rvj 0lo

36
explain select * from test where id in (values (1), (2));

Abfrageplan

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

Aber wenn Sie versuchen, 2. Abfrage:

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

Abfrageplan

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)

Wir können sehen, dass Postgres eine temporäre Tabelle erstellen und sich dieser anschließen


Aber was ich gehört habe, dass postgres-9.3 + beide gleich performant zu sein scheinen. datadoghq.com/blog/…
PiyusG

18

Die Anzahl der Elemente, die Sie an die IN-Klausel übergeben, ist unbegrenzt. Wenn mehr Elemente vorhanden sind, wird es als Array betrachtet und bei jedem Scan in der Datenbank wird geprüft, ob es im Array enthalten ist oder nicht. Dieser Ansatz ist nicht so skalierbar. Verwenden Sie anstelle der IN-Klausel INNER JOIN mit der temporären Tabelle. Weitere Informationen finden Sie unter http://www.xaprb.com/blog/2006/06/28/why-large-in-clauses-are-problematic/ . Wenn Sie die INNER JOIN-Skalierung gut verwenden, kann der Abfrageoptimierer Hash-Join und andere Optimierungen verwenden. Während mit der IN-Klausel für den Optimierer keine Möglichkeit besteht, die Abfrage zu optimieren. Ich habe mit dieser Änderung eine Beschleunigung von mindestens 2x festgestellt.


2
Der Link, auf den Sie sich beziehen, sagt nicht aus, um welches DBMS es sich handelt. Obwohl ich bestätigen kann, dass die Verwendung temporärer Tabellen in Oracle DB aufgrund des hohen Aufwands beim Parsen und Planen solcher Abfragen einen massiven Leistungsschub gegenüber der Verwendung von Abfragenkombinationen ORund IN-klauseln bedeutet, konnte ich das Problem mit Postgres 9.5 nicht bestätigen. Siehe diese Antwort .
Blubb

17

Als jemand, der mehr Erfahrung mit Oracle DB hat, war ich auch über diese Grenze besorgt. Ich habe einen Leistungstest für eine Abfrage mit ~ 10'000 Parametern in einer INListe durchgeführt und dabei Primzahlen bis zu 100'000 aus einer Tabelle mit den ersten 100'000 Ganzzahlen abgerufen, indem ich tatsächlich alle Primzahlen als Abfrageparameter aufgelistet habe .

Meine Ergebnisse zeigen, dass Sie sich keine Gedanken über das Überladen des Abfrageplanoptimierers oder das Abrufen von Plänen ohne Indexverwendung machen müssen , da die Abfrage so umgewandelt wird, dass = ANY({...}::integer[])sie wie erwartet Indizes nutzen kann:

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

Dieser (ziemlich alte) Thread auf der Mailingliste von pgsql-hackers weist jedoch darauf hin, dass die Planung solcher Abfragen immer noch nicht zu vernachlässigende Kosten verursacht. Nehmen Sie also mein Wort mit einem Körnchen Salz.


3

Wenn Sie Fragen haben wie:

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

Sie können die Leistung erhöhen, wenn Sie Ihre Abfrage wie folgt umschreiben:

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

10
PostgreSQL EXPLAINsagt, dass es intern mein IN (...)als neu schreibt ANY ('{...}'::integer[]).
Kiran Jonnalagadda

4
Wie auch immer, @KiranJonnalagadda, es erhöht die Leistung (vielleicht vernachlässigbar), wenn keine interne Arbeit erforderlich ist.
Rodrigo

1

Ich habe es gerade versucht. Die Antwort lautet -> Ganzzahl außerhalb des Bereichs als 2-Byte-Wert: 32768


0

Möglicherweise möchten Sie diese Abfrage umgestalten, anstatt eine beliebig lange Liste von IDs hinzuzufügen. Sie können einen Bereich verwenden, wenn die IDs tatsächlich dem Muster in Ihrem Beispiel folgen:

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

Eine weitere Option ist das Hinzufügen einer inneren Auswahl:

SELECT * 
FROM user 
WHERE id IN (
    SELECT userId
    FROM ForumThreads ft
    WHERE ft.id = X
);
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.