PostgreSQL
 sql >> Teknologi Basis Data >  >> RDS >> PostgreSQL

Meningkatkan kinerja ORDER BY pada jsonb cross join dengan inner join group by

Mari buat data uji di postgresl 13 dengan 600 set data, 45rb file.

BEGIN;

CREATE TABLE cfiles (
 id SERIAL PRIMARY KEY, 
 dataset_id INTEGER NOT NULL,
 property_values jsonb NOT NULL);

INSERT INTO cfiles (dataset_id,property_values)
 SELECT 1+(random()*600)::INTEGER  AS did, 
   ('{"Sample Names": ["'||array_to_string(array_agg(DISTINCT prop),'","')||'"]}')::jsonb prop 
   FROM (
     SELECT 1+(random()*45000)::INTEGER AS cid,
     'Samp'||(power(random(),2)*30)::INTEGER AS prop 
     FROM generate_series(1,45000*4)) foo 
   GROUP BY cid;

COMMIT;
CREATE TABLE datasets ( id INTEGER PRIMARY KEY, name TEXT NOT NULL );
INSERT INTO datasets SELECT n, 'dataset'||n FROM (SELECT DISTINCT dataset_id n FROM cfiles) foo;
CREATE INDEX cfiles_dataset ON cfiles(dataset_id);
VACUUM ANALYZE cfiles;
VACUUM ANALYZE datasets;

Kueri asli Anda jauh lebih cepat di sini, tapi itu mungkin karena postgres 13 lebih pintar.

 Sort  (cost=114127.87..114129.37 rows=601 width=46) (actual time=658.943..659.012 rows=601 loops=1)
   Sort Key: datasets.name
   Sort Method: quicksort  Memory: 334kB
   ->  GroupAggregate  (cost=0.57..114100.13 rows=601 width=46) (actual time=13.954..655.916 rows=601 loops=1)
         Group Key: datasets.id
         ->  Nested Loop  (cost=0.57..92009.62 rows=4416600 width=46) (actual time=13.373..360.991 rows=163540 loops=1)
               ->  Merge Join  (cost=0.56..3677.61 rows=44166 width=78) (actual time=13.350..113.567 rows=44166 loops=1)
                     Merge Cond: (cfiles.dataset_id = datasets.id)
                     ->  Index Scan using cfiles_dataset on cfiles  (cost=0.29..3078.75 rows=44166 width=68) (actual time=0.015..69.098 rows=44166 loops=1)
                     ->  Index Scan using datasets_pkey on datasets  (cost=0.28..45.29 rows=601 width=14) (actual time=0.024..0.580 rows=601 loops=1)
               ->  Function Scan on jsonb_array_elements_text sn  (cost=0.01..1.00 rows=100 width=32) (actual time=0.003..0.004 rows=4 loops=44166)
 Execution Time: 661.978 ms

Kueri ini membaca tabel besar terlebih dahulu (cfiles) dan menghasilkan lebih sedikit baris karena agregasi. Dengan demikian akan lebih cepat untuk bergabung dengan kumpulan data setelah jumlah baris yang akan digabungkan berkurang, bukan sebelumnya. Ayo pindahkan gabung itu. Saya juga menyingkirkan CROSS JOIN yang tidak perlu, ketika ada fungsi pengembalian set di SELECT postgres akan melakukan apa yang Anda inginkan secara gratis.

SELECT dataset_id, d.name, sample_names FROM (
 SELECT dataset_id, string_agg(sn, '; ') as sample_names FROM (
  SELECT DISTINCT dataset_id,
   jsonb_array_elements_text(cfiles.property_values -> 'Sample Names') AS sn
   FROM cfiles
   ) f GROUP BY dataset_id
  )g JOIN datasets d ON (d.id=g.dataset_id)
 ORDER BY d.name;
                                                                   QUERY PLAN
------------------------------------------------------------------------------------------------------------------------------------------------
 Sort  (cost=536207.44..536207.94 rows=200 width=46) (actual time=264.435..264.502 rows=601 loops=1)
   Sort Key: d.name
   Sort Method: quicksort  Memory: 334kB
   ->  Hash Join  (cost=536188.20..536199.79 rows=200 width=46) (actual time=261.404..261.784 rows=601 loops=1)
         Hash Cond: (d.id = cfiles.dataset_id)
         ->  Seq Scan on datasets d  (cost=0.00..10.01 rows=601 width=14) (actual time=0.025..0.124 rows=601 loops=1)
         ->  Hash  (cost=536185.70..536185.70 rows=200 width=36) (actual time=261.361..261.363 rows=601 loops=1)
               Buckets: 1024  Batches: 1  Memory Usage: 170kB
               ->  HashAggregate  (cost=536181.20..536183.70 rows=200 width=36) (actual time=260.805..261.054 rows=601 loops=1)
                     Group Key: cfiles.dataset_id
                     Batches: 1  Memory Usage: 1081kB
                     ->  HashAggregate  (cost=409982.82..507586.70 rows=1906300 width=36) (actual time=244.419..253.094 rows=18547 loops=1)
                           Group Key: cfiles.dataset_id, jsonb_array_elements_text((cfiles.property_values -> 'Sample Names'::text))
                           Planned Partitions: 4  Batches: 1  Memory Usage: 13329kB
                           ->  ProjectSet  (cost=0.00..23530.32 rows=4416600 width=36) (actual time=0.030..159.741 rows=163540 loops=1)
                                 ->  Seq Scan on cfiles  (cost=0.00..1005.66 rows=44166 width=68) (actual time=0.006..9.588 rows=44166 loops=1)
 Planning Time: 0.247 ms
 Execution Time: 269.362 ms

Itu lebih baik. Tapi saya melihat LIMIT dalam kueri Anda, yang berarti Anda mungkin melakukan sesuatu seperti pagination. Dalam hal ini hanya perlu menghitung seluruh kueri untuk seluruh tabel file dan kemudian membuang sebagian besar hasil karena LIMIT, JIKA hasil kueri besar itu dapat mengubah apakah baris dari kumpulan data disertakan dalam hasil akhir atau tidak. Jika demikian, maka baris dalam kumpulan data yang tidak memiliki cfile yang sesuai tidak akan muncul di hasil akhir, yang berarti isi cfile akan mempengaruhi pagination. Yah, kita selalu bisa menipu:untuk mengetahui apakah baris dari kumpulan data harus disertakan, yang diperlukan hanyalah SATU baris dari cfile yang ada dengan id itu...

Jadi, untuk mengetahui baris kumpulan data mana yang akan dimasukkan dalam hasil akhir, kita dapat menggunakan salah satu dari dua kueri berikut:

SELECT id FROM datasets WHERE EXISTS( SELECT * FROM cfiles WHERE cfiles.dataset_id = datasets.id )
ORDER BY name LIMIT 20;

SELECT dataset_id FROM 
  (SELECT id AS dataset_id, name AS dataset_name FROM datasets ORDER BY dataset_name) f1
  WHERE EXISTS( SELECT * FROM cfiles WHERE cfiles.dataset_id = f1.dataset_id )
  ORDER BY dataset_name
  LIMIT 20;

Itu memakan waktu sekitar 2-3 milidetik. Kami juga dapat menipu:

CREATE INDEX datasets_name_id ON datasets(name,id);

Ini membuatnya menjadi sekitar 300 mikrodetik. Jadi, sekarang kita mendapatkan daftar dataset_id yang benar-benar akan digunakan (dan tidak dibuang) sehingga kita dapat menggunakannya untuk melakukan agregasi lambat besar hanya pada baris yang sebenarnya akan ada di hasil akhir, yang seharusnya menghemat banyak pekerjaan yang tidak perlu...

WITH ds AS (SELECT id AS dataset_id, name AS dataset_name
 FROM datasets WHERE EXISTS( SELECT * FROM cfiles WHERE cfiles.dataset_id = datasets.id )
 ORDER BY name LIMIT 20)

SELECT dataset_id, dataset_name, sample_names FROM (
 SELECT dataset_id, string_agg(DISTINCT sn, '; ' ORDER BY sn) as sample_names FROM (
  SELECT dataset_id, 
   jsonb_array_elements_text(cfiles.property_values -> 'Sample Names') AS sn 
   FROM ds JOIN cfiles USING (dataset_id)
  ) g GROUP BY dataset_id
  ) h JOIN ds USING (dataset_id)
 ORDER BY dataset_name;

Ini memakan waktu sekitar 30 ms, saya juga memesan dengan sample_name yang saya lupa sebelumnya. Ini harus bekerja untuk kasus Anda. Poin penting adalah waktu query tidak lagi bergantung pada ukuran file tabel, karena hanya akan memproses baris yang benar-benar dibutuhkan.

Silakan posting hasilnya;)



  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Tambahkan susunan case-insensitive ke PostgreSQL

  2. satu-ke-satu pembatasan yang berbeda pada seleksi

  3. Perl - DBI dan .pgpass

  4. RangeError untuk penetapan bilangan bulat sederhana di Rails 4.2.0 yang harus ditangkap oleh validasi

  5. Membuat Database PostgreSQL