Sdílet prostřednictvím


Diagnostika a řešení kolize západek na SQL Serveru

Tato příručka popisuje, jak identifikovat a vyřešit kolize západek pozorované při spouštění aplikací SQL Serveru v systémech s vysokou souběžností s určitými úlohami.

Vzhledem k tomu, že počet jader procesoru na serverech stále roste, může přidružené zvýšení souběžnosti zavést kolizí body datových struktur, ke kterým je potřeba přistupovat sériově v databázovém stroji. To platí zejména pro úlohy zpracování transakcí s vysokou propustností/vysokou paralelností (OLTP). Existuje několik nástrojů, technik a způsobů přístupu k těmto výzvám a postupům, které je možné dodržovat při navrhování aplikací, které by jim mohly pomoct úplně se jim vyhnout. Tento článek popisuje konkrétní typ konfliktu u datových struktur, které používají spinlocks k serializaci přístupu k těmto datovým strukturám.

Poznámka:

Tento obsah připravil tým Microsoft SQL Server Customer Advisory Team (SQLCAT) na základě svého procesu identifikace a řešení problémů spojených s konfliktem zámku stránky v aplikacích SQL Serveru na systémech s vysokou mírou souběžnosti. Doporučení a osvědčené postupy popsané zde jsou založeny na skutečných zkušenostech během vývoje a nasazování skutečných systémů OLTP.

Co je soutěž uzamčení SQL Serveru?

Západky jsou prostá synchronizační primitiva, s jejichž využitím modul SQL Serveru zajišťuje konzistenci struktur v paměti, včetně indexu, datových stránek a interních struktur, jako jsou stránky jiného typu než list v B-stromu. SQL Server využívá západky vyrovnávacích pamětí k zajištění ochrany stránek ve fondu vyrovnávacích pamětí a západky vstupně-výstupních operací k zajištění ochrany stránek, které se do fondu vyrovnávacích pamětí ještě nenačetly. Pokaždé, když jsou data zapsána na stránku nebo přečtena ze stránky ve fondu vyrovnávací paměti SQL Serveru, musí pracovní vlákno nejprve získat zámek vyrovnávací paměti pro stránku. Pro přístup ke stránkám ve fondu vyrovnávací paměti jsou k dispozici různé typy západek vyrovnávací paměti, včetně výhradních západek (PAGELATCH_EX) a sdílených západek (PAGELATCH_SH). Když se SQL Server pokusí získat přístup ke stránce, která ještě není ve fondu vyrovnávací paměti, publikuje se asynchronní vstupně-výstupní operace pro načtení stránky do fondu vyrovnávací paměti. Pokud SQL Server potřebuje počkat na odpověď V/V subsystému, čeká na výhradní (PAGEIOLATCH_EX) nebo sdílené (PAGEIOLATCH_SH) západky vstupně-výstupních operací v závislosti na typu požadavku; to je provedeno, aby se zabránilo načtení stejné stránky do fondu vyrovnávací paměti jiným pracovním vláknem s nekompatibilní západkou. Západky se také používají k ochraně přístupu k interním paměťovým strukturám jiným než stránkám fondu vyrovnávací paměti; označují se jako západky bez vyrovnávací paměti.

Kolize na západkách stránek je nejběžnějším scénářem, ke kterým dochází u systémů s více procesory, a proto se většina tohoto článku zaměřuje na tyto systémy.

Kolize západek nastane, když se více vláken souběžně pokusí získat nekompatibilní západky do stejné struktury v paměti. Protože zarážka je vnitřní kontrolní mechanismus, SQL engine automaticky určí, kdy ji použít. Vzhledem k tomu, že chování západek je deterministické, můžou tato chování ovlivnit rozhodnutí aplikací včetně návrhu schématu. Cílem tohoto článku je poskytnout následující informace:

  • Základní informace o tom, jak sql Server používá západky.
  • Nástroje používané ke zkoumání kolidování západek
  • Jak zjistit, jestli je zjištěné množství kolizí problematické.

Probereme některé běžné scénáře a způsob, jak je nejlépe zvládnout, aby se zmírňovaly kolize.

Jak SQL Server používá západky?

Stránka v SQL Serveru je 8 kB a může ukládat více řádků. Pro zvýšení souběžnosti a výkonu se západky vyrovnávací paměti uchovávají pouze po dobu trvání fyzické operace na stránce, na rozdíl od zámků, které se uchovávají po dobu trvání logické transakce.

Západky jsou interní pro modul SQL a používají se k zajištění konzistence paměti, zatímco zámky používají SQL Server k zajištění logické transakční konzistence. Následující tabulka porovnává západky se zámky:

Struktura Účel Řízeno pomocí Náklady na výkon Odhaleno uživatelem
Západka Zaručuje konzistenci struktur v paměti. Pouze modul SQL Serveru. Náklady na výkon jsou nízké. Pro zajištění maximální souběžnosti a zajištění maximálního výkonu se západky uchovávají pouze po dobu trvání fyzické operace ve struktuře v paměti, na rozdíl od zámků, které se uchovávají po dobu trvání logické transakce. sys.dm_os_wait_stats - Poskytuje informace o PAGELATCH, PAGEIOLATCH a LATCH typech čekání (LATCH_EX se používá k seskupení všech čekání nepatřících k vyrovnávacím západkám).
sys.dm_os_latch_stats – Poskytuje podrobné informace o čekání na zámky nevyrovnávací paměti.
sys.dm_db_index_operational_stats – Toto zobrazení dynamické správy poskytuje agregované čekací doby pro každý index, což je užitečné při řešení potíží s výkonem souvisejících se zámky.
Uzamknout Zaručuje konzistenci transakcí. Může být řízen uživatelem. Náklady na výkon jsou vysoké vzhledem k západkám, protože zámky musí být drženy po dobu trvání transakce. sys.dm_tran_locks.
sys.dm_exec_sessions.

Režimy latchů SQL Serveru a kompatibilita

Nějaké soutěžení o uzamčení se očekává být normální součástí provozu motoru SQL Serveru. Je nevyhnutelné, že v systému s vysokou souběžností dochází k mnoha souběžným žádostem o zarážky s různou mírou kompatibility. SQL Server zajišťuje kompatibilitu západek tím, že nekompatibilní požadavky na západku musí čekat ve frontě, dokud nejsou dokončeny nevyřízené požadavky na západky.

Západky se získávají v jednom z pěti různých módů, které souvisejí s úrovní přístupu. Režimy západek SQL Serveru lze shrnout takto:

  • KP: Držte západku. Zajišťuje, aby odkazovaná struktura nemohla být zničena. Používá se, když se vlákno chce podívat na strukturu vyrovnávací paměti. Vzhledem k tomu, že západka KP je kompatibilní se všemi západkami s výjimkou západky zničení (DT), je západka KP považována za nenáročnou, což znamená, že vliv na výkon při jejím použití je minimální. Vzhledem k tomu, že západka KP není kompatibilní se západkou DT, brání jakémukoli jinému vláknu zničit odkazovanou strukturu. Například západka KP brání struktuře, na kterou odkazuje, aby byla zničena opožděným procesem zápisu. Další informace o tom, jak se používá proces lazy writer v souvislosti se správou stránek vyrovnávací paměti SQL Serveru, naleznete Zápis stránek v databázovém stroji.

  • SH: Sdílená západka. Vyžaduje se ke čtení odkazované struktury (například čtení datové stránky). Více vláken může současně přistupovat k prostředku pro čtení v režimu sdíleného zámku.

  • UP: Aktualizace záchytu. Kompatibilní pouze s SH (sdílenou západkou) a KP, ale ne s žádnými jinými, a proto EX západka nemůže zapisovat do odkazované struktury.

  • EX: Exkluzivní západka. Blokuje jiným vláknům zápis do nebo čtení z odkazované struktury. Jedním z příkladů použití je úprava obsahu stránky pro ochranu před poškozením stránky.

  • DT: Zničit západku. Před zničením obsahu odkazované struktury je nutné získat potřebné oprávnění. Například proces opožděného zápisu musí získat zámek DT, aby bylo možné uvolnit čistou stránku před jejím přidáním do seznamu volných vyrovnávacích pamětí, které jsou k dispozici pro použití jinými vlákny.

Režimy západek mají různé úrovně kompatibility, například sdílená západka (SH) je kompatibilní s aktualizační západkou (UP) nebo západkou (KP), ale nekompatibilní s destruktivní západkou (DT). Více západek lze současně získat ve stejné struktuře, pokud jsou západky kompatibilní. Když se vlákno pokusí získat zámek držený v režimu, který není kompatibilní, je zařazeno do fronty a čeká na signál označující, že je prostředek dostupný. K ochraně čekací fronty se používá spinlock typu SOS_Task, který vynucuje serializovaný přístup k frontě. Tento zámek se musí získat, aby bylo možné přidávat položky do fronty. SOS_Task spinlock také signalizuje vlákna ve frontě, když jsou nekompatibilní západky uvolněny, což umožňuje čekajícím vláknům získat kompatibilní západku a pokračovat v jejich práci. Fronta čekání se zpracovává na principu první dovnitř, první ven (FIFO), jakmile se uvolňují požadavky na západku. Západky se řídí tímto systémem FIFO, aby byla zajištěna spravedlnost a zabránila hladovění vláken.

Kompatibilita režimu západky je uvedená v následující tabulce (Ano označuje kompatibilitu a ne značí nekompatibilitu):

Režim západky KP SH NAHORU EX DT
KP Ano Ano Ano Ano Ne
SH Ano Ano Ano Ne Ne
UP Ano Ano Ne Ne Ne
EX Ano Ne Ne Ne Ne
DT Ne Ne Ne Ne Ne

Superlatches a sublatches v SQL Serveru

Díky rostoucí přítomnosti systémů s více sokety nebo vícejádrových systémů založených na technologii NUMA zavedl SQL Server 2005 superlatche, označované také jako sublachy, které jsou efektivní pouze na systémech s 32 nebo více logickými procesory. Superlatches vylepšují efektivitu SQL modulu pro určité vzorce použití ve vysoce souběžných OLTP úlohách; například když mají určité stránky vzorec intenzivního sdíleného (SH) přístupu jen pro čtení, ale zapisují se zřídka. Příkladem stránky s takovým vzorem přístupu je kořenová stránka B(to znamená index). Modul SQL vyžaduje, aby se sdílená západka uchovávala na kořenové stránce, když dojde k rozdělení stránky na libovolné úrovni ve stromu B. V úloze OLTP charakterizované vysokým počtem vkládání a vysokou souběžností se počet rozdělení stránek významně zvyšuje úměrně s propustností, což může vést k poklesu výkonu. Superlatches může povolit vyšší výkon pro přístup ke sdíleným stránkám, kde více souběžně spuštěných pracovních vláken vyžaduje SH západky. K dosažení tohoto cíle modul SQL Serveru dynamicky povýší západku na takové stránce na superzápadku. Superlatch rozdělí jednu západku na pole podsložek, jednu podsložku na oddíl na jádro procesoru, přičemž hlavní západka se stane přesměrovacím proxy a pro západky určené jen ke čtení se nevyžaduje globální synchronizace stavu. Při tom musí pracovník, který je vždy přiřazen ke konkrétnímu procesoru, získat pouze sdílený (SH) dílčí zámek přiřazený místnímu plánovači.

Poznámka:

Dokumentace používá termín B-tree obecně v odkazu na indexy. V indexech rowstore databázový stroj implementuje strom B+. To neplatí pro indexy columnstore ani indexy v tabulkách optimalizovaných pro paměť. Další informace najdete v SQL Serveru a architektuře indexu Azure SQL a průvodci návrhem.

Získání kompatibilních západek, jako je například sdílený Superlatch, využívá méně prostředků a lépe škáluje přístup k horkým stránkám než nedělený sdílený západek, protože odstranění požadavku na globální synchronizaci stavu výrazně zvyšuje výkon díky přístupu pouze k místní paměti NUMA. Naopak získání exkluzivního (EX) superlatch je dražší než získání EX běžné západky, protože SQL musí signalizovat napříč všemi podzápadkami. Když se zjistí, že superlatch používá vzor intenzivního EX přístupu, modul SQL může snížit jeho prioritu po vyřazení stránky z vyrovnávací paměti. Následující diagram znázorňuje normální západku a dělený Superlatch:

Diagram superzámku SQL Serveru

Pomocí objektu SQL Server:Latches a přidružených čítačů v nástroji Sledování výkonu můžete shromažďovat informace o superlatchech, včetně jejich počtu, povýšení superlatchů za sekundu a degradace superlatchů za sekundu. Další informace o objektu SQL Server:Latches a přidružených čítačích naleznete v tématu SQL Server, latches objekt.

Čekací typy západek

Sql Server sleduje kumulativní informace o čekání a lze k němu získat přístup pomocí zobrazení dynamické správy (DMW). sys.dm_os_wait_stats SQL Server používá tři typy čekání na západky, které jsou definované pomocí odpovídajících wait_type v sys.dm_os_wait_stats dynamickém spravovacím zobrazení.

  • Západka vyrovnávací paměti (BUF): slouží k zajištění konzistence indexů a datových stránek pro objekty uživatele. Slouží také k ochraně přístupu k datovým stránkám, které SQL Server používá pro systémové objekty. Západky vyrovnávacích pamětí chrání například stránky, které spravují přidělení. Patří mezi ně stránky Volného místa (PFS), Globální mapa přidělování (GAM), Sdílená globální mapa přidělování (SGAM) a Mapa přidělování indexů (IAM). Zámky bufferu jsou hlášeny v sys.dm_os_wait_stats s wait_typePAGELATCH_*.

  • Západka bez vyrovnávací paměti (Non-BUF): slouží k zajištění konzistence všech struktur v paměti jiných než stránek fondu vyrovnávací paměti. Všechny čekání na západky bez vyrovnávací paměti jsou hlášeny jako wait_typeLATCH_*.

  • Západka I/O: podmnožina západek vyrovnávací paměti, která zaručuje konzistenci stejných struktur chráněných západkami vyrovnávací paměti, když je třeba tyto struktury načíst do fondu vyrovnávací paměti pomocí operace I/O. Západky vstupně-výstupních operací brání načtení jiného vlákna na stejnou stránku do fondu vyrovnávací paměti s nekompatibilní západkou. Přidruženo k wait_typePAGEIOLATCH_*.

    Poznámka:

    Pokud vidíte značné PAGEIOLATCH čekání, znamená to, že SQL Server čeká na vstupně-výstupní subsystém. Zatímco určité množství PAGEIOLATCH čekání je očekávané a normální chování, pokud průměrné PAGEIOLATCH doby čekání jsou konzistentně vyšší než 10 milisekund (ms), měli byste prozkoumat, proč je subsystém vstupně-výstupní operace pod tlakem.

Pokud při zkoumání sys.dm_os_wait_stats DMV narazíte na západky mimo vyrovnávací paměť, je třeba zkontrolovat sys.dm_os_latch_stats pro získání podrobného rozpisu kumulativních informací o čekání na západky mimo vyrovnávací paměť. Všechny čekací události na západku vyrovnávací paměti jsou klasifikovány pod třídou západky BUFFER, a zbývající se používají ke klasifikaci západek, které nejsou vyrovnávací pamětí.

Příznaky a příčiny kolize západek SQL Serveru

V zaneprázdněném systému s vysokou souběžností je normální vidět aktivní soutěžení na strukturách, které jsou často přístupné a chráněné zámky a dalšími kontrolními mechanismy v SQL Serveru. Považuje se za problematické, když kolize a doba čekání spojená se získáním zámku stránky stačí ke snížení využití prostředků (CPU), což brání propustnosti.

Příklad kolize zámků

Modrá čára v následujícím diagramu představuje propustnost SQL Serveru měřenou transakcemi za sekundu; černá čára představuje průměrnou dobu čekání na západku stránky. V tomto případě každá transakce provádí INSERT do clusterovaného indexu s postupně rostoucí počáteční hodnotou, například při naplnění IDENTITY sloupce datového typu bigint. S nárůstem počtu procesorů na 32 je zřejmé, že celková propustnost se snížila a doba čekání na západku stránky se zvýšila na přibližně 48 milisekund, jak je důkazem černé čáry. Tento inverzní vztah mezi propustností a dobou čekání na zámek stránky je běžný případ, který lze snadno diagnostikovat.

Diagram znázorňující, jak se propustnost snižuje při nárůstu souběžnosti

Výkon při řešení kolize západek

Jak ilustruje následující diagram, SQL Server již není zúžen úzkým hrdlem čekání na zámky stránek a propustnost se zvýšila o 300% měřená transakcemi za sekundu. To bylo provedeno pomocí dělení hash s počítaným sloupcem popsaným dále v tomto článku. Toto zlepšení výkonu je zaměřeno na systémy s vysokým počtem jader a vysokou úrovní souběžnosti.

Diagram zlepšení propustnosti realizovaných pomocí dělení pomocí hash

Faktory ovlivňující kolize západek

Kolize zádržek, která brání výkonu v prostředích OLTP, je obvykle způsobena vysokou souběžností související s jedním nebo více z následujících faktorů:

Faktor Podrobnosti
Vysoký počet logických procesorů používaných SQL Serverem Kontence zámků může nastat v jakémkoliv systému s více jádry. V SQLCAT byly nadměrné kolize zámků, které mají vliv na výkon aplikace nad přijatelnými úrovněmi, nejčastěji pozorovány na systémech s více než 16 jádry procesoru a mohly by se zvýšit, jak je k dispozici více jader.
Vzory návrhu schématu a přístupu Hloubka B-stromu, návrh klastrovaných a neklastrovaných indexů, velikost a hustota řádků na stránku a vzory přístupu (aktivita čtení/zápis/mazání) jsou faktory, které mohou přispět k nadměrnému srážkovému zatížení zámků stránek.
Vysoký stupeň souběžnosti na úrovni aplikace Nadměrné spory o zámek stránky obvykle nastávají ve spojení s vysokou úrovní souběžných požadavků z aplikační úrovně. Existují určité programovací postupy, které mohou také zavést velký počet požadavků na konkrétní stránku.
Rozložení logických souborů používaných databázemi SQL Serveru Rozložení logického souboru může ovlivnit úroveň soutěžení o stránkové závory, které způsobují struktury přidělování, jako jsou stránky PFS (Page Free Space), Global Allocation Map (GAM), Shared Global Allocation Map (SGAM) a Index Allocation Map (IAM). Další informace najdete v tématu Monitorování a řešení potíží databáze TempDB: Kritické body přidělení.
Výkon subsystému vstupně-výstupních operací Významné PAGEIOLATCH čekání značí, že SQL Server čeká na vstupně-výstupní subsystém.

Diagnostika konfliktu zádržek SQL Serveru

Tato část obsahuje informace o diagnostice konfliktů latchů v SQL Serveru, abyste zjistili, zda to představuje problém pro vaše prostředí.

Nástroje a metody pro diagnostiku souběhu zámků

Nástroje primárně používané k diagnostice kolizí západek jsou:

  • Sledování výkonu za účelem monitorování využití procesoru a doby čekání na SQL Server a zjištění, jestli existuje vztah mezi využitím procesoru a dobami čekání na západku.

  • Zobrazení dynamické správy SYSTÉMU SQL Server, které lze použít k určení konkrétního typu západky, která způsobuje problém a ovlivněný prostředek.

  • V některých případech je nutné získat a analyzovat výpisy paměti procesu SQL Serveru pomocí nástrojů pro ladění systému Windows.

Poznámka:

Tato úroveň pokročilého odstraňování potíží se obvykle vyžaduje pouze v případě, že se řeší konflikt zámků mimo vyrovnávací paměť. Pro tento typ pokročilého řešení potíží můžete chtít zapojit služby podpory produktů společnosti Microsoft.

Technický proces diagnostiky kolize západek lze shrnout v následujících krocích:

  1. Určete, zda může být konflikt, který by mohl souviset se závorami.

  2. K určení typu západky a ovlivněných prostředků použijte dynamická zobrazení správy (DMV) uvedená v Dodatku: Skripty pro řešení kolizí západky v SQL Serveru.

  3. Zmírněte kolize pomocí jedné z technik popsaných v Řešení latche pro různé vzory tabulek.

Indikátory kolize západek

Jak jsme uvedli dříve, kolize západek je problematická pouze v případě, že kolize a doba čekání spojená se získáním západek stránky brání zvýšení propustnosti, když jsou k dispozici prostředky procesoru. Určení přijatelného množství kolizí vyžaduje holistický přístup, který zohledňuje požadavky na výkon a propustnost společně s dostupnými vstupně-výstupními prostředky a prostředky procesoru. Tato část vás provede určením dopadu souběhu zámků na zatížení následujícím způsobem:

  1. Změřte celkové doby čekání během reprezentativního testu.
  2. Seřaďte je.
  3. Určete poměr dob čekání, které souvisejí se západkami.

Kumulativní informace o čekání jsou k dispozici z sys.dm_os_wait_stats DMV. Nejběžnějším typem kolize západky je kolize západky vyrovnávací paměti, pozorované jako zvýšení doby čekání na západky s wait_typePAGELATCH_*. Západky nevyužívající vyrovnávací paměť jsou zařazeny pod typem LATCH* čekání. Jak znázorňuje následující diagram, měli byste nejprve se kumulativním pohledem na čekání systému pomocí sys.dm_os_wait_stats zobrazení dynamické správy určit procento celkové doby čekání způsobené vyrovnávací pamětí nebo západkami mimo vyrovnávací paměť. Pokud narazíte na západky, které nejsou vyrovnávací, je nutné také prozkoumat zobrazení dynamické správy sys.dm_os_latch_stats.

Následující diagram popisuje vztah mezi informacemi vrácenými zobrazeními dynamické správy sys.dm_os_wait_stats a sys.dm_os_latch_stats.

Diagram čekání synchronizačního zámku

Další informace o zobrazení dynamické správy sys.dm_os_wait_stats najdete v části nápovědy k SQL Serveru, v tématu sys.dm_os_wait_stats.

Další informace o sys.dm_os_latch_stats zobrazení dynamické správy najdete v tématu sys.dm_os_latch_stats nápovědy k SQL Serveru.

Následující míry doby čekání západky jsou indikátory, že nadměrné kolize západek ovlivňuje výkon aplikace:

  • Průměrná doba čekání na stránkovou západku se konzistentně zvyšuje s propustností: Pokud se průměrné doby čekání na stránkovou západku konzistentně zvyšují s propustností a průměrná doba čekání na vyrovnávací západku se také zvýší nad očekávanou dobu odezvy disku, měli byste prozkoumat aktuální čekající úlohy pomocí pohledu dynamického řízení. Průměry můžou být zavádějící, pokud jsou analyzovány izolovaně, takže je důležité se na systém podívat živě, pokud je to možné, abyste porozuměli charakteristikám úloh. Konkrétně zkontrolujte, zda na libovolné stránce nejsou vysoká očekávání požadavky PAGELATCH_EX nebo PAGELATCH_SH. Chcete-li diagnostikovat zvyšující se průměrné čekací doby na zámku stránky v kontextu propustnosti, postupujte takto:

    Poznámka:

    Pro výpočet průměrné doby čekání pro určitý typ čekání (označený sys.dm_os_wait_stats jako wt_:type), vydělte celkovou dobu čekání (označenou jako wait_time_ms) počtem čekajících úkolů (označených jako waiting_tasks_count).

  • Procento celkové doby čekání strávené na druzích čekání na západky během špičkového zatížení: Pokud se průměrná doba čekání na západku v procentech celkové doby čekání zvyšuje v souladu se zatížením aplikace, může západka ovlivňovat výkon a měla by být prošetřena.

    Měřte čekání na zajištění stránky a nezajištění stránky pomocí čítačů výkonu objektu Statistika čekání SQL Server. Pak porovnejte hodnoty těchto čítačů výkonu s čítači výkonu přidruženými k procesoru, vstupně-výstupním operacím, paměti a propustnosti sítě. Například počet transakcí za sekundu a dávkových žádostí za sekundu jsou dvě dobré míry využití prostředků.

    Poznámka:

    Relativní doba čekání pro každý typ čekání není zahrnuta v sys.dm_os_wait_stats DMV, protože toto DMV měří doby čekání od posledního spuštění instance SQL Serveru nebo od resetování kumulativních statistik čekání pomocí DBCC SQLPERF. Chcete-li vypočítat relativní dobu čekání pro každý typ čekání, pořiďte snímek sys.dm_os_wait_stats před zátěží ve špičce, po zátěži ve špičce, a pak vypočítejte rozdíl. K tomuto účelu lze použít ukázkový skript Calculate Waits Over a Time Period (Výpočet čekání v průběhu časového období ).

    Pouze pro neprodukční prostředí vymažte sys.dm_os_wait_stats zobrazení dynamické správy pomocí následujícího příkazu:

    DBCC SQLPERF ('sys.dm_os_wait_stats', 'CLEAR');
    

    Spustit podobný příkaz pro vyprázdnění sys.dm_os_latch_stats DMV (zobrazení dynamické správy):

    DBCC SQLPERF ('sys.dm_os_latch_stats', 'CLEAR');
    
  • Propustnost se nezvyšuje a v některých případech se snižuje zatížení aplikace a zvyšuje se počet procesorů dostupných pro SQL Server: To bylo znázorněno v příkladu kolizí latch.

  • Využití procesoru se nezvyšuje s tím, jak se zvyšuje zatížení aplikace: Pokud se využití procesoru v systému nezvýší s ohledem na současnost řízenou propustností aplikace, jedná se o indikátor, že SQL Server čeká na určitou operaci, což je příznak kolize západek.

Analýza původní příčiny I když je každá z předchozích podmínek pravdivá, stále je možné, že původní příčina problémů s výkonem leží jinde. Ve většině případů je neoptimální využití procesoru způsobeno jinými typy čekání, jako jsou blokování zámků, čekání související s vstupně-výstupními operacemi nebo problémy související se sítí. Obecně platí, že je vždy nejlepší vyřešit čekání na prostředky, které představuje největší podíl celkové doby čekání, než budete pokračovat v podrobnější analýze.

Analyzovat aktuální čekací západky vyrovnávací paměti

Kolize zámků vyrovnávací paměti se projevuje jako zvýšení doby čekání na zámky s wait_type buď PAGELATCH_* nebo PAGEIOLATCH_*, jak je uvedeno v sys.dm_os_wait_stats pohledu dynamické správy. Pokud se chcete podívat na systém v reálném čase, spusťte následující dotaz v systému, který propojí zobrazení dynamické správy sys.dm_os_wait_stats, sys.dm_exec_sessions a sys.dm_exec_requests. Výsledky lze použít k určení aktuálního typu čekání na relace spuštěné na serveru.

SELECT wt.session_id,
       wt.wait_type,
       er.last_wait_type AS last_wait_type,
       wt.wait_duration_ms,
       wt.blocking_session_id,
       wt.blocking_exec_context_id,
       resource_description
FROM sys.dm_os_waiting_tasks AS wt
     INNER JOIN sys.dm_exec_sessions AS es
         ON wt.session_id = es.session_id
     INNER JOIN sys.dm_exec_requests AS er
         ON wt.session_id = er.session_id
WHERE es.is_user_process = 1
      AND wt.wait_type <> 'SLEEP_TASK'
ORDER BY wt.wait_duration_ms DESC;

Snímek obrazovky typu čekání pro provádění relací

Statistiky zveřejněné tímto dotazem jsou popsány takto:

Statistika Popis
session_id ID relace přidružené k úkolu.
wait_type Typ čekání, který SQL Server zaznamenal v enginu, což brání spuštění aktuálního požadavku.
last_wait_type Pokud byl tento požadavek dříve zablokovaný, vrátí tento sloupec typ posledního čekání. Není nastavitelný na null.
wait_duration_ms Celková doba čekání v milisekundách strávená čekáním na tento typ od spuštění instance SQL Serveru nebo od vynulování kumulativních statistik čekání.
blocking_session_id ID relace, která blokuje požadavek.
blocking_exec_context_id ID kontextu spuštění přidruženého k úloze.
resource_description Sloupec resource_description uvádí přesnou stránku, na kterou se čeká ve formátu: <database_id>:<file_id>:<page_id>

Následující dotaz vrátí informace pro všechny nevyrovnávací paměťové zámky:

SELECT * FROM sys.dm_os_latch_stats
WHERE latch_class <> 'BUFFER'
ORDER BY wait_time_ms DESC;

Snímek obrazovky s výstupem dotazu

Statistiky zveřejněné tímto dotazem jsou popsány takto:

Statistika Popis
latch_class Typ západky, kterou SQL Server zaznamenal v modulu, což brání spuštění aktuálního požadavku.
waiting_requests_count Celkový počet čekání na uzamykací mechanismy v této třídě od restartování SQL Serveru. Tento čítač se inkrementuje na začátku čekání na blokování.
wait_time_ms Celková doba čekání v milisekundách strávená čekáním na tento typ západky.
max_wait_time_ms Maximální doba v milisekundách u všech požadavků strávených čekáním na tento typ západky.

Hodnoty vrácené tímto DMV jsou kumulativní od posledního restartování databázového engine nebo resetování DMV. Pomocí sloupce sqlserver_start_time v sys.dm_os_sys_info vyhledejte čas posledního spuštění databázového stroje. V systému, který běží dlouho, to znamená, že některé statistiky, jako max_wait_time_ms jsou zřídka užitečné. K resetování statistik čekání pro tuto DMV můžete použít následující příkaz:

DBCC SQLPERF ('sys.dm_os_latch_stats', CLEAR);

Scénáře kolize západek SQL Serveru

Byly pozorovány následující scénáře, které způsobují nadměrné soutěžení o západky.

Spor při vkládání poslední nebo následující stránky

Běžným postupem OLTP je vytvoření clusterovaného indexu ve sloupci identity nebo data. To pomáhá udržovat dobrou fyzickou organizaci indexu, což může výrazně těžit z výkonu čtení i zápisů do indexu. Tento návrh schématu však může neúmyslně vést ke kolizím západek. K tomuto problému nejčastěji dochází u velké tabulky s malými řádky a při vkládání do indexu, který obsahuje vedoucí klíčový sloupec se sekvenčně rostoucí hodnotou, například vzestupné celé číslo nebo datum a čas. V tomto scénáři aplikace zřídka, pokud vůbec, provádí aktualizace nebo mazání, výjimku tvoří operace archivace.

V následujícím příkladu vlákno jeden a vlákno dva chtějí provést vložení záznamu, který bude uložen na stránce 299. Z logického hlediska uzamčení není problém, protože se používají zámky na úrovni řádků a výhradní zámky na obou záznamech na stejné stránce se dají uchovávat současně. Pokud však chcete zajistit integritu fyzické paměti, může pouze jedno vlákno získat výhradní západku, takže přístup ke stránce je serializován, aby se zabránilo ztrátě aktualizací v paměti. V tomto případě vlákno 1 získá výhradní západku, zatímco vlákno 2 čeká, což zaregistruje PAGELATCH_EX čekání na tento prostředek ve statistikách čekání. Toto je zobrazeno prostřednictvím hodnoty wait_type v zobrazení sys.dm_os_waiting_tasks DMV.

Diagram výhradního zámku stránky na posledním řádku

Tento souběh se běžně označuje jako "Vložení poslední stránky", protože k němu dochází na pravém okraji B-stromu, jak je znázorněno v následujícím diagramu:

Diagram sporu při vkládání poslední stránky

Tento typ kolize západek lze vysvětlit následujícím způsobem. Když se do indexu vloží nový řádek, SQL Server použije k provedení změny následující algoritmus:

  1. Procházením B-stromu vyhledejte správnou stránku pro uložení nového záznamu.

  2. Zamkněte stránku PAGELATCH_EX, abyste zabránili ostatním v její úpravě, a získejte sdílené západky (PAGELATCH_SH) na všech nelistových stránkách.

    Poznámka:

    V některých případech vyžaduje SQL Engine také držáky EX na nelistových stránkách B-stromu. Například když dojde k rozdělení stránky, všechny stránky, které jsou přímo ovlivněny, musí být výhradně zamknuty (PAGELATCH_EX).

  3. Záznam položky protokolu, kterou byl řádek změněn.

  4. Přidejte řádek na stránku a označte stránku jako špinavou.

  5. Uvolněte všechny stránky.

Pokud index tabulky vychází z postupně se zvyšujícího klíče, každé nové vložení přejde na stejnou stránku na konci stromu B, dokud nebude tato stránka plná. Ve scénářích s vysokou souběžností to může způsobit kolize na pravém okraji stromu B a může nastat v clusterovaných a neskupených indexech. Tabulky, které jsou ovlivněny tímto typem kolizí, primárně přijímají INSERT dotazy a stránky problematických indexů jsou obvykle poměrně zhuštěné (například velikost řádku ~165 bajtů (včetně režijních nákladů na řádek) se rovná ~49 řádků na stránku). V tomto příkladu náročném na vložení očekáváme PAGELATCH_EX/PAGELATCH_SH , že dojde k čekání a toto je typické pozorování. Chcete-li prozkoumat čekání na zamknutí stránky ve srovnání s čekáním na zamknutí stromové stránky, použijte zobrazení dynamické správy sys.dm_db_index_operational_stats.

Následující tabulka shrnuje hlavní faktory pozorované tímto typem kolize západek:

Faktor Typické pozorování
Logické procesory používané SQL Serverem Tento typ kolize západek se vyskytuje hlavně na systémech se 16 a více procesorovými jádry a nejčastěji na systémech s 32 a více jádry.
Vzory návrhu schématu a přístupu Používá sekvenčně se zvyšující hodnotu identity jako hlavní sloupec v indexu tabulky pro transakční data.

Index má rostoucí primární klíč s vysokou frekvencí vkládání.

Index má alespoň jednu sekvenční rostoucí hodnotu sloupce.

Obvykle malá velikost řádku s mnoha řádky na stránku.
Zjištěný typ čekání Mnoho vláken usilujících o stejný prostředek s výhradním (EX) nebo sdíleným čekáním na západek (SH) přidruženým ke stejnému resource_description v sys.dm_os_waiting_tasks zobrazení dynamické správy, jak vrací dotaz sys.dm_os_waiting_tasks seřazené podle doby čekání.
Faktory návrhu, které je potřeba zvážit Zvažte změnu pořadí sloupců indexu, jak je popsáno ve strategii pro zmírnění rizik nesekvenčního indexu, pokud můžete zaručit, že se vložení budou vždy rovnoměrně distribuovat napříč stromem B.

Pokud se použije strategie mitigace hashového dělení, odstraní možnost využití dělení pro jakékoli jiné účely, například k archivaci posuvného okna.

Použití strategie zmírnění počtu oddílů hash může vést k problémům s odstraněním oddílů u SELECT dotazů používaných aplikací.

Kolize synchronizace u malých tabulek s neklastrovaným indexem a náhodným vkládáním (frontová tabulka)

Tento scénář se obvykle používá, když se tabulka SQL používá jako dočasná fronta (například v asynchronním systému zasílání zpráv).

V tomto scénáři může dojít k exkluzivní (EX) a sdílené kontenzi západky (SH) za následujících podmínek:

  • Operace vložení, výběru, aktualizace nebo odstranění probíhají v rámci vysoké souběžnosti.
  • Velikost řádku je relativně malá (což vede k hustým stránkám).
  • Počet řádků v tabulce je relativně malý, což vede k mělkému B-Tree, který má hloubku indexu dvě nebo tři.

Poznámka:

I B-stromy s větší hloubkou než tato mohou zaznamenat konkurenci s tímto typem přístupového vzoru, pokud je frekvence používání jazyka pro manipulaci s daty (DML) a míra paralelnosti systému dostatečně vysoká. Úroveň kolize západek se může stát výrazným, protože souběžnost se zvyšuje, když je pro systém k dispozici 16 nebo více jader procesoru.

Kolize západek může nastat, i když je přístup k B-stromu náhodný, například když je v clusterovém indexu nesekvenční sloupec jako počáteční klíč. Následující snímek obrazovky pochází ze systému, u kterého dochází k tomuto typu soutěžení o západku. V tomto příkladu je kolize způsobená hustotou stránek způsobených malou velikostí řádku a relativně mělkým B-stromem. S nárůstem souběžnosti dochází ke kolizím západek na stránkách, i když vkládání probíhá náhodně napříč B-stromem, protože prvním sloupcem v indexu byl identifikátor GUID.

Na následujícím snímku obrazovky se čekání vyskytují na stránkách vyrovnávací paměti databufferu i na stránkách volného úložného prostoru (PFS). I když se zvýšil počet datových souborů, konflikt západek na datových stránkách vyrovnávací paměti byl běžný.

Snímek obrazovky s typy čekání

Následující tabulka shrnuje hlavní faktory pozorované tímto typem kolize západek:

Faktor Typické pozorování
Logické procesory používané SQL Serverem Kolize zámků se vyskytuje hlavně na počítačích s více než 16 jádry CPU.
Vzory návrhu schématu a přístupu Vysoká rychlost vložení, výběru, aktualizace nebo odstranění vzorů přístupu u malých tabulek.

Mělký B-strom (hloubka indexu dvou nebo tří).

Malá velikost řádku (mnoho záznamů na stránku)
Úroveň souběžnosti Spor o západky se vyskytuje pouze při vysokém počtu souběžných požadavků z aplikační vrstvy.
Zjištěný typ čekání Sledujte čekání na vyrovnávací paměť (PAGELATCH_EX a PAGELATCH_SH) a na západku ACCESS_METHODS_HOBT_VIRTUAL_ROOT nezávislou na vyrovnávací paměti kvůli rozštěpům kořene. Také PAGELATCH_UP čeká na stránkách PFS. Další informace o čekání na zámky mimo vyrovnávací paměť naleznete v nápovědě k SQL Serveru pod sys.dm_os_latch_stats.

Kombinace mělkého B-stromu a náhodných vložení do indexu snadno způsobí rozdělení stránek v B-stromu. Aby bylo možné provést rozdělení stránky, musí SQL Server získat sdílené (SH) západky na všech úrovních a pak získat výhradní (EX) západky na stránkách ve stromu B, které jsou součástí rozdělení stránky. Pokud je souběžnost vysoká a data se průběžně vkládají a odstraňují, může dojít k rozdělení kořene stromu B. V tomto případě mohou další vložení čekat na všechny západky bez vyrovnávací paměti získané ve stromu B. To se projevuje jako velký počet čekání na ACCESS_METHODS_HOBT_VIRTUAL_ROOT typ západky pozorovaný v sys.dm_os_latch_stats zobrazení dynamické správy.

Následující skript lze upravit a určit hloubku stromu B pro indexy v ovlivněné tabulce.

SELECT
    o.name AS [table],
    i.name AS [index],
    indexProperty(object_id(o.name), i.name, 'indexDepth') + indexProperty(object_id(o.name), i.name, 'isClustered') AS depth, --clustered index depth reported doesn't count leaf level
    i.[rows] AS [rows],
    i.origFillFactor AS [fillFactor],
    CASE (indexProperty(object_id(o.name), i.name, 'isClustered'))
        WHEN 1 THEN 'clustered'
        WHEN 0 THEN 'nonclustered'
        ELSE 'statistic'
    END AS type
FROM sysIndexes AS i
     INNER JOIN sysObjects AS o
         ON o.id = i.id
WHERE o.type = 'u'
      AND indexProperty(object_id(o.name), i.name, 'isHypothetical') = 0 --filter out hypothetical indexes
      AND indexProperty(object_id(o.name), i.name, 'isStatistics') = 0 --filter out statistics
ORDER BY o.name;

Kolize západek na stránkách volného místa stránky (PFS)

PFS je zkratka pro volné místo stránky, SQL Server přiděluje jednu stránku PFS pro každých 8088 stránek (počínaje PageID = 1) v každém souboru databáze. Každý bajt na stránce PFS zaznamenává informace, včetně toho, kolik volného místa je na stránce, pokud je přiděleno nebo ne a jestli stránka ukládá stínové záznamy. Stránka PFS obsahuje informace o stránkách, které jsou k dispozici pro přidělení, pokud je nová stránka vyžadována operací vložení nebo aktualizace. Stránka PFS musí být aktualizována v několika scénářích, včetně případů, kdy dojde k přidělení nebo zrušení přidělení. Vzhledem k tomu, že použití zámku aktualizace (UP) je vyžadováno k ochraně stránek PFS, může dojít ke konfliktu zámků na stránkách PFS, pokud máte v souborové skupině poměrně málo datových souborů a velký počet jader procesoru. Jednoduchým způsobem, jak to vyřešit, je zvýšit počet souborů na skupinu souborů.

Výstraha

Zvýšení počtu souborů na skupinu souborů může nepříznivě ovlivnit výkon určitých zatížení, například zatížení s mnoha velkými operacemi řazení, které přetékají paměť na disk.

Pokud je u stránek PAGELATCH_UPPFS nebo SGAM zjištěno mnoho tempdb čekání, pomocí těchto kroků tento kritický bod odstraňte:

  1. Přidejte datové soubory tak tempdb , aby se počet datových souborů tempdb rovnal počtu jader procesoru na vašem serveru.

  2. Povolte příznak trasování SQL Serveru 1118.

Další informace o kritických bodech přidělení způsobených kolizemi na systémových stránkách najdete v blogovém příspěvku Co je kritický bod přidělení?

Tabulkové funkce a kolize západek v databázi tempdb

Existují i další faktory nad rámec kolize přidělení, které můžou způsobit kolize tempdb západek, jako je například intenzivní použití TVF v rámci dotazů.

Řešení konfliktu zámků pro různé vzory tabulek

Následující části popisují techniky, které je možné použít k řešení nebo obejití problémů s výkonem souvisejících s nadměrným soutěžením o západky.

Použití nekvenčního úvodního indexového klíče

Jednou z metod zpracování kolize západek je nahrazení sekvenčního indexového klíče nesekvenčním klíčem, který rovnoměrně distribuuje vložení do rozsahu indexu.

Obvykle se toho dosahuje tím, že hlavní sloupec v indexu distribuuje zatížení úměrně. Tady je několik možností:

Možnost: Použití sloupce v tabulce k distribuci hodnot napříč rozsahem klíčů indexu

Vyhodnoťte své pracovní zatížení pro přirozenou hodnotu, kterou můžete použít k distribuci vkládání napříč rozsahem klíčů. Představte si například scénář bankovnictví ATM, kde ATM_ID může být vhodným kandidátem na distribuci vložení do transakční tabulky pro výběry, protože jeden zákazník může současně používat pouze jeden bankomat. Podobně by v prodejním systému mohlo Checkout_ID nebo ID obchodu být přirozenou hodnotou, která by se dala použít k distribuci insertů napříč rozsahy klíčů. Tato technika vyžaduje vytvoření složeného indexového klíče s počátečním klíčovým sloupcem, který je buď hodnotou identifikovaného sloupce, nebo hodnotou hash této hodnoty v kombinaci s jedním nebo více nadbytečnými sloupci za účelem zajištění jedinečnosti. Ve většině případů funguje hash hodnoty optimálně, protože příliš mnoho jedinečných hodnot vede k špatné fyzické organizaci. Například v pokladním systému je možné vytvořit hodnotu hash z ID obchodu pomocí nějaké modulo operace, která odpovídá počtu jader procesoru. Výsledkem této techniky by byl relativně malý počet oblastí v tabulce, ale bylo by stačit distribuovat vložení takovým způsobem, aby nedocházelo ke kolizím západek. Tento postup znázorňuje následující obrázek.

Snímek obrazovky s vloženími po použití nesekvenčního indexu

Důležité

Tento model je v rozporu s tradičními osvědčenými postupy indexování. I když tato technika pomáhá zajistit rovnoměrné rozdělení vložení napříč stromem B, může také vyžadovat změnu schématu na úrovni aplikace. Tento model může navíc negativně ovlivnit výkon dotazů, které vyžadují kontroly rozsahu, které využívají clusterovaný index. K určení, jestli tento přístup k návrhu dobře funguje, je potřeba provést analýzu vzorů úloh. Tento model by se měl implementovat, pokud se vám podaří obětovat určitý výkon sekvenčního prohledávání, abyste získali propustnost vkládání a škálovatelnost.

Tento vzor byl implementován během zapojení v laboratoři výkonu a vyřešil západkovou konkurenci v systému s 32 fyzickými jádry procesoru. Tabulka byla použita k uložení konečného zůstatku na konci transakce; každá obchodní transakce prováděla jednu vložení do tabulky.

Původní definice tabulky

Při použití původní definice tabulky došlo k nadměrné kolizi západky na clustrovaném indexu pk_table1.

CREATE TABLE table1
(
    TransactionID BIGINT NOT NULL,
    UserID INT NOT NULL,
    SomeInt INT NOT NULL
);
GO

ALTER TABLE table1
    ADD CONSTRAINT pk_table1 PRIMARY KEY CLUSTERED (TransactionID, UserID);
GO

Poznámka:

Názvy objektů v definici tabulky byly změněny z původních hodnot.

Změna pořadí definice indexu

Změna pořadí klíčových sloupců indexu tak, že sloupec UserID byl úvodním sloupcem v indexu na primární klíč, poskytla téměř náhodné rozdělení vkládání dat na stránkách. Výsledná distribuce nebyla 100% náhodná, protože ne všichni uživatelé jsou online současně, ale byla dostatečně náhodná, aby zmírnila nadměrné zatížení synchronizačních zámků. Jedním z rizik změny pořadí definice indexu je, že všechny výběrové dotazy na tuto tabulku musí být upraveny tak, aby používaly jak UserID, tak i TransactionID jako predikáty rovnosti.

Důležité

Před spuštěním v produkčním prostředí se ujistěte, že důkladně otestujete všechny změny v testovacím prostředí.

CREATE TABLE table1
(
    TransactionID BIGINT NOT NULL,
    UserID INT NOT NULL,
    SomeInt INT NOT NULL
);
GO

ALTER TABLE table1
    ADD CONSTRAINT pk_table1 PRIMARY KEY CLUSTERED (UserID, TransactionID);
GO

Použití hodnoty hash jako počátečního sloupce v primárním klíči

Následující definice tabulky lze použít k vygenerování modulu, který odpovídá počtu procesorů, HashValue je generován pomocí sekvenční rostoucí hodnoty TransactionID , aby se zajistilo jednotné rozdělení napříč B-Tree:

CREATE TABLE table1
(
    TransactionID BIGINT NOT NULL,
    UserID INT NOT NULL,
    SomeInt INT NOT NULL
);
GO

-- Consider using bulk loading techniques to speed it up
ALTER TABLE table1
    ADD [HashValue] AS (CONVERT (TINYINT, ABS([TransactionID]) % (32))) PERSISTED NOT NULL;

ALTER TABLE table1
    ADD CONSTRAINT pk_table1 PRIMARY KEY CLUSTERED (HashValue, TransactionID, UserID);
GO

Možnost: Jako úvodní klíčový sloupec indexu použijte identifikátor GUID.

Pokud neexistuje žádný přirozený oddělovač, můžete sloupec GUID použít jako úvodní klíčový sloupec indexu, aby se zajistilo jednotné rozdělení vložení. I když použití identifikátoru GUID jako vedoucího sloupce v klíči indexu umožňuje využití particionování pro jiné funkce, může tato technika také přinést potenciální nevýhody, jako je větší počet rozdělení stránek, špatná fyzická organizace a nízká hustota stránek.

Poznámka:

Použití identifikátorů GUID jako hlavních klíčových sloupců indexů je vysoce diskutované téma. Podrobná diskuze o výhodách a nevýhodách této metody spadá mimo rozsah tohoto článku.

Použijte hashové dělení s výpočtovým sloupcem

Dělení tabulek v SQL Serveru lze použít ke zmírnění nadměrného zatížení latchů. Vytvoření schématu hash rozdělení s vypočítaným sloupcem na rozdělené tabulce je běžný přístup, který lze provést pomocí těchto kroků:

  1. Vytvořte novou skupinu souborů nebo použijte existující skupinu souborů k uložení oddílů.

  2. Pokud používáte novou skupinu souborů, rovnoměrně vyrovnejte jednotlivé soubory přes logickou jednotku a dbejte na optimální rozložení. Pokud vzor přístupu zahrnuje vysokou míru vkládání, nezapomeňte vytvořit stejný počet souborů jako fyzické jádra procesoru na počítači s SQL Serverem.

  3. CREATE PARTITION FUNCTION Pomocí příkazu rozdělte tabulky do oddílů X, kde X je počet fyzických jader procesoru na počítači s SQL Serverem. (alespoň až 32 oddílů)

    Poznámka:

    Zarovnání 1:1 počtu oddílů na počet jader procesoru není vždy nutné. V mnoha případech to může být hodnota menší než počet jader procesoru. Pokud máte více oddílů, může to mít za následek větší náročnost dotazů, které musí prohledávat všechny oddíly, a v těchto případech může pomoct mít méně oddílů. Testování SQLCAT na systémech se 64 a 128 logickými CPU s reálnými pracovními zátěžemi zákazníků ukázalo, že 32 oddílů bylo dostačujících k vyřešení nadměrné contention latche a dosažení cílů škálování. V konečném důsledku by se měl určit ideální počet oddílů prostřednictvím testování.

  4. Použijte příkaz CREATE PARTITION SCHEME:

    • Vytvořte vazbu funkce oddílu na skupiny souborů.
    • Přidejte do tabulky sloupec hash typu tinyint nebo smallint.
    • Vypočítejte dobrou distribuci hodnot hash. Například použijte HASHBYTES s modulo nebo BINARY_CHECKSUM.

Následující ukázkový skript lze přizpůsobit pro účely implementace:

--Create the partition scheme and function, align this to the number of CPU cores 1:1 up to 32 core computer
-- so for below this is aligned to 16 core system
CREATE PARTITION FUNCTION [pf_hash16](TINYINT)
    AS RANGE LEFT
    FOR VALUES (0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15);

CREATE PARTITION SCHEME [ps_hash16]
    AS PARTITION [pf_hash16]
    ALL TO ([ALL_DATA]);
-- Add the computed column to the existing table (this is an OFFLINE operation)

-- Consider using bulk loading techniques to speed it up
ALTER TABLE [dbo].[latch_contention_table]
    ADD [HashValue] AS (CONVERT (TINYINT, ABS(BINARY_CHECKSUM([hash_col]) % (16)), (0))) PERSISTED NOT NULL;

--Create the index on the new partitioning scheme
CREATE UNIQUE CLUSTERED INDEX [IX_Transaction_ID]
    ON [dbo].[latch_contention_table]([T_ID] ASC, [HashValue])
    ON ps_hash16 (HashValue);

Tento skript lze použít k hashování oddílů tabulky, u které dochází k problémům způsobeným kolizemi vložení poslední stránky nebo koncové stránky. Tato technika přesune kolize z poslední stránky rozdělením tabulky a distribucí vložení mezi oddíly tabulky pomocí operace modulu hodnot hash.

Co dělá hashové rozdělení s vypočítaným sloupcem

Jak znázorňuje následující diagram, tato technika přesune kolize z poslední stránky opětovným sestavením indexu na funkci hash a vytvořením stejného počtu oddílů jako fyzické jádra procesoru na počítači s SQL Serverem. Vložení stále probíhá na konec logického rozsahu (sekvenční zvýšení hodnoty), ale operace modulu hodnot hash zajišťuje rozdělení vložení mezi různé stromy B, což snižuje kritický bod. To je znázorněno v následujících diagramech:

Diagram kolize západky stránky při posledním vložení stránky

Diagram konfliktu zámků stránky řešeného pomocí dělení.

Kompromisy při použití rozdělování pomocí hashování

Zatímco rozklad podle hashů může eliminovat obsazení při vkládání, existuje několik kompromisů, které je třeba zvážit při rozhodování, zda tuto techniku použít.

  • Výběrové dotazy ve většině případů je potřeba upravit tak, aby zahrnovaly oddíl hash do predikátu a vést k plánu dotazů, který při vydání těchto dotazů neposkytuje žádné odstranění oddílů. Následující snímek obrazovky ukazuje chybný plán bez odstranění oddílu po implementaci hashového dělení.

    Snímek obrazovky s plánem dotazu bez odstranění particí

  • Zabraňuje eliminaci partition u některých dotazů, jako jsou například sestavy založené na rozsahu.

  • Pokud chcete dosáhnout eliminace oddílů při spojování tabulky rozdělené podle hodnoty hash s jinou tabulkou, musí být tato druhá tabulka rovněž rozdělena podle stejného klíče a hashovací klíč by měl být součástí kritérií pro spojení.

  • Dělení hash brání použití dělení pro další funkce správy, jako je archivace s posuvným oknem a funkce přepínání oddílů.

Dělení hash je účinná strategie pro zmírnění nadměrné kolize západek, protože zvyšuje celkovou propustnost systému tím, že snižuje kolize při vkládání. Vzhledem k tomu, že existují určité kompromisy, nemusí být optimálním řešením pro některé vzory přístupu.

Shrnutí technik používaných k řešení kolizí západky

Následující dvě části obsahují souhrn technik, které lze použít k řešení nadměrného zatížení západky:

Nesekvenční klíč nebo index

Výhody:

  • Umožňuje používat další funkce dělení, jako je archivace dat pomocí posuvného schématu oken a funkce přepínání oddílů.

Nevýhody:

  • Možné výzvy při výběru klíče nebo indexu, aby se zajistilo 'dostatečně' jednotné rozdělení vkládání po celou dobu.
  • Identifikátor GUID lze jako počáteční sloupec použít k zajištění jednotné distribuce, s výhradou, že to může vést k nadměrným operacím rozdělení stránek.
  • Náhodné vložení v rámci B-stromu může vést k příliš mnoha operacím rozdělení stránek a vést k soutěžení o zámky na nolistových stránkách.

Dělení hash pomocí počítaného sloupce

Výhody:

  • Průhledné pro vložení.

Nevýhody:

  • Dělení nelze použít pro zamýšlené funkce správy, jako je archivace dat pomocí možností přepínání oddílů.
  • Může způsobit problémy s odstraněním oddílů pro dotazy, včetně jednotlivých výběrů nebo aktualizací založených na rozsahu a dotazů, které provádějí spojení.
  • Přidání trvalého počítaného sloupce je offline operace.

Návod

Další techniky najdete v blogovém příspěvku PAGELATCH_EX čekání a těžké vkládání.

Návod: Diagnostika kolize latchů

Následující postup ukazuje nástroje a techniky popsané v Diagnostikování kolize západek v SQL Serveru a Zpracování kolize západek pro různé vzory tabulek k vyřešení problému v reálném světovém scénáři. Tento scénář popisuje zapojení zákazníků k provádění zátěžového testování prodejního systému, který simuloval přibližně 8 000 obchodů provádějících transakce s aplikací SQL Serveru běžící na 8 soketu, 32 fyzického základního systému s 256 GB paměti.

Následující diagram podrobně popisuje hardware použitý k otestování prodejního bodu:

Diagram testovacího prostředí prodejního systému

Příznak: Horké západky

V tomto případě jsme zaznamenali vysoké čekání, kdy PAGELATCH_EX obvykle definujeme vysokou hodnotu jako průměr více než 1 ms. V tomto případě jsme konzistentně zaznamenali čekání přesahující 20 ms.

Snímek obrazovky s aktivními západkami

Jakmile jsme zjistili, že kolize západky je problematická, začali jsme následně zjišťovat, co ji způsobuje.

Izolace objektu způsobujícího kolize západek

Následující skript používá sloupec resource_description k izolaci, který index způsobil PAGELATCH_EX kolizí:

Poznámka:

Sloupec resource_description vrácený tímto skriptem poskytuje popis prostředku ve formátu <DatabaseID,FileID,PageID>, kde lze název databáze přidružené k DatabaseID určit předáním hodnoty DatabaseID do funkce DB_NAME().

SELECT wt.session_id,
       wt.wait_type,
       wt.wait_duration_ms,
       s.name AS schema_name,
       o.name AS object_name,
       i.name AS index_name
FROM sys.dm_os_buffer_descriptors AS bd
     INNER JOIN (SELECT *,
             --resource_description
             CHARINDEX(':', resource_description) AS file_index,
             CHARINDEX(':', resource_description, CHARINDEX(':', resource_description) + 1) AS page_index,
             resource_description AS rd
      FROM sys.dm_os_waiting_tasks AS wt
      WHERE wait_type LIKE 'PAGELATCH%') AS wt
     ON bd.database_id = SUBSTRING(wt.rd, 0, wt.file_index)
        AND bd.file_id = SUBSTRING(wt.rd, wt.file_index + 1, 1) --wt.page_index)
        AND bd.page_id = SUBSTRING(wt.rd, wt.page_index + 1, LEN(wt.rd))
     INNER JOIN sys.allocation_units AS au
         ON bd.allocation_unit_id = au.allocation_unit_id
     INNER JOIN sys.partitions AS p
         ON au.container_id = p.partition_id
     INNER JOIN sys.indexes AS i
         ON p.index_id = i.index_id
        AND p.object_id = i.object_id
     INNER JOIN sys.objects AS o
         ON i.object_id = o.object_id
     INNER JOIN sys.schemas AS s
         ON o.schema_id = s.schema_id
ORDER BY wt.wait_duration_ms DESC;

Jak je znázorněno zde, sporný bod je v tabulce LATCHTEST a v názvu indexu CIX_LATCHTEST. Názvy poznámek byly změněny tak, aby se úloha anonymizovala.

Snímek obrazovky s konfliktem LATCHTEST

Pokud hledáte pokročilejší skript, který se opakovaně dotazuje a používá dočasnou tabulku k určení celkové doby čekání v konfigurovatelném období, naleznete jej v příloze v části Popisovače vyrovnávací paměti dotazu k určení objektů způsobujících konflikt západky.

Alternativní technika izolace objektu způsobujícího konflikt zámků

Někdy může být nepraktické dotazovat sys.dm_os_buffer_descriptors. Jak se paměť v systému a dostupná pro fond vyrovnávací paměti zvyšuje, zvyšuje se i doba potřebná ke spuštění tohoto zobrazení dynamické správy. V systému s kapacitou 256 GB může provedení tohoto dynamického zobrazení správy trvat až 10 minut nebo déle. K dispozici je alternativní technika a je obecně popsána následujícím způsobem a je znázorněna s jinou úlohou, kterou jsme spustili v testovacím prostředí:

  1. Dotaz na aktuálně čekající úkoly pomocí skriptu dodatku Dotaz sys.dm_os_waiting_tasks seřazeno podle doby čekání.

  2. Identifikujte klíčovou stránku, na které se pozoruje konvoj, ke kterému dochází v případě, že na stejné stránce soupeří více vláken. V tomto příkladu soupeří vlákna provádějící vložení o koncovou stránku ve stromu B a čekají, až mohou získat západku EX. To je indikováno resource_description v prvním dotazu, v našem případě 8:1:111305.

  3. Povolte trasovací příznak 3604, který odhaluje další informace o stránce pomocí DBCC PAGE podle následující syntaxe. Nahraďte hodnotu, kterou jste získali prostřednictvím popisu zdroje (resource_description), za hodnotu v závorkách.

    Povolte příznak trasování 3604 pro zobrazení výstupu na konzoli:

    DBCC TRACEON (3604);
    

    Prozkoumejte podrobnosti stránky:

    DBCC PAGE (8, 1, 111305, -1);
    
  4. Prozkoumejte výstup DBCC. V našem případě 78623323by mělo existovat přidružené ID objektu metadat .

    Snímek obrazovky s ID objektu metadat

  5. Nyní můžeme spustit následující příkaz k určení názvu objektu, který způsobuje kolizí, což je LATCHTESTpodle očekávání .

    Poznámka:

    Ujistěte se, že jste ve správném kontextu databáze, jinak dotaz vrátí NULL.

    --get object name
    SELECT OBJECT_NAME(78623323);
    

    Snímek obrazovky s názvem objektu

Souhrn a výsledky

Pomocí výše uvedené techniky jsme mohli potvrdit, že kolize probíhala v clusterovém indexu s sekvenčním zvýšením hodnoty klíče v tabulce, která zdaleka přijala nejvyšší počet vložení. Tento typ kolize není neobvyklý pro indexy s klíči, jejichž hodnota se sekvenčně zvyšuje, jako je datetime, identity nebo aplikací generovaná TransactionID.

K vyřešení tohoto problému jsme použili hash partitioning s počítaným sloupcem a zaznamenali jsme zlepšení výkonu o 690%. Následující tabulka shrnuje výkon aplikace před a po implementaci hashovacího oddílení s použitím počítaného sloupce. Využití procesoru se po odebrání kolize západek výrazně zvyšuje v souladu s propustností podle očekávání:

Měření Před dělením hodnot hash Po rozdělení pomocí hashovací funkce
Obchodní transakce/Sekce 36 249
Průměrná doba čekání na zámek stránky 36 milisekund 0,6 milisekundy
Čekačky na uzamčení za sekundu 9,562 2,873
Čas procesoru SQL 24 % 78%
Požadavky služby SQL Batch za sekundu 12,368 47,045

Jak je vidět z předchozí tabulky, správná identifikace a řešení problémů s výkonem způsobených nadměrným konfliktem v oblasti synchronizace může mít pozitivní vliv na celkový výkon aplikace.

Příloha: Alternativní technika

Jednou z možných strategií pro zabránění nadměrným kolizím zámku stránky je doplnit řádky znakovým sloupcem, aby se zajistilo, že každý řádek používá celou stránku. Tato strategie je vhodná, pokud je celková velikost dat malá a potřebujete vyřešit EX soutěžení o blokování stránek způsobené následující kombinací faktorů:

  • Malá velikost řádku
  • Mělký B-strom
  • Vzor přístupu s vysokou mírou náhodných operací insert, select, update a delete.
  • Malé tabulky, jako jsou dočasné frontové tabulky

Když nastavíte řádky tak, aby vyplňovaly celou stránku, je nutné, aby SQL přidělil více stránek, což zpřístupní více stránek pro vložení a sníží konkurenci západek stránek EX.

Přizpůsobit řádky tak, aby každý řádek zabíral celou stránku

Skript podobný následujícímu se dá použít k doplňování řádků, aby zabíraly celou stránku:

ALTER TABLE mytable ADD Padding CHAR(5000) NOT NULL DEFAULT ('X');

Poznámka:

Použijte nejmenší možný znak, který vynutí jeden řádek na stránku, aby se snížily dodatečné požadavky procesoru na hodnotu odsazení a nadbytečné místo potřebné k protokolování řádku. Každý bajt se počítá v systému s vysokým výkonem.

Tato technika je vysvětlena pro úplnost; v praxi ji SQLCAT použil pouze na malé tabulce s 10 000 řádky v jednom výkonovém zapojení. Tato technika má omezené použití, protože zvyšuje zatížení paměti SQL Serveru pro velké tabulky a může vést k soupeření o zámky na nelistových stránkách. Dodatečný tlak paměti může být významným omezujícím faktorem pro použití této techniky. Při množství paměti dostupné na moderním serveru se velká část pracovní sady pro úlohy OLTP obvykle uchovává v paměti. Když se sada dat zvětší na velikost, která se už nevejde do paměti, dojde k významnému poklesu výkonu. Proto je tato technika něco, co se vztahuje pouze na malé tabulky. SQLCAT tuto techniku nepoužívá pro scénáře, jako je konflikt při vkládání na poslední nebo koncovou stránku u velkých tabulek.

Důležité

Použití této strategie může způsobit velký počet čekání na ACCESS_METHODS_HOBT_VIRTUAL_ROOT typ západky, protože tato strategie může vést k velkému počtu rozdělení stránek, ke kterým dochází v jiných než listových úrovních stromu B. V takovém případě musí SQL Server získat sdílené (SH) západky na všech úrovních, následované výhradními (EX) západkami na stránkách v B-stromu, kde je možné rozdělení stránka. sys.dm_os_latch_stats Zkontrolujte dynamickou správu zobrazení při vysokém počtu čekání na ACCESS_METHODS_HOBT_VIRTUAL_ROOT typ západky po upravení řádků.

Příloha: Skripty pro řešení konfliktů se západkami v SQL Serveru

Tato část obsahuje skripty, které můžete použít k diagnostice a odstraňování potíží se zabíráním zámků.

Dotaz sys.dm_os_waiting_tasks seřazený podle ID relace

Následující ukázkový skript dotazuje sys.dm_os_waiting_tasks a vrací čekání na synchronizaci seřazená podle ID relace:

-- WAITING TASKS ordered by session_id
SELECT wt.session_id,
       wt.wait_type,
       er.last_wait_type AS last_wait_type,
       wt.wait_duration_ms,
       wt.blocking_session_id,
       wt.blocking_exec_context_id,
       resource_description
FROM sys.dm_os_waiting_tasks AS wt
     INNER JOIN sys.dm_exec_sessions AS es
         ON wt.session_id = es.session_id
     INNER JOIN sys.dm_exec_requests AS er
         ON wt.session_id = er.session_id
WHERE es.is_user_process = 1
      AND wt.wait_type <> 'SLEEP_TASK'
ORDER BY session_id;

Dotaz sys.dm_os_waiting_tasks seřazený podle doby čekání

Následující ukázkový skript sys.dm_os_waiting_tasks dotazuje a vrací čekací zámky seřazené podle doby čekání.

-- WAITING TASKS ordered by wait_duration_ms
SELECT wt.session_id,
       wt.wait_type,
       er.last_wait_type AS last_wait_type,
       wt.wait_duration_ms,
       wt.blocking_session_id,
       wt.blocking_exec_context_id,
       resource_description
FROM sys.dm_os_waiting_tasks AS wt
     INNER JOIN sys.dm_exec_sessions AS es
         ON wt.session_id = es.session_id
     INNER JOIN sys.dm_exec_requests AS er
         ON wt.session_id = er.session_id
WHERE es.is_user_process = 1
      AND wt.wait_type <> 'SLEEP_TASK'
ORDER BY wt.wait_duration_ms DESC;

Výpočet čekání v časovém období

Následující skript vypočítá a vrátí čekací doby na zámky během časového období.

/* Snapshot the current wait stats and store so that this can be compared over a time period
   Return the statistics between this point in time and the last collection point in time.

   **This data is maintained in tempdb so the connection must persist between each execution**
   **alternatively this could be modified to use a persisted table in tempdb.  if that
   is changed code should be included to clean up the table at some point.**
*/
USE tempdb;
GO

DECLARE @current_snap_time AS DATETIME;
DECLARE @previous_snap_time AS DATETIME;

SET @current_snap_time = GETDATE();

IF NOT EXISTS (SELECT name
               FROM tempdb.sys.sysobjects
               WHERE name LIKE '#_wait_stats%')
    CREATE TABLE #_wait_stats
    (
        wait_type VARCHAR (128),
        waiting_tasks_count BIGINT,
        wait_time_ms BIGINT,
        avg_wait_time_ms INT,
        max_wait_time_ms BIGINT,
        signal_wait_time_ms BIGINT,
        avg_signal_wait_time INT,
        snap_time DATETIME
    );

INSERT INTO #_wait_stats (wait_type, waiting_tasks_count, wait_time_ms, max_wait_time_ms, signal_wait_time_ms, snap_time)
SELECT wait_type,
       waiting_tasks_count,
       wait_time_ms,
       max_wait_time_ms,
       signal_wait_time_ms,
       getdate()
FROM sys.dm_os_wait_stats;

--get the previous collection point
SELECT TOP 1 @previous_snap_time = snap_time
FROM #_wait_stats
WHERE snap_time < (SELECT MAX(snap_time)
                   FROM #_wait_stats)
ORDER BY snap_time DESC;

--get delta in the wait stats
SELECT TOP 10 s.wait_type,
              (e.waiting_tasks_count - s.waiting_tasks_count) AS [waiting_tasks_count],
              (e.wait_time_ms - s.wait_time_ms) AS [wait_time_ms],
              (e.wait_time_ms - s.wait_time_ms) / ((e.waiting_tasks_count - s.waiting_tasks_count)) AS [avg_wait_time_ms],
              (e.max_wait_time_ms) AS [max_wait_time_ms],
              (e.signal_wait_time_ms - s.signal_wait_time_ms) AS [signal_wait_time_ms],
              (e.signal_wait_time_ms - s.signal_wait_time_ms) / ((e.waiting_tasks_count - s.waiting_tasks_count)) AS [avg_signal_time_ms],
              s.snap_time AS [start_time],
              e.snap_time AS [end_time],
              DATEDIFF(ss, s.snap_time, e.snap_time) AS [seconds_in_sample]
FROM #_wait_stats AS e
     INNER JOIN (SELECT *
      FROM #_wait_stats
      WHERE snap_time = @previous_snap_time) AS s
     ON (s.wait_type = e.wait_type)
WHERE e.snap_time = @current_snap_time
      AND s.snap_time = @previous_snap_time
      AND e.wait_time_ms > 0
      AND (e.waiting_tasks_count - s.waiting_tasks_count) > 0
      AND e.wait_type NOT IN ('LAZYWRITER_SLEEP', 'SQLTRACE_BUFFER_FLUSH', 'SOS_SCHEDULER_YIELD',
                              'DBMIRRORING_CMD', 'BROKER_TASK_STOP', 'CLR_AUTO_EVENT',
                              'BROKER_RECEIVE_WAITFOR', 'WAITFOR', 'SLEEP_TASK',
                              'REQUEST_FOR_DEADLOCK_SEARCH', 'XE_TIMER_EVENT',
                              'FT_IFTS_SCHEDULER_IDLE_WAIT', 'BROKER_TO_FLUSH',
                              'XE_DISPATCHER_WAIT', 'SQLTRACE_INCREMENTAL_FLUSH_SLEEP')
ORDER BY (e.wait_time_ms - s.wait_time_ms) DESC;

--clean up table
DELETE FROM #_wait_stats
WHERE snap_time = @previous_snap_time;

Popisovače vyrovnávací paměti dotazu k určení objektů způsobujících kolize západek

Následující skript dotazuje popisovače vyrovnávací paměti, aby určil, které objekty jsou přidruženy k nejdelší době čekání na západky.

IF EXISTS (SELECT *
           FROM tempdb.sys.objects
           WHERE [name] LIKE '#WaitResources%')
    DROP TABLE #WaitResources;

CREATE TABLE #WaitResources
(
    session_id INT,
    wait_type NVARCHAR (1000),
    wait_duration_ms INT,
    resource_description sysname NULL,
    db_name NVARCHAR (1000),
    schema_name NVARCHAR (1000),
    object_name NVARCHAR (1000),
    index_name NVARCHAR (1000)
);
GO

DECLARE @WaitDelay AS VARCHAR (16), @Counter AS INT, @MaxCount AS INT, @Counter2 AS INT;
SELECT @Counter = 0, @MaxCount = 600, @WaitDelay = '00:00:00.100'; -- 600x.1=60 seconds

SET NOCOUNT ON;

WHILE @Counter < @MaxCount
BEGIN
   INSERT INTO #WaitResources (session_id, wait_type, wait_duration_ms, resource_description)--, db_name, schema_name, object_name, index_name)
   SELECT wt.session_id,
         wt.wait_type,
         wt.wait_duration_ms,
         wt.resource_description
   FROM sys.dm_os_waiting_tasks AS wt
   WHERE wt.wait_type LIKE 'PAGELATCH%'
         AND wt.session_id <> @@SPID;

   -- SELECT * FROM sys.dm_os_buffer_descriptors;

   SET @Counter = @Counter + 1;
   WAITFOR DELAY @WaitDelay;
END

--SELECT * FROM #WaitResources;

UPDATE #WaitResources
    SET db_name = DB_NAME(bd.database_id),
        schema_name = s.name,
        object_name = o.name,
        index_name = i.name
FROM #WaitResources AS wt
     INNER JOIN sys.dm_os_buffer_descriptors AS bd
         ON bd.database_id = SUBSTRING(wt.resource_description, 0, CHARINDEX(':', wt.resource_description))
        AND bd.file_id = SUBSTRING(wt.resource_description, CHARINDEX(':', wt.resource_description) + 1, CHARINDEX(':', wt.resource_description, CHARINDEX(':', wt.resource_description) + 1) - CHARINDEX(':', wt.resource_description) - 1)
        AND bd.page_id = SUBSTRING(wt.resource_description, CHARINDEX(':', wt.resource_description, CHARINDEX(':', wt.resource_description) + 1) + 1, LEN(wt.resource_description) + 1)
        -- AND wt.file_index > 0 AND wt.page_index > 0
     INNER JOIN sys.allocation_units AS au
         ON bd.allocation_unit_id = AU.allocation_unit_id
     INNER JOIN sys.partitions AS p
         ON au.container_id = p.partition_id
     INNER JOIN sys.indexes AS i
         ON p.index_id = i.index_id
        AND p.object_id = i.object_id
     INNER JOIN sys.objects AS o
         ON i.object_id = o.object_id
     INNER JOIN sys.schemas AS s
         ON o.schema_id = s.schema_id;

SELECT * FROM #WaitResources
ORDER BY wait_duration_ms DESC;
GO
/*
--Other views of the same information
SELECT wait_type, db_name, schema_name, object_name, index_name, SUM(wait_duration_ms) [total_wait_duration_ms] FROM #WaitResources
GROUP BY wait_type, db_name, schema_name, object_name, index_name;
SELECT session_id, wait_type, db_name, schema_name, object_name, index_name, SUM(wait_duration_ms) [total_wait_duration_ms] FROM #WaitResources
GROUP BY session_id, wait_type, db_name, schema_name, object_name, index_name;
*/

--SELECT * FROM #WaitResources
--DROP TABLE #WaitResources;

Skript pro particionování pomocí hashí

Použití tohoto skriptu je popsáno v Použití dělení hash s počítaným sloupcem a mělo by být přizpůsobeno pro účely vaší implementace.

--Create the partition scheme and function, align this to the number of CPU cores 1:1 up to 32 core computer
-- so for below this is aligned to 16 core system
CREATE PARTITION FUNCTION [pf_hash16](TINYINT)
    AS RANGE LEFT
    FOR VALUES (0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15);

CREATE PARTITION SCHEME [ps_hash16]
    AS PARTITION [pf_hash16]
    ALL TO ([ALL_DATA]);
-- Add the computed column to the existing table (this is an OFFLINE operation)

-- Consider using bulk loading techniques to speed it up
ALTER TABLE [dbo].[latch_contention_table]
    ADD [HashValue] AS (CONVERT (TINYINT, ABS(BINARY_CHECKSUM([hash_col]) % (16)), (0))) PERSISTED NOT NULL;

--Create the index on the new partitioning scheme
CREATE UNIQUE CLUSTERED INDEX [IX_Transaction_ID]
    ON [dbo].[latch_contention_table]([T_ID] ASC, [HashValue])
    ON ps_hash16 (HashValue);