PostgreSQL - jumlah parameter maksimum dalam klausa “IN”?


147

Di Postgres, Anda dapat menentukan klausa IN, seperti ini:

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

Adakah yang tahu berapa jumlah maksimum parameter yang dapat Anda berikan ke IN?

Jawaban:


83

Menurut kode sumber yang terletak di sini, mulai dari baris 850, PostgreSQL tidak secara eksplisit membatasi jumlah argumen.

Berikut ini adalah komentar kode dari baris 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

Ini sebenarnya bukan jawaban untuk pertanyaan ini, tetapi mungkin juga membantu orang lain.

Setidaknya saya bisa tahu ada batas teknis 32767 nilai (= Short.MAX_VALUE) yang dapat dilewati untuk backend PostgreSQL, menggunakan driver JDBC Posgresql's 9.1.

Ini adalah tes "delete from x where id in (... 100k values ​​...)" dengan driver jdbc postgresql:

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
OP telah bertanya tentang batasan mesin DB, tetapi mencari batasan JDBC saya telah datang ke sini dan itulah yang saya cari. Jadi ada batasannya, bagaimanapun, cukup tinggi.
9ilsdx 9rvj 0lo

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

RENCANA QUERY

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

Tetapi jika coba kueri ke-2:

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

RENCANA QUERY

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)

Kita dapat melihat bahwa postgres membuat tabel temp dan bergabung dengannya


Tapi apa yang saya dengar bahwa postgres-9.3 + keduanya nampaknya pemain yang sama. datadoghq.com/blog/…
PiyusG

18

Tidak ada batasan jumlah elemen yang Anda lewati untuk klausa IN. Jika ada lebih banyak elemen, ia akan menganggapnya sebagai array dan kemudian untuk setiap pemindaian dalam database, ia akan memeriksa apakah terdapat dalam array atau tidak. Pendekatan ini tidak begitu scalable. Alih-alih menggunakan klausa IN coba gunakan INNER JOIN dengan temp table. Lihat http://www.xaprb.com/blog/2006/06/28/why-large-in-clauses-are-problematic/ untuk info lebih lanjut. Menggunakan skala INNER JOIN serta pengoptimal kueri dapat menggunakan hash join dan optimisasi lainnya. Sedangkan dengan klausa IN tidak ada cara bagi pengoptimal untuk mengoptimalkan kueri. Saya perhatikan speedup minimal 2x dengan perubahan ini.


2
Tautan yang Anda maksud tidak mengatakan apa DBMS yang dibicarakannya. Sementara saya dapat mengonfirmasi bahwa pada Oracle DB, menggunakan tabel sementara memberikan peningkatan kinerja yang sangat besar dibandingkan menggunakan penggabungan kueri ORdan INklausa karena besarnya overhead dalam penguraian dan perencanaan kueri seperti itu, saya tidak dapat mengkonfirmasi masalah dengan Postgres 9.5, lihat jawaban ini .
blubb

17

Sebagai seseorang yang lebih berpengalaman dengan Oracle DB, saya juga khawatir tentang batasan ini. Saya melakukan tes kinerja untuk kueri dengan ~ 10'000 parameter dalam daftar IN, mengambil bilangan prima hingga 100'000 dari tabel dengan bilangan bulat 100'000 pertama dengan benar-benar mendaftarkan semua bilangan prima sebagai parameter kueri .

Hasil saya menunjukkan bahwa Anda tidak perlu khawatir tentang kelebihan pengoptimal rencana kueri atau mendapatkan rencana tanpa penggunaan indeks , karena itu akan mengubah kueri untuk digunakan di = ANY({...}::integer[])mana ia dapat meningkatkan indeks seperti yang diharapkan:

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

Namun, utas ini (yang agak lama) pada milis pgsql-hacker menunjukkan bahwa masih ada biaya yang tidak dapat diabaikan dalam perencanaan pertanyaan semacam itu, jadi terima kata-kata saya dengan sebutir garam.


3

Jika Anda memiliki pertanyaan seperti:

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

Anda dapat meningkatkan kinerja jika menulis ulang kueri Anda seperti:

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

10
PostgreSQL EXPLAINmengatakan secara internal menulis ulang IN (...)as saya ANY ('{...}'::integer[]).
Kiran Jonnalagadda

4
Pokoknya, @KiranJonnalagadda, ini meningkatkan kinerja (dapat diabaikan, mungkin) jika tidak diperlukan kerja internal.
Rodrigo

1

Baru saja mencobanya. jawabannya adalah -> integer out-of-range sebagai nilai 2-byte: 32768


0

Anda mungkin ingin mempertimbangkan refactoring kueri itu alih-alih menambahkan daftar id panjang yang sewenang-wenang ... Anda bisa menggunakan rentang jika id memang mengikuti pola dalam contoh Anda:

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

Pilihan lain adalah menambahkan pilih dalam:

SELECT * 
FROM user 
WHERE id IN (
    SELECT userId
    FROM ForumThreads ft
    WHERE ft.id = X
);
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.