Poznámka:
Přístup k této stránce vyžaduje autorizaci. Můžete se zkusit přihlásit nebo změnit adresáře.
Přístup k této stránce vyžaduje autorizaci. Můžete zkusit změnit adresáře.
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 sSH(sdílenou západkou) a KP, ale ne s žádnými jinými, a protoEXzá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:
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_statsswait_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éPAGEIOLATCHdoby č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.
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.
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:
Určete, zda může být konflikt, který by mohl souviset se závorami.
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.
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:
- Změřte celkové doby čekání během reprezentativního testu.
- Seřaďte je.
- 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.
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_EXneboPAGELATCH_SH. Chcete-li diagnostikovat zvyšující se průměrné čekací doby na zámku stránky v kontextu propustnosti, postupujte takto:Pomocí ukázkových skriptů Query sys.dm_os_waiting_tasks Ordered by Session ID nebo Calculate Waits Over a Time Period se podívejte na aktuální čekající úlohy a změřte průměrnou dobu čekání na latch.
Pomocí ukázkových skriptů popisovačů vyrovnávací paměti dotazu určete objekty způsobující kolizí latch k určení indexu a podkladové tabulky, na které kolizí dochází.
Změřte průměrnou dobu čekání na uzamčení stránky pomocí čítače Performance Monitor MSSQL%InstanceName%\Wait Statistics\Page Latch Waits\Average Wait Time nebo spuštěním
sys.dm_os_wait_statsdynamického zobrazení správy.
Poznámka:
Pro výpočet průměrné doby čekání pro určitý typ čekání (označený
sys.dm_os_wait_statsjakowt_:type), vydělte celkovou dobu čekání (označenou jakowait_time_ms) počtem čekajících úkolů (označených jakowaiting_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_statsDMV, 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ímeksys.dm_os_wait_statspř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_statszobrazení 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_statsDMV (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;
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;
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.
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:
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:
Procházením B-stromu vyhledejte správnou stránku pro uložení nového záznamu.
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
EXna 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).Záznam položky protokolu, kterou byl řádek změněn.
Přidejte řádek na stránku a označte stránku jako špinavou.
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ý.
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:
Přidejte datové soubory tak
tempdb, aby se počet datových souborů tempdb rovnal počtu jader procesoru na vašem serveru.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.
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ů:
Vytvořte novou skupinu souborů nebo použijte existující skupinu souborů k uložení oddílů.
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.
CREATE PARTITION FUNCTIONPomocí 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í.
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
HASHBYTESs modulo neboBINARY_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 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í.
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:
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.
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.
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í:
Dotaz na aktuálně čekající úkoly pomocí skriptu dodatku Dotaz sys.dm_os_waiting_tasks seřazeno podle doby čekání.
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.Povolte trasovací příznak 3604, který odhaluje další informace o stránce pomocí
DBCC PAGEpodle 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);Prozkoumejte výstup DBCC. V našem případě
78623323by mělo existovat přidružené ID objektu metadat .
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);
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);