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?

Alex Riley
sumber

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.
 */
Jordan S. Jones
sumber
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)
nimai
sumber
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

hacker13ua
sumber
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.

Prasanth Jayachandran
sumber
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.

blubb
sumber
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)
hacker13ua
sumber
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

Andrew
sumber
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
);
PatrikAkerstrand
sumber