sql >> Database >  >> RDS >> Sqlserver

Interne SQL Server:problematische operators Pt. II – Hashing

Dit maakt deel uit van een serie SQL Server Internals Problematic Operators. Klik hier om het eerste bericht te lezen.

SQL Server bestaat al meer dan 30 jaar en ik werk al bijna net zo lang met SQL Server. Ik heb in de loop der jaren (en decennia!) en versies van dit ongelooflijke product veel veranderingen gezien. In deze berichten zal ik met u delen hoe ik kijk naar enkele van de functies of aspecten van SQL Server, soms samen met een beetje historisch perspectief.

De vorige keer had ik het over een scanbewerking in een SQL Server-queryplan als een mogelijk problematische operator in SQL Server-diagnostixs. Hoewel scans vaak alleen worden gebruikt omdat er geen bruikbare index is, zijn er momenten waarop de scan eigenlijk een betere keuze is dan een indexzoekbewerking.

In dit artikel zal ik je vertellen over een andere familie van operators die af en toe als problematisch wordt gezien:hashing. Hashing is een zeer bekend algoritme voor gegevensverwerking dat al tientallen jaren bestaat. Ik heb het in mijn datastructuren-lessen lang geleden bestudeerd toen ik voor het eerst informatica aan de universiteit studeerde. Als je achtergrondinformatie wilt over hashing en hash-functies, kun je dit artikel op Wikipedia lezen. SQL Server heeft echter pas in SQL Server 7 hashing aan zijn repertoire van queryverwerkingsopties toegevoegd. (Terzijde, ik zal vermelden dat SQL Server hashing gebruikte in sommige van zijn eigen interne zoekalgoritmen. Zoals het Wikipedia-artikel vermeldt , hashing gebruikt een speciale functie om gegevens van willekeurige grootte toe te wijzen aan gegevens van een vaste grootte. SQL gebruikte hashing als zoektechniek om elke pagina van een database van willekeurige grootte toe te wijzen aan een buffer in het geheugen, wat een vaste grootte is. , was er een optie voor sp_configure genaamd 'hash-buckets', waarmee u het aantal buckets kunt bepalen dat wordt gebruikt voor het hashen van databasepagina's in geheugenbuffers.)

Wat is hashen?

Hashing is een zoektechniek waarbij de gegevens niet geordend hoeven te worden. SQL Server kan het gebruiken voor JOIN-bewerkingen, aggregatiebewerkingen (DISTINCT of GROUP BY) of UNION-bewerkingen. Wat deze drie bewerkingen gemeen hebben, is dat de query-engine tijdens de uitvoering op zoek is naar overeenkomende waarden. In een JOIN willen we rijen vinden in de ene tabel (of rijenset) die overeenkomende waarden hebben met rijen in een andere. (En ja, ik ben me bewust van joins die geen rijen vergelijken op basis van gelijkheid, maar die niet-equijoins zijn niet relevant voor deze discussie.) Voor GROUP BY vinden we overeenkomende waarden om in dezelfde groep op te nemen, en voor UNION en DISTINCT zoeken we naar overeenkomende waarden om ze uit te sluiten. (Ja, ik weet dat UNION ALL een uitzondering is.)

Vóór SQL Server 7 konden deze bewerkingen alleen gemakkelijk overeenkomende waarden vinden als de gegevens waren gesorteerd. Dus als er geen bestaande index was die de gegevens in gesorteerde volgorde bijhield, zou het queryplan een SORT-bewerking aan het plan toevoegen. Hashing organiseert uw gegevens voor efficiënt zoeken door alle rijen met hetzelfde resultaat van de interne hashfunctie in dezelfde 'hash-bucket' te plaatsen.

Voor een meer gedetailleerde uitleg van de hash JOIN-bewerking van SQL Server, inclusief diagrammen, bekijk deze blogpost van SQL Shack.

Zodra hashing een optie werd, negeerde SQL Server de mogelijkheid om gegevens te sorteren voordat ze worden samengevoegd of samengevoegd, maar het werd gewoon een mogelijkheid voor de optimizer om te overwegen. Als u echter probeert om UNION te joinen, samen te voegen of uit te voeren op ongesorteerde gegevens, kiest de optimizer meestal voor een hash-bewerking. Zoveel mensen gaan ervan uit dat een HASH JOIN (of een andere HASH-bewerking) in een plan betekent dat u geen geschikte indexen hebt en dat u geschikte indexen moet bouwen om de hash-bewerking te vermijden.

Laten we een voorbeeld bekijken. Ik zal eerst twee niet-geïndexeerde tabellen maken.

USE AdventureWorks2016 GO DROP TABLE IF EXISTS Details;

GO

SELECT * INTO Details FROM Sales.SalesOrderDetail;

GO

DROP TABLE IF EXISTS Headers;

GO

SELECT * INTO Headers FROM Sales.SalesOrderHeader;

GO

Now, I’ll join these two tables together and filter the rows in the Details table:

SELECT *

FROM Details d JOIN Headers h

ON d.SalesOrderID = h.SalesOrderID

WHERE SalesOrderDetailID < 100;

Quest Spotlight Tuning Pack lijkt de hash-join niet als een probleem aan te geven. Het markeert alleen de twee tabelscans.

In de suggesties wordt aanbevolen om voor elke tabel een index te bouwen die elke afzonderlijke niet-sleutelkolom als INBEGREPEN kolom bevat. Ik neem die aanbevelingen zelden op (zoals ik in mijn vorige bericht al zei). Ik bouw alleen de index op de Details tabel, op de join-kolom, en geen opgenomen kolommen hebben.

CREATE INDEX Header_index on Headers(SalesOrderID);

Zodra die index is opgebouwd, verdwijnt de HASH JOIN. De index sorteert de gegevens in de Headers tabel en stelt SQL Server in staat om de overeenkomende rijen in de binnenste tabel te vinden met behulp van de sorteervolgorde van de index. Nu is het duurste onderdeel van het plan de scan op de buitenste tafel (Details ) die kan worden verminderd door een index te bouwen op de SalesOrderID kolom in die tabel. Ik laat dat als een oefening voor de lezer.

Een plan met een HASH JOIN is echter niet altijd slecht. De alternatieve operator (behalve in speciale gevallen) is een NESTED LOOPS JOIN, en dat is meestal de keuze als er goede indexen aanwezig zijn. Een NESTED-lusbewerking vereist echter meerdere zoekopdrachten van de binnenste tabel. De volgende pseudocode toont het geneste loops join-algoritme:

for each row R1 in the outer table

     for each row R2 in the inner table

         if R1 joins with R2

             return (R1, R2)

Zoals de naam aangeeft, wordt een NESTED LOOP JOIN uitgevoerd als een geneste lus. Het doorzoeken van de binnenste tafel wordt meestal meerdere keren uitgevoerd, één keer voor elke kwalificerende rij in de buitenste tafel. Zelfs als er maar een paar procent van de rijen in aanmerking komen, als de tabel erg groot is (misschien in de honderden miljoenen, of miljarden of rijen), zullen er veel rijen zijn om te lezen. In een systeem dat I/O-gebonden is, kunnen deze miljoenen of miljarden reads een echte bottleneck zijn.

Een HASH JOIN daarentegen kan geen van beide tabellen meerdere keren lezen. Het leest de buitenste tabel eenmaal om de hash-emmers te maken, en dan leest het eenmaal de binnenste tabel, waarbij de hash-emmers worden gecontroleerd om te zien of er een overeenkomende rij is. We hebben een bovengrens van één keer door elke tafel. Ja, er zijn CPU-bronnen nodig om de hashfunctie te berekenen en de inhoud van de buckets te beheren. Er zijn geheugenbronnen nodig om de gehashte informatie op te slaan. Maar als u een I/O-gebonden systeem heeft, heeft u mogelijk geheugen- en CPU-bronnen over. HASH JOIN kan een redelijke keuze zijn voor de optimizer in deze situaties waarin uw I/O-bronnen beperkt zijn en u zich bij zeer grote tabellen voegt.

Hier is pseudocode voor het hash-join-algoritme:

for each row R1 in the build table

  begin

     calculate hash value on R1 join key(s)

     insert R1 into the appropriate hash bucket

  end

for each row R2 in the probe table

  begin

     calculate hash value on R2 join key(s)

     for each row R1 in the corresponding hash bucket

         if R1 joins with R2

         output (R1, R2)

  end

Zoals eerder vermeld, kan hashing ook worden gebruikt voor aggregatie (evenals UNION) bewerkingen. Nogmaals, als er een bruikbare index is waarin de gegevens al zijn gesorteerd, kan het groeperen van de gegevens zeer efficiënt worden gedaan. Er zijn echter ook veel situaties waarin hashen helemaal geen slechte operator is. Overweeg een query als de volgende, waarin de gegevens worden gegroepeerd in de Details tabel (hierboven gemaakt) door de ProductID kolom. Er zijn 121.317 rijen in de tabel en slechts 266 verschillende ProductID waarden.

SELECT ProductID, count(*)

FROM Details

GROUP BY ProductID;

GO

Hashing-bewerkingen gebruiken

Om hashing te gebruiken, hoeft SQL Server slechts 266 buckets te maken en te onderhouden, wat niet veel is. In feite geeft Quest Spotlight Tuning Pack niet aan dat er problemen zijn met deze vraag.

Ja, het moet een tabelscan doen, maar dat komt omdat we elke rij in de tabel moeten onderzoeken en we weten dat scans niet altijd slecht zijn. Een index zou alleen helpen bij het voorsorteren van de gegevens, maar het gebruik van hash-aggregatie voor zo'n klein aantal groepen levert meestal nog steeds redelijke prestaties op, zelfs als er geen bruikbare index beschikbaar is.

Net als tabelscans worden hash-bewerkingen vaak gezien als een 'slechte' operator om in een plan te hebben. Er zijn gevallen waarin u de prestaties aanzienlijk kunt verbeteren door nuttige indexen toe te voegen om de hash-bewerkingen te verwijderen, maar dat is niet altijd waar. En als u probeert het aantal indexen te beperken op tabellen die sterk worden bijgewerkt, moet u zich ervan bewust zijn dat hash-bewerkingen niet altijd iets zijn dat moet worden 'gefixeerd', dus het verlaten van de query om een ​​hash te gebruiken, kan redelijk zijn Te doen. Bovendien kan hashing voor bepaalde query's op grote tabellen die op I/O-gebonden systemen worden uitgevoerd, betere prestaties leveren dan alternatieve algoritmen vanwege het beperkte aantal leesbewerkingen dat moet worden uitgevoerd. De enige manier om het zeker te weten, is door verschillende mogelijkheden op uw systeem te testen, met uw vragen en uw gegevens.

In het volgende bericht in deze serie zal ik je vertellen over andere problematische operators die kunnen voorkomen in je queryplannen, dus kom snel terug!


  1. Databasekeuze voor het maken van twee gekoppelde tabellen?

  2. Database instellen en een alleen-lezen gebruiker maken in AWS Redshift en Mysql

  3. Identiteitstoename in SQL Server 2012-kolom springt van 6 naar 1000+ bij 7e invoer

  4. Fout bij het installeren van Psycopg2 op MacOS 10.9.5