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

Sammenføjning af 2 store postgres-borde ved hjælp af int8range skaleres ikke godt

Jeg tænkte oprindeligt på en lateral sammenføjning som i andre foreslåede tilgange (f.eks. den sidste forespørgsel fra Erwin Brandstetter, hvor han bruger simpel int8 datatype og simple indekser), men ville ikke skrive det i svaret, fordi jeg tænkte, at det ikke er rigtig effektivt. Når du prøver at finde alle netblock intervaller, der dækker det givne interval, hjælper et indeks ikke meget.

Jeg gentager Erwin Brandstetters forespørgsel her:

SELECT * -- vælg kun de kolonner, du skal bruge for at gøre det hurtigere FROM routing_details r , LATERAL ( SELECT * FROM netblock_details n HVOR n.ip_min <=r.ip_min OG n.ip_max>=r.ip_max BESTIL EFTER n .ip_max - n.ip_min LIMIT 1 ) n; 

Når du har et indeks på netblock_details, sådan her:

OPRET INDEX netblock_details_ip_min_max_idx PÅ netblock_details (ip_min, ip_max DESC NULLS LAST); 

du kan hurtigt (i O(logN) ) find startpunktet for scanningen i netblock_details tabel - enten det maksimale n.ip_min det er mindre end r.ip_min , eller minimum n.ip_max det er mere end r.ip_max . Men så skal du scanne/læse resten af ​​indekset/tabellen og for hver række foretage den anden del af kontrollen og filtrere de fleste rækker fra.

Med andre ord hjælper dette indeks med hurtigt at finde den startrække, der opfylder de første søgekriterier:n.ip_min <=r.ip_min , men så fortsætter du med at læse alle rækker, der opfylder dette kriterium, og for hver sådan række udfører du den anden kontrol n.ip_max>=r.ip_max . I gennemsnit (hvis data har jævn fordeling) skal du læse halvdelen af ​​rækkerne i netblock_details bord. Optimizer kan vælge at bruge indeks til at søge n.ip_max>=r.ip_max først og derefter anvende det andet filter n.ip_min <=r.ip_min , men du kan ikke bruge dette indeks til at anvende begge filtre sammen.

Slutresultat:for hver række fra routing_details vi læser halvdelen af ​​rækkerne fra netblock_details . 600K * 4M =2.400.000.000.000 rækker. Det er 2 gange bedre end kartesisk produkt. Du kan se dette nummer (kartesisk produkt) i outputtet af EXPLAIN ANALYZE i spørgsmålet.

592.496 * 8.221.675 =4.871.309.550.800 

Ikke underligt, at dine forespørgsler løber tør for diskplads, når du prøver at materialisere og sortere dette.

Den overordnede proces på højt niveau for at nå det endelige resultat:

  • deltage i to borde (uden at finde det bedste match endnu). I værste tilfælde er det et kartesisk produkt, i det bedste tilfælde dækker det hele intervaller fra netblock_details for hvert område fra routing_details . Du sagde, at der er flere poster i netblock_details for hver indtastning i routing_details , alt fra 3 til omkring 10. Så resultatet af denne joinforbindelse kunne have ~6M rækker (ikke for mange)

  • bestil/partition resultatet af joinforbindelsen ved hjælp af routing_details intervaller og for hvert sådant sortiment skal du finde det bedste (mindste) dækningsområde fra netblock_details .

Min idé er at vende forespørgslen. I stedet for at finde alle dækningsområder fra større netblock_details for hver række fra mindre routing_details tabel foreslår jeg at finde alle mindre områder fra mindre routing_details for hver række fra større netblock_details .

Totrinsproces

For hver række fra større netblock_details find alle områder fra routing_details som er inde i netblock rækkevidde.

Jeg ville bruge følgende skema i forespørgslerne. Jeg har tilføjet primær nøgle ID til begge borde.

CREATE TABLE routing_details (ID int,ip_min int8,ip_max int8,asn text,netblock text);CREATE TABLE netblock_details (ID int,ip_min int8,ip_max int8,navntekst,landetekst,kildetekst);SELECT netblock_details.ID AS n_ID ,netblock_details.ip_max - netblock_details.ip_min AS n_length ,r.ID AS r_IDFROM netblock_details INNER JOIN LATERAL (VÆLG routing_details.ID FRA routing_details WHERE routing_details.ip_min>=_ netmin_details.ip_min>=_net -- bemærk hvordan routing_details.ip_min er begrænset fra begge sider -- dette ville gøre det muligt kun at scanne (forhåbentlig) lille -- del af tabellen i stedet for hel eller halv tabel OG routing_details.ip_max <=netblock_details.ip_max -- dette klausul sikrer, at hele routingområdet -- er inden for netblokområdet ) AS r ON true 

Vi har brug for indeks på routing_details(ip_min, ip_max) . Det vigtigste her er indeks på ip_min . At have anden kolonne i indekset hjælper ved at eliminere behovet for at foretage opslag efter værdien ip_max; det hjælper ikke i træsøgningen.

Bemærk, at den laterale underforespørgsel ikke har LIMIT . Det er ikke det endelige resultat endnu. Denne forespørgsel skulle returnere ~6M rækker. Gem dette resultat i en midlertidig tabel. Tilføj et indeks til den midlertidige tabel på (r_ID, n_length, n_ID) . n_ID er igen bare at fjerne ekstra opslag. Vi har brug for et indeks, undgå at sortere dataene for hver r_ID .

Sidste trin

For hver række fra routing_details find n_ID med den mindste n_length . Nu kan vi bruge sidesammenføjningen i "korrekt" rækkefølge. Her temp tabel er resultatet af det forrige trin med indekset .

SELECT routing_details.* ,t.n_ID ,netblock_details.*FROM routing_details INNER JOIN LATERAL (VÆLG temp.n_ID FRA temp. WHERE temp.r_ID =routing_details.ID ORDER BY temp.n_length LIMIT ON true ) SOM t. INNER JOIN netblock_details PÅ netblock_details.ID =t.n_ID 

Her skal underforespørgsel være en søgning af indekset, ikke scanning. Optimizer burde være smart nok til kun at søge og returnere det først fundne resultat på grund af LIMIT 1 , så du vil have 600.000 indekssøgninger i 6M række midlertidige tabel.

Originalt svar (jeg beholder det kun for diagrammet over områder):

Kan du "snyde"?

Hvis jeg har forstået din forespørgsel korrekt, for hver routing_details.range du vil finde en mindste netblock_details.range der dækker/er større end routing_details.range .

Givet følgende eksempel, hvor r er routingområde og n1, ..., n8 er netblok-intervaller, er det korrekte svar n5 .

|---| n1 |------------------------| n2 |--------------| n3 |-----| n4 |------------------------| n5 |---------------------------------------------| n6 |--------------------------------| n7 |-----| n8 |------------| r start endn.start <=r.start AND n.end>=r.endorder by n.lengthlimit 1

Din forespørgsel, der tog 14 timer viser, at indeksscanning tog 6ms, men sortering efter rækkevidde tog 80ms.

Med denne form for søgning er der ingen simpel 1D-bestilling af dataene. Du bruger n.start sammen med n.end og sammen med n.length . Men måske er dine data ikke så generiske, eller det er OK at returnere et noget andet resultat.

For eksempel, hvis det var OK at returnere n6 som et resultat, kunne det arbejde meget hurtigere. n6 er det dækningsområde, der har størst start :

n.start <=r.start OG n.end>=r.endorder by n.start desclimit 1  

Eller du kan gå efter n7 , som har den mindste ende :

n.start <=r.start OG n.end>=r.endorder by n.endlimit 1  

Denne form for søgning ville bruge simpelt indeks på n.start (eller n.end ) uden ekstra sortering.

En anden, helt anden tilgang. Hvor store/lange er intervallerne? Hvis de er relativt korte (få tal), kan du prøve at gemme dem som en eksplicit liste over heltal i stedet for et interval. For eksempel område [5-8] vil blive gemt som 4 rækker:(5, 6, 7, 8) . Med denne lagermodel kan det være nemmere at finde skæringspunkter mellem områder.



  1. SQL Azure:Database XXXYYY på serveren er ikke tilgængelig i øjeblikket

  2. SQL Server's Equivalent to Sleep():WAITFOR-erklæringen

  3. uploade billeder + billedinformation fra php-formular til mysql-database

  4. ECONNRESET fejl ved nedbrud af NodeJS-applikation