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

Forespørgselsbutik:Viser indvirkningen af ​​indekser på indstik

Introduktion

Det er almindeligt kendt i databasekredse, at indekser forbedrer forespørgselsydeevne enten ved at opfylde det krævede resultatsæt fuldstændigt (Covering Indexes) eller ved at fungere som opslag, der nemt dirigerer forespørgselsmotoren til den nøjagtige placering af det påkrævede datasæt. Men som erfarne DBA'er ved, bør man ikke være for begejstret for at skabe indekser i OLTP-miljøer uden at forstå arten af ​​arbejdsbyrden. Ved at bruge Query Store i SQL Server 2019-instansen (Query Store blev introduceret i SQL Server 2016), er det ret nemt at vise effekten af ​​et indeks på inserts.

Indsæt uden indeks

Vi starter med at gendanne WideWorldImporters Sample-databasen og derefter oprette en kopi af Salget. Fakturatabel ved hjælp af scriptet i liste 1. Bemærk, at prøvedatabasen allerede har aktiveret Query Store i læse-skrivetilstand.

-- Listing 1 Make a Copy Of Invoices
SELECT * 
INTO [SALES].[INVOICES1] 
FROM [SALES].[INVOICES]  
WHERE 1=2;

Bemærk, at der slet ikke er nogen indekser i den tabel, vi lige har oprettet. Det eneste, vi har, er bordstrukturen. Når det er gjort, udfører vi indsættelser i den nye tabel ved hjælp af data fra dens overordnede som vist i liste 2.

-- Listing 2 Populate Invoices1
-- TRUNCATE TABLE [SALES].[INVOICES1]
INSERT INTO [SALES].[INVOICES1] 
SELECT * FROM [SALES].[INVOICES]; 
GO 100

Under denne operation fanger Query Store forespørgslens udførelsesplan. Figur 1 viser kort, hvad der sker under emhætten. Når vi læser fra venstre mod højre, ser vi, at SQL Server udfører indsættelserne ved hjælp af Plan ID 563 – en indeksscanning på kildetabellens primære nøgle for at hente dataene og derefter en tabelindsættelse på destinationstabellen. (Læser fra venstre mod højre). Bemærk, at i dette tilfælde er hovedparten af ​​omkostningerne på tabelindlægget – 99 % af forespørgselsomkostningerne.

Fig. 1 Udførelsesplan 563

Fig. 2 Tabel Indsæt på destination

Fig. 3 Clustered Index Scan på kildetabel

Indsæt med indeks

Vi opretter derefter et indeks på destinationstabellen ved hjælp af DDL i Listing 3. Når vi gentager sætningen i Listing 2 efter at have trunkeret destinationstabellen, ser vi en lidt anderledes eksekveringsplan (Plan ID 593 vist i Fig. 4). Vi ser stadig tabelindsættelsen, men den bidrager kun med 58 % til prisen for forespørgslen. Udførelsesdynamikken er lidt skæv med introduktionen af ​​en sortering og en Index Insert. Det, der i bund og grund sker, er, at SQL Server skal indføre tilsvarende rækker på indekset, efterhånden som nye poster introduceres i tabellen.

-- LISTING 3 Create Index on Destination Table
CREATE NONCLUSTERED INDEX [IX_Sales_Invoices_ConfirmedDeliveryTime] ON [Sales].[Invoices1]
(
	[ConfirmedDeliveryTime] ASC
)
INCLUDE ( 	[ConfirmedReceivedBy]) 
WITH (PAD_INDEX = OFF
, STATISTICS_NORECOMPUTE = OFF
, SORT_IN_TEMPDB = OFF
, DROP_EXISTING = OFF
, ONLINE = OFF
, ALLOW_ROW_LOCKS = ON
, ALLOW_PAGE_LOCKS = ON) ON [USERDATA]
GO

Fig. 4 Udførelsesplan 593

Ser dybere

Vi kan undersøge detaljerne i begge planer og se, hvordan disse nye faktorer eskalerer udførelsestiden for erklæringen. Plan 593 tilføjer yderligere 300 ms eller deromkring til den gennemsnitlige varighed af erklæringen. Under stor arbejdsbyrde i et produktionsmiljø kan denne forskel være betydelig.

At slå STATISTICS IO til, når der kun udføres insert-sætningen én gang i begge tilfælde - med Index på destinationstabel og uden et indeks på Destination table - viser også, at der arbejdes mere med hensyn til logisk IO, når der indsættes rækker i en tabel med indekser.

Fig. 5 Detaljer om udførelsesplan 563

Fig. 4 Detaljer om udførelsesplan 593

Intet indeks:Output med STATISTICS IO slået til:

Tabel 'Fakturaer1'. Scanningsantal 0, logisk læser 78372 , fysisk læser 0, read-ahead læser 0, lob logisk læser 0, lob fysisk læser 0, lob read-ahead læser 0.

Tabel 'Fakturaer'. Scanningsantal 1, logisk læser 11400, fysisk læser 0, read-ahead læser 0, lob logisk læser 0, lob fysisk læser 0, lob read-ahead læser 0.

(70510 rækker påvirket)

Indeks:Output med STATISTICS IO slået til:

Tabel 'Fakturaer1'. Scanningsantal 0, logisk læser 81119 , fysisk læser 0, read-ahead læser 0, lob logisk læser 0, lob fysisk læser 0, lob read-ahead læser 0.

Tabel 'Arbejdsbord'. Scanningsantal 0, logisk læser 0, fysisk læser 0, read-ahead læser 0, lob logisk læser 0, lob fysisk læser 0, lob read-ahead læser 0.

Tabel 'Fakturaer'. Scanningsantal 1, logisk læser 11400 , fysisk læser 0, read-ahead læser 0, lob logisk læser 0, lob fysisk læser 0, lob read-ahead læser 0.

(70510 rækker påvirket)

Yderligere oplysninger

Microsoft og andre kilder leverer scripts til at undersøge produktionsmiljøet for indekser og identificere sådanne situationer som:

  1. Redundante indekser – Indekser, der er duplikeret
  2. Manglende indekser – Indekser, der kunne forbedre ydeevnen baseret på arbejdsbelastning
  3. Dynge – Tabeller uden grupperede indekser
  4. Overindekserede tabeller – Tabeller med flere indeks end kolonner
  5. Indeksbrug – Optælling af søgninger, scanninger og opslag på indekser

Punkt 2, 3 og 5 er mere relateret til præstationspåvirkning med hensyn til læsninger, mens punkt 1 og 4 er relateret til præstationspåvirkning med hensyn til skrivninger. Liste 4 og 5 er to eksempler på disse offentligt tilgængelige forespørgsler.

-- LISTING 4 Check Redundant Indexes
;WITH INDEXCOLUMNS AS(
SELECT DISTINCT
SCHEMA_NAME (O.SCHEMA_ID) AS 'SCHEMANAME'
, OBJECT_NAME(O.OBJECT_ID) AS TABLENAME
,I.NAME AS INDEXNAME, O.OBJECT_ID,I.INDEX_ID,I.TYPE
,(SELECT CASE KEY_ORDINAL WHEN 0 THEN NULL ELSE '['+COL_NAME(K.OBJECT_ID,COLUMN_ID) +']' END AS [DATA()]
FROM SYS.INDEX_COLUMNS AS K WHERE K.OBJECT_ID = I.OBJECT_ID AND K.INDEX_ID = I.INDEX_ID
ORDER BY KEY_ORDINAL, COLUMN_ID FOR XML PATH('')) AS COLS
FROM SYS.INDEXES AS I INNER JOIN SYS.OBJECTS O ON I.OBJECT_ID =O.OBJECT_ID 
INNER JOIN SYS.INDEX_COLUMNS IC ON IC.OBJECT_ID =I.OBJECT_ID AND IC.INDEX_ID =I.INDEX_ID
INNER JOIN SYS.COLUMNS C ON C.OBJECT_ID = IC.OBJECT_ID AND C.COLUMN_ID = IC.COLUMN_ID
WHERE I.OBJECT_ID IN (SELECT OBJECT_ID FROM SYS.OBJECTS WHERE TYPE ='U') AND I.INDEX_ID <>0 AND I.TYPE <>3 AND I.TYPE <>6
GROUP BY O.SCHEMA_ID,O.OBJECT_ID,I.OBJECT_ID,I.NAME,I.INDEX_ID,I.TYPE
) 

SELECT 
IC1.SCHEMANAME,IC1.TABLENAME,IC1.INDEXNAME,IC1.COLS AS INDEXCOLS,IC2.INDEXNAME AS REDUNDANTINDEXNAME, IC2.COLS AS REDUNDANTINDEXCOLS
FROM INDEXCOLUMNS IC1
JOIN INDEXCOLUMNS IC2 ON IC1.OBJECT_ID = IC2.OBJECT_ID
AND IC1.INDEX_ID <> IC2.INDEX_ID
AND IC1.COLS <> IC2.COLS
AND IC2.COLS LIKE REPLACE(IC1.COLS,'[','[[]') + ' %'
ORDER BY 1,2,3,5;

-- LISTING 5 Check Indexes Usage
SELECT O.NAME AS TABLE_NAME
, I.NAME AS INDEX_NAME
, S.USER_SEEKS
, S.USER_SCANS
, S.USER_LOOKUPS
, S.USER_UPDATES
FROM SYS.DM_DB_INDEX_USAGE_STATS S
INNER JOIN SYS.INDEXES I
ON I.INDEX_ID=S.INDEX_ID
AND S.OBJECT_ID = I.OBJECT_ID
INNER JOIN SYS.OBJECTS O
ON S.OBJECT_ID = O.OBJECT_ID
INNER JOIN SYS.SCHEMAS C
ON O.SCHEMA_ID = C.SCHEMA_ID;

Konklusion

Vi har vist, ved hjælp af Query Store, at yderligere arbejdsbyrde med et indeks kan introducere i eksekveringsplanen for en eksempelindsættelseserklæring. I produktionen kan overdrevne og redundante indekser have en negativ indvirkning på ydeevnen, især i databaser beregnet til OLTP-arbejdsbelastninger. Det er vigtigt at bruge tilgængelige scripts og værktøjer til at undersøge indekser og afgøre, om de faktisk hjælper eller skader ydeevnen.

Nyttigt værktøj:

dbForge Index Manager – praktisk SSMS-tilføjelse til at analysere status for SQL-indekser og løse problemer med indeksfragmentering.


  1. DBCC_OBJECT_METADATA-låsen

  2. 7 måder at finde dublerede rækker, mens du ignorerer den primære nøgle i MySQL

  3. Hvordan udtrækker man gruppe fra regulært udtryk i Oracle?

  4. Multi-sætning tabel værdisat funktion vs inline tabel værdisat funktion