sql >> Database >  >> RDS >> Sqlserver

Voorwaardelijke aggregatieprestaties

Korte samenvatting

  • De prestaties van de methode voor subquery's zijn afhankelijk van de gegevensdistributie.
  • De prestaties van voorwaardelijke aggregatie zijn niet afhankelijk van de gegevensdistributie.

De methode voor subquery's kan sneller of langzamer zijn dan voorwaardelijke aggregatie, dit hangt af van de gegevensdistributie.

Natuurlijk, als de tabel een geschikte index heeft, zullen subquery's er waarschijnlijk van profiteren, omdat index het mogelijk zou maken om alleen het relevante deel van de tabel te scannen in plaats van de volledige scan. Het is onwaarschijnlijk dat het hebben van een geschikte index de voorwaardelijke aggregatiemethode aanzienlijk ten goede zal komen, omdat het toch de volledige index zal scannen. Het enige voordeel zou zijn als de index smaller is dan de tabel en de engine minder pagina's in het geheugen hoeft in te lezen.

Als u dit weet, kunt u beslissen welke methode u kiest.

Eerste test

Ik heb een grotere testtabel gemaakt, met 5M rijen. Er waren geen indexen op de tafel. Ik heb de IO- en CPU-statistieken gemeten met SQL Sentry Plan Explorer. Ik gebruikte SQL Server 2014 SP1-CU7 (12.0.4459.0) Express 64-bit voor deze tests.

Inderdaad, uw oorspronkelijke zoekopdrachten gedroegen zich zoals u beschreef, d.w.z. subquery's waren sneller, ook al waren de uitlezingen 3 keer hoger.

Na een paar pogingen op een tabel zonder index heb ik je voorwaardelijke aggregaat herschreven en variabelen toegevoegd om de waarde van DATEADD vast te houden uitdrukkingen.

De totale tijd werd aanzienlijk sneller.

Daarna heb ik SUM vervangen met COUNT en het werd weer een beetje sneller.

Voorwaardelijke aggregatie werd immers vrijwel net zo snel als subquery's.

Verwarm de cache (CPU=375)

SELECT -- warm cache
    COUNT(*) AS all_cnt
FROM LogTable
OPTION (RECOMPILE);

Subquery's (CPU=1031)

SELECT -- subqueries
(
    SELECT count(*) FROM LogTable 
) all_cnt, 
(
    SELECT count(*) FROM LogTable WHERE datesent > DATEADD(year,-1,GETDATE())
) last_year_cnt,
(
    SELECT count(*) FROM LogTable WHERE datesent > DATEADD(year,-10,GETDATE())
) last_ten_year_cnt
OPTION (RECOMPILE);

Oorspronkelijke voorwaardelijke aggregatie (CPU=1641)

SELECT -- conditional original
    COUNT(*) AS all_cnt,
    SUM(CASE WHEN datesent > DATEADD(year,-1,GETDATE())
             THEN 1 ELSE 0 END) AS last_year_cnt,
    SUM(CASE WHEN datesent > DATEADD(year,-10,GETDATE())
             THEN 1 ELSE 0 END) AS last_ten_year_cnt
FROM LogTable
OPTION (RECOMPILE);

Voorwaardelijke aggregatie met variabelen (CPU=1078)

DECLARE @VarYear1 datetime = DATEADD(year,-1,GETDATE());
DECLARE @VarYear10 datetime = DATEADD(year,-10,GETDATE());

SELECT -- conditional variables
    COUNT(*) AS all_cnt,
    SUM(CASE WHEN datesent > @VarYear1
             THEN 1 ELSE 0 END) AS last_year_cnt,
    SUM(CASE WHEN datesent > @VarYear10
             THEN 1 ELSE 0 END) AS last_ten_year_cnt
FROM LogTable
OPTION (RECOMPILE);

Voorwaardelijke aggregatie met variabelen en COUNT in plaats van SUM (CPU=1062)

SELECT -- conditional variable, count, not sum
    COUNT(*) AS all_cnt,
    COUNT(CASE WHEN datesent > @VarYear1
             THEN 1 ELSE NULL END) AS last_year_cnt,
    COUNT(CASE WHEN datesent > @VarYear10
             THEN 1 ELSE NULL END) AS last_ten_year_cnt
FROM LogTable
OPTION (RECOMPILE);

Op basis van deze resultaten vermoed ik dat CASE aangeroepen DATEADD voor elke rij, terwijl WHERE was slim genoeg om het een keer uit te rekenen. Plus COUNT is een klein beetje efficiënter dan SUM .

Uiteindelijk is voorwaardelijke aggregatie slechts iets langzamer dan subquery's (1062 versus 1031), misschien omdat WHERE is een beetje efficiënter dan CASE op zichzelf, en bovendien, WHERE filtert nogal wat rijen uit, dus COUNT hoeft minder rijen te verwerken.

In de praktijk zou ik voorwaardelijke aggregatie gebruiken, omdat ik dat aantal reads belangrijker vind. Als uw tabel klein is om in de bufferpool te passen en te blijven, zal elke vraag snel zijn voor de eindgebruiker. Maar als de tabel groter is dan het beschikbare geheugen, verwacht ik dat het lezen van de schijf subquery's aanzienlijk zou vertragen.

Tweede test

Aan de andere kant is het ook belangrijk om de rijen zo vroeg mogelijk uit te filteren.

Hier is een kleine variatie van de test, die het aantoont. Hier stel ik de drempel in op GETDATE() + 100 jaar, om er zeker van te zijn dat geen enkele rij aan de filtercriteria voldoet.

Verwarm de cache (CPU=344)

SELECT -- warm cache
    COUNT(*) AS all_cnt
FROM LogTable
OPTION (RECOMPILE);

Subquery's (CPU=500)

SELECT -- subqueries
(
    SELECT count(*) FROM LogTable 
) all_cnt, 
(
    SELECT count(*) FROM LogTable WHERE datesent > DATEADD(year,100,GETDATE())
) last_year_cnt
OPTION (RECOMPILE);

Oorspronkelijke voorwaardelijke aggregatie (CPU=937)

SELECT -- conditional original
    COUNT(*) AS all_cnt,
    SUM(CASE WHEN datesent > DATEADD(year,100,GETDATE())
             THEN 1 ELSE 0 END) AS last_ten_year_cnt
FROM LogTable
OPTION (RECOMPILE);

Voorwaardelijke aggregatie met variabelen (CPU=750)

DECLARE @VarYear100 datetime = DATEADD(year,100,GETDATE());

SELECT -- conditional variables
    COUNT(*) AS all_cnt,
    SUM(CASE WHEN datesent > @VarYear100
             THEN 1 ELSE 0 END) AS last_ten_year_cnt
FROM LogTable
OPTION (RECOMPILE);

Voorwaardelijke aggregatie met variabelen en COUNT in plaats van SUM (CPU=750)

SELECT -- conditional variable, count, not sum
    COUNT(*) AS all_cnt,
    COUNT(CASE WHEN datesent > @VarYear100
             THEN 1 ELSE NULL END) AS last_ten_year_cnt
FROM LogTable
OPTION (RECOMPILE);

Hieronder staat een plan met subquery's. Je kunt zien dat er 0 rijen in de Stream Aggregate zijn gegaan in de tweede subquery, ze zijn allemaal uitgefilterd bij de stap Tabelscan.

Als gevolg hiervan zijn subquery's weer sneller.

Derde toets

Hier heb ik de filtercriteria van de vorige test gewijzigd:alle > werden vervangen door < . Als gevolg hiervan wordt de voorwaardelijke COUNT telde alle rijen in plaats van geen. Verrassing, verrassing! Voorwaardelijke aggregatiequery duurde dezelfde 750 ms, terwijl subquery's 813 werden in plaats van 500.

Hier is het plan voor subquery's:

Kunt u mij een voorbeeld geven waarbij voorwaardelijke aggregatie met name beter presteert dan de subquery-oplossing?

Hier is het. De prestaties van de subquery-methode zijn afhankelijk van de gegevensdistributie. De prestaties van voorwaardelijke aggregatie zijn niet afhankelijk van de gegevensdistributie.

De methode voor subquery's kan sneller of langzamer zijn dan voorwaardelijke aggregatie, dit hangt af van de gegevensdistributie.

Als u dit weet, kunt u beslissen welke methode u kiest.

Bonusdetails

Als u met de muis over de Table Scan . gaat operator kunt u de Actual Data Size . zien in verschillende varianten.

  1. Eenvoudig COUNT(*) :

  1. Voorwaardelijke aggregatie:

  1. Subquery in test 2:

  1. Subquery in test 3:

Nu wordt duidelijk dat het verschil in prestaties waarschijnlijk wordt veroorzaakt door het verschil in de hoeveelheid gegevens die door het plan stroomt.

In het geval van eenvoudige COUNT(*) er is geen Output list (er zijn geen kolomwaarden nodig) en de gegevensgrootte is het kleinst (43 MB).

Bij voorwaardelijke aggregatie verandert dit bedrag niet tussen test 2 en 3, het is altijd 72MB. Output list heeft één kolom datesent .

In het geval van subquery's, is dit aantal wel veranderen afhankelijk van de gegevensdistributie.



  1. Load balancing met ProxySQL voor Percona XtraDB Cluster

  2. Kies uit de tabel door alleen de datum zonder tijd te kennen (ORACLE)

  3. Stop (lang)lopende SQL-query in PostgreSQL wanneer sessie of verzoeken niet meer bestaan?

  4. 2 manieren om dubbele rijen in Oracle te verwijderen