sql >> Databáze >  >> RDS >> Database

Zamykání a výkon

Typ a počet zámků získaných a uvolněných během provádění dotazu může mít překvapivý vliv na výkon (při použití úrovně izolace zamykání, jako je výchozí potvrzené čtení), i když nedochází k žádnému čekání nebo blokování. V prováděcích plánech nejsou žádné informace, které by indikovaly množství aktivity zamykání během provádění, což ztěžuje rozpoznání, kdy nadměrné zamykání způsobuje problém s výkonem.

Abych prozkoumal některé méně známé chování zamykání na serveru SQL Server, znovu použiji dotazy a ukázková data z mého posledního příspěvku o výpočtu mediánů. V tom příspěvku jsem zmínil, že OFFSET seskupené mediánové řešení potřebovalo explicitní PAGLOCK zamykací nápověda, aby nedošlo ke ztrátě vnořeného kurzoru řešení, takže začněme tím, že se podrobně podíváme na důvody.

Řešení OFFSET Grouped Medián

Test seskupeného mediánu znovu použil ukázková data z dřívějšího článku Aarona Bertranda. Skript níže znovu vytvoří toto nastavení s milionem řádků, které se skládá z deseti tisíc záznamů pro každého ze sta imaginárních prodejců:

CREATE TABLE dbo.Sales
(
    SalesPerson integer NOT NULL, 
    Amount integer NOT NULL
);
 
WITH X AS
(
    SELECT TOP (100) 
        V.number
    FROM master.dbo.spt_values AS V
    GROUP BY 
        V.number
)
INSERT dbo.Sales WITH (TABLOCKX) 
(
    SalesPerson, 
    Amount
)
SELECT 
    X.number,
    ABS(CHECKSUM(NEWID())) % 99
FROM X 
CROSS JOIN X AS X2 
CROSS JOIN X AS X3;
 
CREATE CLUSTERED INDEX cx 
ON dbo.Sales
    (SalesPerson, Amount);

SQL Server 2012 (a novější) OFFSET řešení vytvořené Peterem Larssonem je následující (bez jakýchkoli zámků):

DECLARE @s datetime2 = SYSUTCDATETIME();
 
DECLARE @Result AS table 
(
    SalesPerson integer PRIMARY KEY, 
    Median float NOT NULL
);
 
INSERT @Result
    (SalesPerson, Median)
SELECT	
    d.SalesPerson, 
    w.Median
FROM
(
    SELECT SalesPerson, COUNT(*) AS y
    FROM dbo.Sales
    GROUP BY SalesPerson
) AS d
CROSS APPLY
(
    SELECT AVG(0E + Amount)
    FROM
    (
        SELECT z.Amount
        FROM dbo.Sales AS z
        WHERE z.SalesPerson = d.SalesPerson
        ORDER BY z.Amount
        OFFSET (d.y - 1) / 2 ROWS
        FETCH NEXT 2 - d.y % 2 ROWS ONLY
    ) AS f
) AS w (Median);
 
SELECT Peso = DATEDIFF(MILLISECOND, @s, SYSUTCDATETIME());

Důležité části plánu po provedení jsou uvedeny níže:

Se všemi požadovanými daty v paměti se tento dotaz provede za 580 ms v průměru na mém notebooku (s SQL Server 2014 Service Pack 1). Výkon tohoto dotazu lze zlepšit na 320 ms jednoduše přidáním nápovědy k uzamčení granularity stránky do tabulky Prodej v poddotazu použití:

DECLARE @s datetime2 = SYSUTCDATETIME();
 
DECLARE @Result AS table 
(
    SalesPerson integer PRIMARY KEY, 
    Median float NOT NULL
);
 
INSERT @Result
    (SalesPerson, Median)
SELECT	
    d.SalesPerson, 
    w.Median
FROM
(
    SELECT SalesPerson, COUNT(*) AS y
    FROM dbo.Sales
    GROUP BY SalesPerson
) AS d
CROSS APPLY
(
    SELECT AVG(0E + Amount)
    FROM
    (
        SELECT z.Amount
        FROM dbo.Sales AS z WITH (PAGLOCK) -- NEW!
        WHERE z.SalesPerson = d.SalesPerson
        ORDER BY z.Amount
        OFFSET (d.y - 1) / 2 ROWS
        FETCH NEXT 2 - d.y % 2 ROWS ONLY
    ) AS f
) AS w (Median);
 
SELECT Peso = DATEDIFF(MILLISECOND, @s, SYSUTCDATETIME());

Prováděcí plán je nezměněn (samozřejmě kromě textu nápovědy pro zamykání v showplan XML):

Analýza seskupeného mediánu uzamčení

Vysvětlení pro dramatické zlepšení výkonu díky PAGLOCK nápověda je docela jednoduchá, alespoň zpočátku.

Pokud ručně monitorujeme aktivitu zamykání během provádění tohoto dotazu, vidíme, že bez nápovědy k granularitě zamykání stránky SQL Server získává a uvolňuje více než půl milionu zámků na úrovni řádků při hledání seskupeného indexu. Na vině není žádné blokování; prosté získání a uvolnění takového množství zámků přidává podstatnou režii na provádění tohoto dotazu. Vyžadování zámků na úrovni stránky výrazně snižuje aktivitu zamykání, což vede k mnohem lepšímu výkonu.

Problém s výkonem zamykání tohoto konkrétního plánu je omezen na hledání seskupeného indexu ve výše uvedeném plánu. Úplné prohledávání seskupeného indexu (používá se k výpočtu počtu řádků přítomných pro každého prodejce) automaticky používá zámky na úrovni stránky. To je zajímavý bod. Podrobné zamykání enginu SQL Serveru není v Books Online do velké míry zdokumentováno, ale různí členové týmu SQL Server učinili v průběhu let několik obecných poznámek, včetně skutečnosti, že neomezené skenování má tendenci začínat načítáním stránky. zámky, zatímco menší operace mají tendenci začínat zámky řádků.

Optimalizátor dotazů zpřístupňuje úložišti některé informace, včetně odhadů mohutnosti, interních rad pro úroveň izolace a granularity zamykání, které interní optimalizace lze bezpečně použít a tak dále. Tyto podrobnosti opět nejsou v Books Online zdokumentovány. Úložný modul nakonec používá různé informace, aby rozhodl, které zámky jsou vyžadovány za běhu a s jakou granularitou by měly být použity.

Jako vedlejší poznámku a při vědomí, že mluvíme o dotazu spouštěném pod výchozí úrovní izolace potvrzené transakce pro čtení zamykáním, si všimněte, že zámky řádků provedené bez náznaku granularity se v tomto případě nezvýší na zámek tabulky. Je to proto, že normálním chováním při čtení potvrzeno je uvolnit předchozí zámek těsně před získáním dalšího zámku, což znamená, že v každém konkrétním okamžiku bude zadržen pouze jeden sdílený zámek řádku (s přidruženými zámky sdílenými záměry vyšší úrovně). Vzhledem k tomu, že počet souběžně držených zámků řádků nikdy nedosáhne prahové hodnoty, nedojde k pokusu o eskalaci zámku.

Řešení OFFSET Single Medián

Test výkonu pro výpočet jediného mediánu používá jinou sadu vzorových dat, opět reprodukovaných z Aaronova dřívějšího článku. Skript níže vytvoří tabulku s deseti miliony řádků pseudonáhodných dat:

CREATE TABLE dbo.obj
(
    id  integer NOT NULL IDENTITY(1,1), 
    val integer NOT NULL
);
 
INSERT dbo.obj WITH (TABLOCKX) 
    (val)
SELECT TOP (10000000) 
    AO.[object_id]
FROM sys.all_columns AS AC
CROSS JOIN sys.all_objects AS AO
CROSS JOIN sys.all_objects AS AO2
WHERE AO.[object_id] > 0
ORDER BY 
    AC.[object_id];
 
CREATE UNIQUE CLUSTERED INDEX cx 
ON dbo.obj(val, id);

OFFSET řešení je:

DECLARE @Start datetime2 = SYSUTCDATETIME();
 
DECLARE @Count bigint = 10000000
--(
--    SELECT COUNT_BIG(*) 
--    FROM dbo.obj AS O
--);
 
SELECT 
    Median = AVG(1.0 * SQ1.val)
FROM 
(
    SELECT O.val 
    FROM dbo.obj AS O
    ORDER BY O.val
    OFFSET (@Count - 1) / 2 ROWS
    FETCH NEXT 1 + (1 - @Count % 2) ROWS ONLY
) AS SQ1;
 
SELECT Peso = DATEDIFF(MILLISECOND, @Start, SYSUTCDATETIME());

Plán po provedení je:

Tento dotaz se provede za 910 ms v průměru na mém testovacím stroji. Výkon se nezmění, pokud PAGLOCK je přidána nápověda, ale důvodem není to, co si možná myslíte…

Analýza zamykání jednoho mediánu

Možná očekáváte, že úložný modul si stejně vybere sdílené zámky na úrovni stránky, kvůli skenování seskupeného indexu, což vysvětluje, proč PAGLOCK nápověda nemá žádný účinek. Sledování uzamčení přijatých během provádění tohoto dotazu ve skutečnosti odhalí, že nejsou přijaty žádné sdílené zámky (S), při jakékoli granularitě . Jedinými přijatými zámky jsou sdílené záměry (IS) na úrovni objektu a stránky.

Vysvětlení tohoto chování má dvě části. První věc, kterou si všimnete, je, že Clustered Index Scan je v plánu provádění pod nejvyšším operátorem. To má důležitý vliv na odhady mohutnosti, jak ukazuje plán před provedením (odhadovaný):

OFFSET a FETCH klauzule v dotazu odkazují na výraz a proměnnou, takže optimalizátor dotazů odhadne počet řádků, které budou potřeba za běhu. Standardní odhad pro Top je sto řádků. To je samozřejmě hrozný odhad, ale stačí to k tomu, abyste přesvědčili modul úložiště, aby se uzamkl na úrovni granularity řádků místo na úrovni stránky.

Pokud zakážeme efekt „cíl řádku“ operátoru Top pomocí dokumentovaného příznaku trasování 4138, odhadovaný počet řádků při skenování se změní na deset milionů (což je stále špatně, ale v opačném směru). To stačí ke změně rozhodnutí o granularitě zamykání úložiště, takže se použijí sdílené zámky na úrovni stránky (poznámka, nikoli zámky sdílené záměrem):

DECLARE @Start datetime2 = SYSUTCDATETIME();
 
DECLARE @Count bigint = 10000000
--(
--    SELECT COUNT_BIG(*) 
--    FROM dbo.obj AS O
--);
 
SELECT 
    Median = AVG(1.0 * SQ1.val)
FROM 
(
    SELECT O.val 
    FROM dbo.obj AS O
    ORDER BY O.val
    OFFSET (@Count - 1) / 2 ROWS
    FETCH NEXT 1 + (1 - @Count % 2) ROWS ONLY
) AS SQ1
OPTION (QUERYTRACEON 4138); -- NEW!
 
SELECT Peso = DATEDIFF(MILLISECOND, @Start, SYSUTCDATETIME());

Odhadovaný plán provádění vytvořený pod příznakem trasování 4138 je:

Vrátíme-li se k hlavnímu příkladu, stořádkový odhad kvůli uhádnutému cíli řádku znamená, že se modul úložiště rozhodne uzamknout na úrovni řádku. Zámky sdíleného záměru (IS) však pozorujeme pouze na úrovni tabulky a stránky. Tyto zámky vyšší úrovně by byly docela normální, kdybychom viděli sdílené (S) zámky na úrovni řádků, kam se tedy poděly?

Odpověď je, že modul úložiště obsahuje další optimalizaci, která může za určitých okolností přeskočit sdílené zámky na úrovni řádků. Když je tato optimalizace použita, budou stále získávány zámky sdílené záměrem vyšší úrovně.

Abychom to shrnuli, pro dotaz s jedním mediánem:

  1. Použití proměnné a výrazu v OFFSET klauzule znamená, že optimalizátor odhadne mohutnost.
  2. Nízký odhad znamená, že se o strategii zamykání na úrovni řádků rozhodne modul úložiště.
  3. Interní optimalizace znamená, že zámky S na úrovni řádků jsou za běhu přeskočeny a ponechávají pouze zámky IS na úrovni stránky a objektu.

Jediný mediánový dotaz by měl stejný problém s výkonem zamykání řádků jako seskupený medián (kvůli nepřesnému odhadu optimalizátoru dotazů), ale byl uložen samostatnou optimalizací úložiště, která vedla k tomu, že byly použity pouze zámky stránek a tabulek sdílených záměrem. za běhu.

Znovu navštívený test seskupeného mediánu

Možná se divíte, proč Clustered Index Seek v testu seskupeného mediánu nevyužil stejnou optimalizaci úložiště k přeskočení sdílených zámků na úrovni řádků. Proč bylo použito tolik zámků sdílených řádků, takže PAGLOCK nutná nápověda?

Krátká odpověď je, že tato optimalizace není dostupná pro INSERT...SELECT dotazy. Pokud spustíme SELECT samostatně (tj. bez zápisu výsledků do tabulky) a bez PAGLOCK tip, optimalizace přeskakování zámku řádku je použito:

DECLARE @s datetime2 = SYSUTCDATETIME();
 
--DECLARE @Result AS table 
--(
--    SalesPerson integer PRIMARY KEY, 
--    Median float NOT NULL
--);
 
--INSERT @Result
--    (SalesPerson, Median)
SELECT	
    d.SalesPerson, 
    w.Median
FROM
(
    SELECT SalesPerson, COUNT(*) AS y
    FROM dbo.Sales
    GROUP BY SalesPerson
) AS d
CROSS APPLY
(
    SELECT AVG(0E + Amount)
    FROM
    (
        SELECT z.Amount
        FROM dbo.Sales AS z
        WHERE z.SalesPerson = d.SalesPerson
        ORDER BY z.Amount
        OFFSET (d.y - 1) / 2 ROWS
        FETCH NEXT 2 - d.y % 2 ROWS ONLY
    ) AS f
) AS w (Median);
 
SELECT Peso = DATEDIFF(MILLISECOND, @s, SYSUTCDATETIME());

Používají se pouze zámky sdíleného záměru (IS) na úrovni tabulky a stránky a výkon se zvýší na stejnou úroveň, jako když použijeme PAGLOCK náznak. Toto chování v dokumentaci samozřejmě nenajdete a může se kdykoli změnit. Přesto je dobré si toho být vědom.

V případě, že by vás to zajímalo, příznak trasování 4138 nemá v tomto případě žádný vliv na volbu uzamykací granularity modulu úložiště, protože odhadovaný počet řádků při hledání je příliš nízký (na iteraci aplikace), i když je cíl řádku deaktivován.

Před vyvozením závěrů o výkonu dotazu nezapomeňte zkontrolovat počet a typ zámků, které při provádění provádí. Přestože SQL Server obvykle volí „správnou“ granularitu, jsou chvíle, kdy se může něco pokazit, někdy s dramatickými dopady na výkon.


  1. Tipy pro monitorování MySQL pro Moodle

  2. Oznamujeme repmgr 2.0

  3. Proces upgradu krok za krokem pro aktualizaci R12.2, část -4 (používání 12.2.x Release Update Pack)

  4. co se stane ve fázi adopce, připravte