sql >> Databasteknik >  >> RDS >> Database

Flera planer för en identisk fråga

Jag ser ofta människor kämpar med SQL Server när de ser två olika exekveringsplaner för vad de tror är samma fråga. Vanligtvis upptäcks detta efter andra observationer, såsom väldigt olika avrättningstider. Jag säger att de tror att det är samma fråga eftersom det ibland är det och ibland inte.

Ett av de vanligaste fallen är när de testar en fråga i SSMS och får en annan plan än den de får från sin applikation. Det finns potentiellt två faktorer som spelar in här (som också kan vara relevanta när jämförelsen INTE är mellan applikationen och SSMS):

  1. Applikationen har nästan alltid olika SET inställningar än SSMS (detta är saker som ARITHABORT , ANSI_NULLS och QUOTED_IDENTIFIER ). Detta tvingar SQL Server att lagra de två planerna separat; Erland Sommarskog har behandlat detta mycket detaljerat i sin artikel, Långsamt i applikationen, snabbt i SSMS?
  2. De parametrar som användes av applikationen när dess kopia av planen kompilerades första gången kunde ha varit mycket annorlunda och ledde till en annan plan än de som användes första gången frågan kördes från SSMS – detta kallas parametersniffning . Erland pratar om det också på djupet, och jag tänker inte upprepa hans rekommendationer, utan sammanfattar genom att påminna om att det inte alltid är användbart att testa applikationens sökfråga i SSMS, eftersom det är ganska osannolikt att det är ett äpple-till-äpple-test.

Det finns ett par andra scenarier som är lite mer oklara som jag tar upp i mitt tal om dåliga vanor och bästa praxis. Det här är fall där planerna inte är olika, men det finns flera kopior av samma plan som sväller upp planens cache. Jag tänkte att jag borde nämna dem här eftersom de alltid överraskar så många människor.

case och blanksteg är viktiga

SQL Server hashar frågetexten till ett binärt format, vilket innebär att varje enskilt tecken i frågetexten är avgörande. Låt oss ta följande enkla frågor:

USE AdventureWorks2014;
DBCC FREEPROCCACHE WITH NO_INFOMSGS;
GO
SELECT StoreID FROM Sales.Customer;
GO -- original query
GO
SELECT  StoreID FROM Sales.Customer;
GO ----^---- extra space
GO
SELECT storeid FROM sales.customer;
GO ---- lower case names
GO
select StoreID from Sales.Customer;
GO ---- lower case keywords
GO

Dessa genererar exakt samma resultat, uppenbarligen, och genererar exakt samma plan. Men om vi tittar på vad vi har i plancachen:

SELECT t.[text], p.size_in_bytes, p.usecounts
 FROM sys.dm_exec_cached_plans AS p
 CROSS APPLY sys.dm_exec_sql_text(p.plan_handle) AS t
 WHERE LOWER(t.[text]) LIKE N'%sales'+'.'+'customer%';

Resultaten är olyckliga:

Så i det här fallet är det tydligt att skiftläge och blanksteg är mycket viktiga. Jag pratade om detta mycket mer i detalj i maj förra året.

Schemareferenser är viktiga

Jag har bloggat tidigare om vikten av att specificera schemaprefixet när jag hänvisar till något objekt, men vid den tidpunkten var jag inte helt medveten om att det också hade plancache-implikationer.

Låt oss ta en titt på ett mycket enkelt fall där vi har två användare med olika standardscheman, och de kör exakt samma frågetext, utan att referera till objektet med dess schema:

USE AdventureWorks2014;
DBCC FREEPROCCACHE WITH NO_INFOMSGS;
GO
 
CREATE USER SQLPerf1 WITHOUT LOGIN WITH DEFAULT_SCHEMA = Sales;
CREATE USER SQLPerf2 WITHOUT LOGIN WITH DEFAULT_SCHEMA = Person;
GO
 
CREATE TABLE dbo.AnErrorLog(id INT);
GRANT SELECT ON dbo.AnErrorLog TO SQLPerf1, SQLPerf2;
GO
 
EXECUTE AS USER = N'SQLPerf1';
GO
SELECT id FROM AnErrorLog;
GO
REVERT;
GO
EXECUTE AS USER = N'SQLPerf2';
GO
SELECT id FROM AnErrorLog;
GO
REVERT;
GO

Om vi ​​nu tar en titt på plancachen kan vi dra in sys.dm_exec_plan_attributes för att se exakt varför vi får två olika planer för identiska frågor:

SELECT t.[text], p.size_in_bytes, p.usecounts, 
  [schema_id] = pa.value, 
  [schema] = s.name
FROM sys.dm_exec_cached_plans AS p
CROSS APPLY sys.dm_exec_sql_text(p.plan_handle) AS t
CROSS APPLY sys.dm_exec_plan_attributes(p.plan_handle) AS pa
INNER JOIN sys.schemas AS s ON s.[schema_id] = pa.value
WHERE t.[text] LIKE N'%AnError'+'Log%' 
AND pa.attribute = N'user_id';

Resultat:

Och om du kör allt igen men lägger till dbo. prefix för båda frågorna kommer du att se att det bara finns en plan som används två gånger. Detta blir ett mycket övertygande argument för att alltid fullständigt referera till objekt.

SET inställningar redux

Som en sidoanteckning kan du använda ett liknande tillvägagångssätt för att avgöra om SET inställningarna är olika för två eller flera versioner av samma fråga. I det här fallet undersöker vi frågorna som är involverade i flera planer som genereras av olika anrop till samma lagrade procedur, men du kan också identifiera dem med frågetexten eller frågehash.

SELECT p.plan_handle, p.usecounts, p.size_in_bytes, 
  set_options = MAX(a.value)
FROM sys.dm_exec_cached_plans AS p
CROSS APPLY sys.dm_exec_sql_text(p.plan_handle) AS t
CROSS APPLY sys.dm_exec_plan_attributes(p.plan_handle) AS a
WHERE t.objectid = OBJECT_ID(N'dbo.procedure_name')
AND a.attribute = N'set_options'
GROUP BY p.plan_handle, p.usecounts, p.size_in_bytes;

Om du har flera resultat här bör du se olika värden för set_options (vilket är en bitmask). Det är bara början; Jag ska gå ut här och berätta att du kan bestämma vilken uppsättning alternativ som är aktiverade för varje plan genom att packa upp värdet enligt avsnittet "Utvärdera uppsättningsalternativ" här. Ja, jag är så lat.

Slutsats

Det finns flera anledningar till att du kan se olika planer för samma fråga (eller vad du tror är samma fråga). I de flesta fall kan du isolera orsaken ganska enkelt; utmaningen är ofta att veta att leta efter det i första hand. I mitt nästa inlägg kommer jag att prata om ett lite annorlunda ämne:varför en databas som återställs till en "identisk" server kan ge olika planer för samma fråga.


  1. Hur man skapar en användare med PSQL

  2. PL/SQL-samling:Kapslad tabell i Oracle-databas

  3. Hur man skriver en .Net-applikation som fungerar med både SqlServer och Oracle (nu när System.Data.OracleClient är utfasad)

  4. Flera infoga SQL-oracle