Mengapa array_agg () lebih lambat daripada konstruktor ARRAY () non-agregat?

13

Saya baru saja meninjau beberapa kode lama yang ditulis untuk pra-8.4 PostgreSQL , dan saya melihat sesuatu yang sangat bagus. Saya ingat memiliki fungsi khusus melakukan beberapa hal ini pada hari itu, tapi saya lupaarray_agg() . Untuk ulasan, agregasi modern ditulis seperti ini.

SELECT array_agg(x ORDER BY x DESC) FROM foobar;

Namun, pada suatu waktu, ditulis seperti ini,

SELECT ARRAY(SELECT x FROM foobar ORDER BY x DESC);

Jadi, saya mencobanya dengan beberapa data uji ..

CREATE TEMP TABLE foobar AS
SELECT * FROM generate_series(1,1e7)
  AS t(x);

Hasilnya mengejutkan .. Cara #OldSchoolCool jauh lebih cepat: kecepatan 25%. Apalagi menyederhanakannya tanpa ORDER, menunjukkan kelambatan yang sama.

# EXPLAIN ANALYZE SELECT ARRAY(SELECT x FROM foobar);
                                                         QUERY PLAN                                                          
-----------------------------------------------------------------------------------------------------------------------------
 Result  (cost=104425.28..104425.29 rows=1 width=0) (actual time=1665.948..1665.949 rows=1 loops=1)
   InitPlan 1 (returns $0)
     ->  Seq Scan on foobar  (cost=0.00..104425.28 rows=6017728 width=32) (actual time=0.032..716.793 rows=10000000 loops=1)
 Planning time: 0.068 ms
 Execution time: 1671.482 ms
(5 rows)

test=# EXPLAIN ANALYZE SELECT array_agg(x) FROM foobar;
                                                        QUERY PLAN                                                         
---------------------------------------------------------------------------------------------------------------------------
 Aggregate  (cost=119469.60..119469.61 rows=1 width=32) (actual time=2155.154..2155.154 rows=1 loops=1)
   ->  Seq Scan on foobar  (cost=0.00..104425.28 rows=6017728 width=32) (actual time=0.031..717.831 rows=10000000 loops=1)
 Planning time: 0.054 ms
 Execution time: 2174.753 ms
(4 rows)

Jadi, apa yang terjadi di sini. Mengapa array_agg , fungsi internal jauh lebih lambat dari voodoo SQL perencana?

Menggunakan " PostgreSQL 9.5.5 pada x86_64-pc-linux-gnu, dikompilasi oleh gcc (Ubuntu 6.2.0-5ubuntu12) 6.2.0 20161005, 64-bit"

Evan Carroll
sumber

Jawaban:

17

Tidak ada "sekolah tua" atau "ketinggalan zaman" tentang konstruktor ARRAY (Itulah apa ARRAY(SELECT x FROM foobar)). Ini modern seperti biasa. Gunakan untuk agregasi array sederhana.

Manual:

Dimungkinkan juga untuk membuat array dari hasil subquery. Dalam bentuk ini, konstruktor array ditulis dengan kata kunci ARRAYdiikuti oleh subquery yang di-kurung (tidak dikurung).

Fungsi agregatarray_agg() jauh lebih fleksibel dalam hal itu dapat diintegrasikan dalam SELECTdaftar dengan lebih banyak kolom, mungkin lebih banyak agregasi dalam yang sama SELECT, dan kelompok-kelompok sewenang-wenang dapat dibentuk dengan GROUP BY. Sedangkan konstruktor ARRAY hanya dapat mengembalikan satu array dari SELECTkolom tunggal yang dikembalikan.

Saya tidak mempelajari kode sumber, tetapi akan tampak jelas bahwa alat yang jauh lebih fleksibel juga lebih mahal.

Erwin Brandstetter
sumber
array_aggharus melacak urutan inputnya di mana ARRAYkonstruktor tampaknya melakukan sesuatu yang kira-kira setara dengan UNIONsebagai ekspresi internal. Jika saya harus berani menebak, array_aggkemungkinan akan membutuhkan lebih banyak memori. Saya tidak bisa menguji ini secara mendalam tetapi pada PostgreSQL 9.6 berjalan pada Ubuntu 16.04 ARRAY()kueri dengan ORDER BYmenggunakan penggabungan eksternal dan lebih lambat dari array_aggkueri. Seperti yang Anda katakan, singkat membaca kode jawaban Anda adalah penjelasan terbaik yang kami miliki.
Jeff
@Jeffrey: Anda menemukan sebuah kasus uji mana array_agg()yang lebih cepat daripada konstruktor array yang? Untuk kasus sederhana? Sangat tidak mungkin, tetapi jika demikian mungkin karena Postgres mendasarkan keputusannya untuk rencana permintaan pada statistik pengaturan biaya yang tidak akurat. Saya belum pernah melihat array_agg()mengungguli konstruktor array dan saya telah menguji berkali-kali.
Erwin Brandstetter
1
@ Jeffrey: Tidak ada efek caching yang menyesatkan? Apakah Anda menjalankan setiap permintaan lebih dari satu kali? Saya perlu melihat definisi tabel, kardinalitas, dan permintaan yang tepat untuk mengatakan lebih banyak.
Erwin Brandstetter
1
Ini bukan jawaban yang nyata. Banyak alat serbaguna dapat melakukan serta alat yang lebih spesifik. Jika menjadi serbaguna memang apa yang membuatnya lebih lambat, bagaimana dengan fleksibilitasnya?
Gavin Wahl
1
@ Jeffrey: Sepertinya Postgres memilih algoritma pengurutan yang berbeda untuk setiap varian (berdasarkan estimasi biaya dan statistik tabel). Dan akhirnya memilih metode yang lebih rendah untuk konstruktor ARRAY, yang menunjukkan bahwa satu atau lebih faktor dalam perhitungan perkiraan biaya terlalu jauh. Ini di atas meja temp? Apakah Anda VACUUM ANALYZEmelakukannya sebelum menjalankan kueri? Pertimbangkan: dba.stackexchange.com/a/18694/3684
Erwin Brandstetter
5

Saya percaya jawaban yang diterima oleh Erwin dapat ditambahkan dengan yang berikut.

Biasanya, kami bekerja dengan tabel biasa dengan indeks, bukan tabel sementara (tanpa indeks) seperti pada pertanyaan awal. Penting untuk dicatat bahwa agregasi, seperti ARRAY_AGG, tidak dapat meningkatkan indeks yang ada saat penyortiran dilakukan selama agregasi .

Misalnya, asumsikan kueri berikut:

SELECT ARRAY(SELECT c FROM t ORDER BY id)

Jika kita memiliki indeks aktif t(id, ...), indeks dapat digunakan, mendukung pemindaian berurutan tdiikuti oleh semacam t.id. Selain itu, jika kolom output yang dibungkus dalam array (di sini c) adalah bagian dari indeks (seperti indeks pada t(id, c)atau indeks termasuk padat(id) include(c) ), ini bahkan bisa menjadi hanya indeks-scan.

Sekarang, mari kita tulis ulang kueri itu sebagai berikut:

SELECT ARRAY_AGG(c ORDER BY id) FROM t

Sekarang, agregasi tidak akan menggunakan indeks dan harus mengurutkan baris dalam memori (atau bahkan lebih buruk untuk set data besar, pada disk). Ini akan selalu menjadi pemindaian berurutan pada tdiikuti oleh agregasi + semacam .

Sejauh yang saya tahu, ini tidak didokumentasikan dalam dokumentasi resmi, tetapi dapat diturunkan dari sumbernya. Ini harus menjadi kasus untuk semua versi saat ini, termasuk v11.

pbillen
sumber
2
Poin yang bagus. Tetapi dalam semua keadilan, query dengan array_agg()atau fungsi agregat yang sama masih bisa memanfaatkan indeks dengan subquery seperti: SELECT ARRAY_AGG(c) FROM (SELECT c FROM t ORDER BY id) sub. ORDER BYKlausa per-agregat adalah apa yang menghalangi penggunaan indeks dalam contoh Anda. Konstruktor array lebih cepat daripada array_agg()ketika bisa menggunakan indeks yang sama (atau tidak sama sekali). Hanya saja tidak serba guna. Lihat: dba.stackexchange.com/a/213724/3684
Erwin Brandstetter
1
Benar, itu perbedaan penting untuk dibuat. Saya sedikit mengubah jawaban saya untuk memperjelas bahwa komentar ini hanya berlaku ketika fungsi agregasi harus mengurutkan. Anda memang masih dapat mengambil untung dari indeks dalam kasus sederhana, karena PostgreSQL tampaknya memberikan jaminan bahwa agregasi akan terjadi dalam urutan yang sama seperti yang didefinisikan dalam subquery, sebagaimana dijelaskan dalam tautan. Itu keren sekali. Saya bertanya-tanya apakah ini masih berlaku dalam kasus tabel dipartisi dan / atau tabel FDW dan / atau pekerja paralel - dan apakah PostgreSQL dapat memenuhi janji ini dalam rilis mendatang.
pbillen
Sebagai catatan, saya sama sekali tidak bermaksud untuk meragukan jawaban yang diterima. Saya hanya berpikir itu adalah tambahan yang bagus untuk alasan tentang keberadaan dan penggunaan indeks dalam kombinasi dengan agregasi.
pbillen
1
Ini adalah tambahan yang bagus.
Erwin Brandstetter