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

Matching af udbud og efterspørgsel — løsninger, del 2

[ Hop til:Original udfordring | Løsninger:Del 1 | Del 2 | del 3]

I denne artikel fortsætter jeg dækningen af ​​løsninger på Peter Larssons lokkende matchende udbud med efterspørgsel udfordring. Tak igen til Luca, Kamil Kosno, Daniel Brown, Brian Walker, Joe Obbish, Rainer Hoffmann, Paul White, Charlie og Peter Larsson for at sende jeres løsninger.

I sidste måned dækkede jeg en løsning baseret på interval skæringspunkter ved hjælp af en klassisk prædikatbaseret interval skæringstest. Jeg vil referere til den løsning som klassiske kryds . Den klassiske intervalskæringstilgang resulterer i en plan med kvadratisk skalering (N^2). Jeg demonstrerede dens dårlige ydeevne mod prøveinput, der spænder fra 100K til 400K rækker. Det tog løsningen 931 sekunder at fuldføre mod 400K-rækkens input! Denne måned vil jeg starte med kort at minde dig om sidste måneds løsning, og hvorfor den skalerer og yder så dårligt. Jeg vil derefter introducere en tilgang baseret på en revision af intervalskæringstesten. Denne tilgang blev brugt af Luca, Kamil og muligvis også Daniel, og den muliggør en løsning med meget bedre ydeevne og skalering. Jeg vil referere til den løsning som reviderede vejkryds .

Problemet med den klassiske intersektionstest

Lad os starte med en hurtig påmindelse om sidste måneds løsning.

Jeg brugte følgende indekser på input dbo.Auktionstabellen til at understøtte beregningen af ​​de løbende totaler, der producerer efterspørgsels- og udbudsintervallerne:

-- Index to support solution
 
CREATE UNIQUE NONCLUSTERED INDEX idx_Code_ID_i_Quantity
  ON dbo.Auctions(Code, ID)
  INCLUDE(Quantity);
 
-- Enable batch-mode Window Aggregate
 
CREATE NONCLUSTERED COLUMNSTORE INDEX idx_cs
  ON dbo.Auctions(ID)
  WHERE ID = -1 AND ID = -2;

Følgende kode har den komplette løsning, der implementerer den klassiske intervalskæringstilgang:

-- Drop temp tables if exist
 
SET NOCOUNT ON;
DROP TABLE IF EXISTS #MyPairings, #Demand, #Supply;
GO
 
WITH D0 AS
 
-- D0 computes running demand as EndDemand
 
(
  SELECT ID, Quantity,
    SUM(Quantity) OVER(ORDER BY ID ROWS UNBOUNDED PRECEDING) AS EndDemand
  FROM dbo.Auctions
  WHERE Code = 'D'
),
 
-- D extracts prev EndDemand as StartDemand, expressing start-end demand as an interval
 
D AS
(
  SELECT ID, Quantity, EndDemand - Quantity AS StartDemand, EndDemand
  FROM D0
)
SELECT ID,
  CAST(ISNULL(StartDemand, 0.0) AS DECIMAL(19, 6)) AS StartDemand,
  CAST(ISNULL(EndDemand,   0.0) AS DECIMAL(19, 6)) AS EndDemand
INTO #Demand
FROM D;
 
WITH S0 AS
 
-- S0 computes running supply as EndSupply
 
(
  SELECT ID, Quantity,
    SUM(Quantity) OVER(ORDER BY ID ROWS UNBOUNDED PRECEDING) AS EndSupply
  FROM dbo.Auctions
  WHERE Code = 'S'
),
 
-- S extracts prev EndSupply as StartSupply, expressing start-end supply as an interval
 
S AS
(
  SELECT ID, Quantity, EndSupply - Quantity AS StartSupply, EndSupply
  FROM S0
)
SELECT ID, 
  CAST(ISNULL(StartSupply, 0.0) AS DECIMAL(19, 6)) AS StartSupply,
  CAST(ISNULL(EndSupply, 0.0) AS DECIMAL(19, 6)) AS EndSupply
INTO #Supply
FROM S;
 
CREATE UNIQUE CLUSTERED INDEX idx_cl_ES_ID ON #Supply(EndSupply, ID);
 
-- Trades are the overlapping segments of the intersecting intervals
 
SELECT
  D.ID AS DemandID, S.ID AS SupplyID,
  CASE WHEN EndDemand < EndSupply THEN EndDemand ELSE EndSupply END - CASE WHEN StartDemand > StartSupply THEN StartDemand ELSE StartSupply END
    AS TradeQuantity
INTO #MyPairings
FROM #Demand AS D
  INNER JOIN #Supply AS S WITH (FORCESEEK)
    ON D.StartDemand < S.EndSupply AND D.EndDemand > S.StartSupply;

Denne kode starter med at beregne efterspørgsels- og udbudsintervallerne og skrive dem til midlertidige tabeller kaldet #Demand og #Supply. Koden opretter derefter et klynget indeks på #Supply med EndSupply som den førende nøgle for at understøtte skæringstesten bedst muligt. Endelig forbinder koden #Supply og #Demand, der identificerer handler som de overlappende segmenter af de skærende intervaller, ved hjælp af følgende joinprædikat baseret på den klassiske intervalskæringstest:

D.StartDemand < S.EndSupply AND D.EndDemand > S.StartSupply

Planen for denne løsning er vist i figur 1.

Figur 1:Forespørgselsplan for løsning baseret på klassiske kryds

Som jeg forklarede i sidste måned, blandt de to involverede områdeprædikater kan kun det ene bruges som et søgeprædikat som en del af en indekssøgningsoperation, hvorimod det andet skal anvendes som et resterende prædikat. Det kan du tydeligt se i planen for den sidste forespørgsel i figur 1. Derfor gad jeg kun lave ét indeks på en af ​​tabellerne. Jeg tvang også brugen af ​​en søgning i det indeks, jeg oprettede ved hjælp af FORCESEEK-tippet. Ellers kan optimeringsværktøjet ende med at ignorere det indeks og oprette et af dets eget ved hjælp af en Index Spool-operator.

Denne plan har kvadratisk kompleksitet, da pr. række i den ene side—#Demand i vores tilfælde—indekssøgningen i gennemsnit skal have adgang til halvdelen af ​​rækkerne i den anden side—#Udbud i vores tilfælde—baseret på søgeprædikatet, og identificere sidste kampe ved at anvende det resterende prædikat.

Hvis det stadig er uklart for dig, hvorfor denne plan har kvadratisk kompleksitet, kunne en visuel afbildning af arbejdet måske hjælpe, som vist i figur 2.

Figur 2:Illustration af arbejde med løsning baseret på klassiske skæringspunkter

Denne illustration repræsenterer det arbejde, der er udført af de indlejrede løkker i planen for den sidste forespørgsel. #Demand-heapen er det ydre input af joinforbindelsen, og det klyngede indeks på #Supply (med EndSupply som den førende nøgle) er det indre input. De røde linjer repræsenterer indekssøgningsaktiviteterne udført pr. række i #Demand i indekset på #Supply og de rækker, de får adgang til som en del af rækkeviddescanningerne i indeksbladet. I retning af ekstremt lave StartDemand-værdier i #Demand skal rækkeviddescanningen have adgang tæt på alle rækker i #Supply. Mod ekstremt høje StartDemand-værdier i #Demand skal rækkeviddescanningen have adgang til tæt på nul rækker i #Supply. I gennemsnit skal rækkeviddescanningen have adgang til omkring halvdelen af ​​rækkerne i #Forsyning pr. efterspurgt række. Med D-efterspørgselsrækker og S-forsyningsrækker er antallet af rækker, der tilgås, D + D*S/2. Det er ud over omkostningerne ved de søgninger, der bringer dig til de matchende rækker. For eksempel, når du udfylder dbo.Auktioner med 200.000 efterspørgselsrækker og 200.000 forsyningsrækker, oversættes dette til, at Nested Loops-sammenføjningen får adgang til 200.000 efterspørgselsrækker + 200.000*200.000/2 forsyningsrækker, eller 200,000,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20 Der sker en masse genscanning af forsyningsrækker her!

Hvis du vil holde fast i ideen om intervalkryds, men opnå god ydeevne, skal du finde på en måde at reducere mængden af ​​udført arbejde på.

I sin løsning bucketed Joe Obbish intervallerne baseret på den maksimale intervallængde. På denne måde var han i stand til at reducere antallet af rækker, de sammenføjninger var nødvendige for at håndtere og stole på batchbehandling. Han brugte den klassiske intervalskæringstest som et sidste filter. Joes løsning fungerer godt, så længe den maksimale intervallængde ikke er særlig høj, men løsningens køretid øges, når den maksimale intervallængde øges.

Så hvad kan du ellers gøre...?

Revideret skæringstest

Luca, Kamil og muligvis Daniel (der var en uklar del om hans postede løsning på grund af hjemmesidens formatering, så jeg var nødt til at gætte) brugte en revideret interval skæringstest, der muliggør meget bedre udnyttelse af indeksering.

Deres skæringstest er en adskillelse af to prædikater (prædikater adskilt af OR-operator):

   (D.StartDemand >= S.StartSupply AND D.StartDemand < S.EndSupply) OR (S.StartSupply >= D.StartDemand AND S.StartSupply < D.EndDemand)

På engelsk skærer efterspørgselsstartafgrænseren enten udbudsintervallet på en inkluderende, eksklusiv måde (inklusive starten og ekskluderet slutningen); eller udbudsstartafgrænseren skærer efterspørgselsintervallet på en inkluderende, eksklusiv måde. For at gøre de to prædikater usammenhængende (ikke have overlappende tilfælde), men alligevel fuldstændige i at dække alle tilfælde, kan du beholde =-operatoren i kun den ene eller den anden, for eksempel:

   (D.StartDemand >= S.StartSupply AND D.StartDemand < S.EndSupply) OR (S.StartSupply > D.StartDemand AND S.StartSupply < D.EndDemand)

Denne reviderede intervalskæringstest er ret indsigtsfuld. Hvert af prædikaterne kan potentielt effektivt bruge et indeks. Overvej prædikat 1:

D.StartDemand >= S.StartSupply AND D.StartDemand < S.EndSupply
^^^^^^^^^^^^^                      ^^^^^^^^^^^^^

Forudsat at du opretter et dækkende indeks på #Demand med StartDemand som den ledende nøgle, kan du potentielt få en Nested Loops join ved at anvende arbejdet illustreret i figur 3.


Figur 3:Illustration af teoretisk arbejde, der kræves for at behandle prædikatet 1

Ja, du betaler stadig for en søgning i #Demand-indekset pr. række i #Supply, men intervallet scanner i indeksbladet adgang til næsten usammenhængende undersæt af rækker. Ingen genscanning af rækker!

Situationen er den samme med prædikat 2:

S.StartSupply > D.StartDemand AND S.StartSupply < D.EndDemand
^^^^^^^^^^^^^                     ^^^^^^^^^^^^^

Hvis du antager, at du opretter et dækkende indeks på #Supply med StartSupply som den førende nøgle, kan du potentielt få en Nested Loops-sammenføjning ved at anvende arbejdet illustreret i figur 4.


Figur 4:Illustration af teoretisk arbejde, der kræves for at behandle prædikatet 2

Her betaler du også for en søgning i #Udbudsindekset pr. række i #Demand, og derefter scanner intervallet i indeksbladet næsten usammenhængende delmængder af rækker. Igen, ingen genscanning af rækker!

Forudsat D efterspørgselsrækker og S forsyningsrækker, kan arbejdet beskrives som:

Number of index seek operations: D  + S
Number of rows accessed:         2D + 2S

Så sandsynligvis er den største del af omkostningerne her de I/O-omkostninger, der er forbundet med søgningerne. Men denne del af arbejdet har lineær skalering sammenlignet med den kvadratiske skalering af den klassiske intervalskæringsforespørgsel.

Selvfølgelig skal du overveje de foreløbige omkostninger ved indeksoprettelsen på de midlertidige tabeller, som har n log n skalering på grund af den involverede sortering, men du betaler denne del som en foreløbig del af begge løsninger.

Lad os prøve at omsætte denne teori i praksis. Lad os starte med at udfylde de midlertidige #Demand og #supply-tabeller med efterspørgsels- og udbudsintervallerne (samme som med de klassiske intervalskæringer) og oprette de understøttende indekser:

-- Drop temp tables if exist
 
SET NOCOUNT ON;
DROP TABLE IF EXISTS #MyPairings, #Demand, #Supply;
GO
 
WITH D0 AS
 
-- D0 computes running demand as EndDemand
 
(
  SELECT ID, Quantity,
    SUM(Quantity) OVER(ORDER BY ID ROWS UNBOUNDED PRECEDING) AS EndDemand
  FROM dbo.Auctions
  WHERE Code = 'D'
),
 
-- D extracts prev EndDemand as StartDemand, expressing start-end demand as an interval
 
D AS
(
  SELECT ID, Quantity, EndDemand - Quantity AS StartDemand, EndDemand
  FROM D0
)
SELECT ID, 
  CAST(ISNULL(StartDemand, 0.0) AS DECIMAL(19, 6)) AS StartDemand,
  CAST(ISNULL(EndDemand,   0.0) AS DECIMAL(19, 6)) AS EndDemand
INTO #Demand
FROM D;
 
WITH S0 AS
 
-- S0 computes running supply as EndSupply
 
(
  SELECT ID, Quantity,
    SUM(Quantity) OVER(ORDER BY ID ROWS UNBOUNDED PRECEDING) AS EndSupply
  FROM dbo.Auctions
  WHERE Code = 'S'
),
 
-- S extracts prev EndSupply as StartSupply, expressing start-end supply as an interval
 
S AS
(
  SELECT ID, Quantity, EndSupply - Quantity AS StartSupply, EndSupply
  FROM S0
)
SELECT ID, 
  CAST(ISNULL(StartSupply, 0.0) AS DECIMAL(19, 6)) AS StartSupply,
  CAST(ISNULL(EndSupply,   0.0) AS DECIMAL(19, 6)) AS EndSupply
INTO #Supply
FROM S;
 
-- Indexing
 
CREATE UNIQUE CLUSTERED INDEX idx_cl_SD_ID ON #Demand(StartDemand, ID);
CREATE UNIQUE CLUSTERED INDEX idx_cl_SS_ID ON #Supply(StartSupply, ID);

Planerne for at udfylde de midlertidige tabeller og oprette indekserne er vist i figur 5.


Figur 5:Forespørgselsplaner til udfyldning af midlertidige tabeller og oprettelse af indekser

Ingen overraskelser her.

Nu til den sidste forespørgsel. Du kan blive fristet til at bruge en enkelt forespørgsel med den førnævnte adskillelse af to prædikater, som sådan:

SELECT
  D.ID AS DemandID, S.ID AS SupplyID,
  CASE WHEN EndDemand < EndSupply THEN EndDemand ELSE EndSupply END - CASE WHEN StartDemand > StartSupply THEN StartDemand ELSE StartSupply END
    AS TradeQuantity
INTO #MyPairings
FROM #Demand AS D
  INNER JOIN #Supply AS S
    ON (D.StartDemand >= S.StartSupply AND D.StartDemand < S.EndSupply) OR (S.StartSupply >  D.StartDemand AND S.StartSupply < D.EndDemand);

Planen for denne forespørgsel er vist i figur 6.


Figur 6:Forespørgselsplan for endelig forespørgsel ved hjælp af revideret kryds test, prøv 1

Problemet er, at optimeringsværktøjet ikke ved, hvordan man deler denne logik op i to separate aktiviteter, der hver håndterer et andet prædikat og udnytter det respektive indeks effektivt. Så det ender med et fuldt kartesisk produkt af de to sider, der anvender alle prædikater som resterende prædikater. Med 200.000 efterspørgselsrækker og 200.000 forsyningsrækker behandler joinforbindelsen 40.000.000.000 rækker.

Det indsigtsfulde trick, som Luca, Kamil og muligvis Daniel brugte, var at opdele logikken i to forespørgsler, der forenede deres resultater. Det er her, at det bliver vigtigt at bruge to usammenhængende prædikater! Du kan bruge en UNION ALL-operatør i stedet for UNION, så du undgår de unødvendige omkostninger ved at lede efter dubletter. Her er forespørgslen, der implementerer denne tilgang:

SELECT
  D.ID AS DemandID, S.ID AS SupplyID,
  CASE WHEN EndDemand < EndSupply THEN EndDemand ELSE EndSupply END - CASE WHEN StartDemand > StartSupply THEN StartDemand ELSE StartSupply END
    AS TradeQuantity
INTO #MyPairings
FROM #Demand AS D
  INNER JOIN #Supply AS S
    ON D.StartDemand >= S.StartSupply AND D.StartDemand < S.EndSupply
 
UNION ALL
 
SELECT
  D.ID AS DemandID, S.ID AS SupplyID,
  CASE WHEN EndDemand < EndSupply THEN EndDemand ELSE EndSupply END - CASE WHEN StartDemand > StartSupply THEN StartDemand ELSE StartSupply END
    AS TradeQuantity
FROM #Demand AS D
  INNER JOIN #Supply AS S
    ON S.StartSupply > D.StartDemand AND S.StartSupply < D.EndDemand;

Planen for denne forespørgsel er vist i figur 7.


Figur 7:Forespørgselsplan for endelig forespørgsel ved hjælp af revideret kryds test, prøv 2

Det her er bare smukt! er det ikke? Og fordi det er så smukt, er her hele løsningen fra bunden, inklusive oprettelse af midlertidige tabeller, indeksering og den endelige forespørgsel:

-- Drop temp tables if exist
 
SET NOCOUNT ON;
DROP TABLE IF EXISTS #MyPairings, #Demand, #Supply;
GO
 
WITH D0 AS
 
-- D0 computes running demand as EndDemand
 
(
  SELECT ID, Quantity,
    SUM(Quantity) OVER(ORDER BY ID ROWS UNBOUNDED PRECEDING) AS EndDemand
  FROM dbo.Auctions
  WHERE Code = 'D'
),
 
-- D extracts prev EndDemand as StartDemand, expressing start-end demand as an interval
 
D AS
(
  SELECT ID, Quantity, EndDemand - Quantity AS StartDemand, EndDemand
  FROM D0
)
SELECT ID, 
  CAST(ISNULL(StartDemand, 0.0) AS DECIMAL(19, 6)) AS StartDemand,
  CAST(ISNULL(EndDemand,   0.0) AS DECIMAL(19, 6)) AS EndDemand
INTO #Demand
FROM D;
 
WITH S0 AS
 
-- S0 computes running supply as EndSupply
 
(
  SELECT ID, Quantity,
    SUM(Quantity) OVER(ORDER BY ID ROWS UNBOUNDED PRECEDING) AS EndSupply
  FROM dbo.Auctions
  WHERE Code = 'S'
),
 
-- S extracts prev EndSupply as StartSupply, expressing start-end supply as an interval
 
S AS
(
  SELECT ID, Quantity, EndSupply - Quantity AS StartSupply, EndSupply
  FROM S0
)
SELECT ID,
  CAST(ISNULL(StartSupply, 0.0) AS DECIMAL(19, 6)) AS StartSupply,
  CAST(ISNULL(EndSupply,   0.0) AS DECIMAL(19, 6)) AS EndSupply
INTO #Supply
FROM S;
 
-- Indexing
 
CREATE UNIQUE CLUSTERED INDEX idx_cl_SD_ID ON #Demand(StartDemand, ID);
CREATE UNIQUE CLUSTERED INDEX idx_cl_SS_ID ON #Supply(StartSupply, ID);
 
-- Trades are the overlapping segments of the intersecting intervals
 
SELECT
  D.ID AS DemandID, S.ID AS SupplyID,
  CASE WHEN EndDemand < EndSupply THEN EndDemand ELSE EndSupply END - CASE WHEN StartDemand > StartSupply THEN StartDemand ELSE StartSupply END
    AS TradeQuantity
INTO #MyPairings
FROM #Demand AS D
  INNER JOIN #Supply AS S
    ON D.StartDemand >= S.StartSupply AND D.StartDemand < S.EndSupply
 
UNION ALL
 
SELECT
  D.ID AS DemandID, S.ID AS SupplyID,
  CASE WHEN EndDemand < EndSupply THEN EndDemand ELSE EndSupply END - CASE WHEN StartDemand > StartSupply THEN StartDemand ELSE StartSupply END
    AS TradeQuantity
FROM #Demand AS D
  INNER JOIN #Supply AS S
    ON S.StartSupply > D.StartDemand AND S.StartSupply < D.EndDemand;

Som tidligere nævnt vil jeg referere til denne løsning som reviderede vejkryds .

Kamils ​​løsning

Mellem Lucas, Kamils ​​og Daniels løsninger var Kamils ​​hurtigste. Her er Kamils ​​komplette løsning:

SET NOCOUNT ON;
DROP TABLE IF EXISTS #supply, #demand;
GO
 
CREATE TABLE #demand
(
  DemandID           INT NOT NULL,
  DemandQuantity     DECIMAL(19, 6) NOT NULL,
  QuantityFromDemand DECIMAL(19, 6) NOT NULL,
  QuantityToDemand   DECIMAL(19, 6) NOT NULL
);
 
CREATE TABLE #supply
(
  SupplyID           INT NOT NULL,
  QuantityFromSupply DECIMAL(19, 6) NOT NULL,
  QuantityToSupply   DECIMAL(19,6) NOT NULL
);
 
WITH demand AS
(
  SELECT
    a.ID AS DemandID,
    a.Quantity AS DemandQuantity,
    SUM(a.Quantity) OVER(ORDER BY ID ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) 
      - a.Quantity AS QuantityFromDemand,
    SUM(a.Quantity) OVER(ORDER BY ID ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) 
      AS QuantityToDemand
  FROM dbo.Auctions AS a WHERE Code = 'D'
)
INSERT INTO #demand
(
  DemandID,
  DemandQuantity,
  QuantityFromDemand,
  QuantityToDemand
)
SELECT
  d.DemandID,
  d.DemandQuantity,
  d.QuantityFromDemand,
  d.QuantityToDemand
FROM demand AS d;
 
WITH supply AS
(
  SELECT
    a.ID AS SupplyID,
    SUM(a.Quantity) OVER(ORDER BY ID ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) 
      - a.Quantity AS QuantityFromSupply,
    SUM(a.Quantity) OVER(ORDER BY ID ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) 
      AS QuantityToSupply
  FROM dbo.Auctions AS a WHERE Code = 'S'
)
INSERT INTO #supply
(
  SupplyID,
  QuantityFromSupply,
  QuantityToSupply
)
SELECT
  s.SupplyID,
  s.QuantityFromSupply,
  s.QuantityToSupply
FROM supply AS s;
 
CREATE UNIQUE INDEX ix_1 ON #demand(QuantityFromDemand) 
  INCLUDE(DemandID, DemandQuantity, QuantityToDemand);
CREATE UNIQUE INDEX ix_1 ON #supply(QuantityFromSupply) 
  INCLUDE(SupplyID, QuantityToSupply);
CREATE NONCLUSTERED COLUMNSTORE INDEX nci ON #demand(DemandID) 
  WHERE DemandID is null;
 
DROP TABLE IF EXISTS #myPairings;
 
CREATE TABLE #myPairings
(
  DemandID      INT NOT NULL,
  SupplyID      INT NOT NULL,
  TradeQuantity DECIMAL(19, 6) NOT NULL
);
 
INSERT INTO #myPairings(DemandID, SupplyID, TradeQuantity) 
  SELECT
    d.DemandID,
    s.SupplyID, 
    d.DemandQuantity
      - CASE WHEN d.QuantityFromDemand < s.QuantityFromSupply 
             THEN s.QuantityFromSupply - d.QuantityFromDemand ELSE 0 end
      - CASE WHEN s.QuantityToSupply < d.QuantityToDemand 
             THEN d.QuantityToDemand - s.QuantityToSupply ELSE 0 END AS TradeQuantity 
  FROM #demand AS d 
    INNER JOIN #supply AS s 
      ON (d.QuantityFromDemand < s.QuantityToSupply 
          AND s.QuantityFromSupply <= d.QuantityFromDemand)
 
  UNION ALL
 
  SELECT
    d.DemandID,
    s.SupplyID,
    d.DemandQuantity
      - CASE WHEN d.QuantityFromDemand < s.QuantityFromSupply 
             THEN s.QuantityFromSupply - d.QuantityFromDemand ELSE 0 END
      - CASE WHEN s.QuantityToSupply < d.QuantityToDemand 
             THEN d.QuantityToDemand - s.QuantityToSupply ELSE 0 END AS TradeQuantity
  FROM #supply AS s 
    INNER JOIN #demand AS d 
      ON (s.QuantityFromSupply < d.QuantityToDemand 
          AND d.QuantityFromDemand < s.QuantityFromSupply);

Som du kan se, er det meget tæt på den reviderede vejkrydsløsning, jeg dækkede.

Planen for den endelige forespørgsel i Kamils ​​løsning er vist i figur 8.


Figur 8:Forespørgselsplan for endelig forespørgsel i Kamils ​​løsning

Planen ligner ret meget den, der er vist tidligere i figur 7.

Ydeevnetest

Husk, at den klassiske vejkryds-løsning tog 931 sekunder at fuldføre mod et input med 400K rækker. Det er 15 minutter! Det er meget, meget værre end markørløsningen, som tog omkring 12 sekunder at fuldføre mod det samme input. Figur 9 har ydelsestallene inklusive de nye løsninger, der er diskuteret i denne artikel.


Figur 9:Ydelsestest

Som du kan se, tog Kamils ​​løsning og den lignende reviderede vejkrydsløsning omkring 1,5 sekunder at fuldføre mod input fra 400K-rækkerne. Det er en ret anstændig forbedring sammenlignet med den originale markørbaserede løsning. Den største ulempe ved disse løsninger er I/O-omkostningerne. Med et søgning pr. række, for et input på 400.000 rækker, udfører disse løsninger et overskud på 1,35 millioner aflæsninger. Men det kunne også betragtes som en helt acceptabel pris i betragtning af den gode køretid og skalering, du får.

Hvad er det næste?

Vores første forsøg på at løse den matchende udbud versus efterspørgsel-udfordring var baseret på den klassiske interval skæringstest og fik en dårligt ydende plan med kvadratisk skalering. Meget værre end den markørbaserede løsning. Baseret på indsigt fra Luca, Kamil og Daniel, ved hjælp af en revideret intervalskæringstest, var vores andet forsøg en væsentlig forbedring, der udnytter indeksering effektivt og yder bedre end den markørbaserede løsning. Denne løsning indebærer dog en betydelig I/O-omkostning. Næste måned fortsætter jeg med at udforske yderligere løsninger.

[ Hop til:Original udfordring | Løsninger:Del 1 | Del 2 | del 3]
  1. Hvordan afinstallerer/fjerner jeg Oracle 11g (klient)?

  2. Indsæt DML med bindingsvariabel:BRUGER Klausul af Udfør øjeblikkelig erklæring

  3. Hvordan kalder man Oracle Function i Python?

  4. Multiple REPLACE-funktion i Oracle