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 frarouting_details
. Du sagde, at der er flere poster inetblock_details
for hver indtastning irouting_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 franetblock_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
på (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.