sql >> Database teknologi >  >> RDS >> PostgreSQL

Forbedring af ydeevnen af ​​ORDER BY på jsonb cross join med indre joingruppe af

Lad os oprette testdata på postgresl 13 med 600 datasæt, 45k cfiler.

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;

Din oprindelige forespørgsel er meget hurtigere her, men det er sandsynligvis fordi postgres 13 bare er smartere.

 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

Denne forespørgsel læser først en stor tabel (cfiles) og producerer meget færre rækker på grund af aggregering. Det vil således være hurtigere at forbinde med datasæt, efter at antallet af rækker, der skal forbindes, er reduceret, ikke før. Lad os flytte den sammenføjning. Også jeg slap af med CROSS JOIN, som er unødvendigt, når der er en sæt-returnende funktion i en SELECT postgres vil gøre hvad du vil 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

Det er bedre. Men jeg ser en LIMIT i din forespørgsel, hvilket betyder, at du sandsynligvis laver noget som paginering. I dette tilfælde er det kun nødvendigt at beregne hele forespørgslen for hele cfiles-tabellen og derefter smide de fleste resultater væk på grund af LIMIT, HVIS resultaterne af den store forespørgsel kan ændre, om en række fra datasæt er inkluderet i det endelige resultat eller ikke. Hvis det er tilfældet, vil rækker i datasæt, som ikke har tilsvarende c-filer, ikke vises i det endelige resultat, hvilket betyder, at indholdet af c-filer vil påvirke paginering. Nå, vi kan altid snyde:for at vide, om en række fra datasæt skal inkluderes, kræves det blot, at der findes EN række fra cfiler med det id...

Så for at vide, hvilke rækker af datasæt der vil blive inkluderet i det endelige resultat, kan vi bruge en af ​​disse to forespørgsler:

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;

De tager omkring 2-3 millisekunder. Vi kan også snyde:

CREATE INDEX datasets_name_id ON datasets(name,id);

Dette bringer det ned til omkring 300 mikrosekunder. Så nu har vi listen over dataset_id, der faktisk vil blive brugt (og ikke smidt væk), så vi kan bruge den til kun at udføre den store langsomme aggregering på de rækker, der faktisk vil være i det endelige resultat, hvilket burde spare en stor mængde af unødvendigt arbejde...

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;

Dette tager omkring 30 ms, også jeg satte ordren efter sample_name, som jeg havde glemt før. Det burde virke for din sag. En vigtig pointe er, at forespørgselstiden ikke længere afhænger af størrelsen af ​​tabel c-filer, da den kun vil behandle de rækker, der faktisk er nødvendige.

Send venligst resultater;)



  1. Strengt automatisk forøgelse af værdi i MySQL

  2. Sådan løses ORA 00936 Missing Expression Error?

  3. Sådan genereres drop-tabelerklæring for alle tabellerne i en database - SQL Server / T-SQL vejledning del 48

  4. Hibernate opretter ikke tabel i databasen