Voortzetting van mijn reeks artikelen over vergrendelingen, deze keer ga ik de DBCC_OBJECT_METADATA-vergrendeling bespreken en laten zien hoe dit onder bepaalde omstandigheden een groot knelpunt kan zijn voor consistentiecontroles voorafgaand aan SQL Server 2016. Het probleem is van invloed op DBCC CHECKDB, DBCC CHECKTABLE en DBCC CHECKFILEGROUP, maar voor de duidelijkheid verwijs ik in de rest van dit bericht naar DBCC CHECKDB.
Je vraagt je misschien af waarom ik schrijf over een probleem dat oudere versies treft, maar er zijn nog steeds een groot aantal SQL Server 2014 en oudere exemplaren, dus het is een geldig onderwerp voor mijn serie.
Ik raad je ten zeerste aan de eerste post in de serie voor deze te lezen, zodat je alle algemene achtergrondkennis over vergrendelingen hebt.
Wat is de DBCC_OBJECT_METADATA-vergrendeling?
Om deze vergrendeling uit te leggen, moet ik een beetje uitleggen hoe DBCC CHECKDB werkt.
Onder het enorme aantal consistentiecontroles dat DBCC CHECKDB uitvoert, is een controle van de juistheid van niet-geclusterde indexen. In het bijzonder zorgt DBCC CHECKDB ervoor dat:
- Voor elk niet-geclusterd indexrecord in elke niet-geclusterde index is er precies één "overeenkomend" gegevensrecord in de basistabel (een heap of een geclusterde index)
- Voor elk gegevensrecord in een tabel is er precies één "overeenkomend" niet-geclusterd indexrecord in elke niet-geclusterde index die voor de tabel is gedefinieerd, rekening houdend met gefilterde indexen
Zonder al te diep in te gaan op de details van hoe dit wordt gedaan, construeert DBCC CHECKDB voor elk gegevensrecord in een tabel elk niet-geclusterd indexrecord dat zou moeten bestaan voor elke niet-geclusterde index en zorgt ervoor dat het geconstrueerde niet-geclusterde indexrecord exact overeenkomt met de werkelijke niet-geclusterd indexrecord. Als de niet-geclusterde index een berekende kolom bevat (ofwel als onderdeel van de niet-geclusterde indexsleutel of als een INBEGREPEN kolom), moet DBCC CHECKDB de berekende kolomwaarde bepalen die moet worden gebruikt bij het samenstellen van de indexrecords.
Naast de niet-geclusterde index-correctheidscontroles, als er een aanhoudende berekende kolom in de definitie van een tabel, dan moet DBCC CHECKDB voor elk gegevensrecord in de tabel controleren of de persistente waarde correct is, ongeacht of die kolom deel uitmaakt van een niet-geclusterde index of niet.
Dus hoe berekent het de berekende kolomwaarden?
De Query Processor biedt een mechanisme voor het berekenen van berekende kolomwaarden, de 'expressie-evaluator'. DBCC CHECKDB roept die functie aan en levert de juiste metagegevensinformatie en het gegevensrecord, en de expressie-evaluator gebruikt de opgeslagen definitie van de berekende kolom in de metagegevens en de waarden uit het gegevensrecord en retourneert de waarde van de berekende kolom die DBCC CHECKDB kan gebruiken . De interne werking van de expressie-evaluator valt buiten de controle van de DBCC-code, maar om de expressie-evaluator te kunnen gebruiken, moet eerst een grendel worden verkregen; de DBCC_OBJECT_METADATA-vergrendeling.
Hoe wordt de vergrendeling een knelpunt?
Hier is het probleem:er is maar één acceptabele modus waarin de DBCC_OBJECT_METADATA-grendel kan worden verkregen voordat de expressie-evaluator wordt gebruikt, en dat is de EX-modus (exclusief). En zoals je zult weten als je de intropost van de serie leest, kan slechts één thread tegelijk de vergrendeling in de EX-modus vasthouden.
Al deze informatie samenbrengen:wanneer een database persistente berekende kolommen heeft, of niet-geclusterde indexen die berekende kolommen bevatten, moet de expressie-evaluator worden gebruikt. Als de SQL Server-editie Enterprise is, kan DBCC CHECKDB parallellisme gebruiken en heeft het dus meerdere threads die de verschillende controles uitvoeren. En zodra u meerdere threads hebt die proberen een vergrendeling in de EX-modus te verkrijgen, wordt die vergrendeling een knelpunt. Hoe groot de bottleneck wordt, hangt af van hoeveel de expressie-evaluator moet worden gebruikt, dus hoe meer persistente berekende kolommen of niet-geclusterde indexen die berekende kolommen gebruiken, en hoe groter het aantal tabelrijen in die tabellen, hoe groter de hoe groter de bottleneck die de DBCC _OBJECT_METADATA-latch wordt.
Maar onthoud dat dit knelpunt alleen optreedt voor versies van SQL Server eerder dan SQL Server 2016. In SQL Server 2016 besloot Microsoft het knelpunt te "repareren" door de controles van niet-geclusterde indexen standaard uit te schakelen met behulp van berekende kolommen en deze alleen uit te voeren wanneer de WITH EXTENDED_LOGICAL_CHECKS optie wordt gebruikt.
De bottleneck laten zien
U kunt het knelpunt gemakkelijk voor uzelf reproduceren door DBCC CHECKDB uit te voeren op een database met persistente berekende kolommen of niet-geclusterde indexen met berekende kolommen erin, en de door Microsoft geleverde AdventureWorks-database is een goed voorbeeld. U kunt hier back-ups van AdventureWorks voor uw versie van SQL Server downloaden. Ik heb wat tests uitgevoerd met een AdventureWorks2014-database op een SQL Server 2014-instantie (op een 32-core Dell R720), en ik heb de database vergroot tot een paar honderd GB met behulp van Jonathans scripts.
Toen ik DBCC CHECKDB uitvoerde, met server MAXDOP ingesteld op 0, duurde het meer dan 5 uur om te draaien. Het LATCH_EX-wachttype was goed voor ongeveer 30% van de wachttijden, waarbij elke wachttijd slechts 1 milliseconde bedroeg, en 99% van de LATCH_EX-wachttijden waren voor de DBCC_OBJECT_METADATA-latch.
Ik zocht naar niet-geclusterde indexen die berekende kolommen bevatten met behulp van de volgende code:
SELECT [s].[name] AS [Schema], [o].[name] AS [Object], [i].[name] AS [Index], [c].[name] AS [Column], [ic].* FROM sys.columns [c] JOIN sys.index_columns [ic] ON [ic].[object_id] = [c].[object_id] AND [ic].[column_id] = [c].[column_id] JOIN sys.indexes [i] ON [i].[object_id] = [ic].[object_id] AND [i].[index_id] = [ic].[index_id] JOIN sys.objects [o] ON [i].[object_id] = [o].[object_id] JOIN sys.schemas [s] ON [o].[schema_id] = [s].[schema_id] WHERE [c].[is_computed] = 1;
Die code vond zes niet-geclusterde indexen in de AdventureWorks2014-database. Ik heb alle zes de indexen uitgeschakeld (met ALTER INDEX ... DISABLE) en DBCC CHECKDB opnieuw uitgevoerd en het voltooide het in ongeveer 18 minuten. Dus de DBCC_OBJECT_METADATA-grendelknelpunt was een belangrijke factor waardoor DBCC CHECKDB meer dan 16 keer langzamer werkte!
Samenvatting
Helaas is het uitschakelen van niet-geclusterde indexen met behulp van berekende kolommen (en ze later opnieuw inschakelen met behulp van ALTER INDEX ... REBUILD) de * enige * manier om het DBCC_OBJECT_METADATA-grendelknelpunt in versies vóór SQL Server 2016 te verwijderen, terwijl alle andere functionaliteit van DBCC behouden blijft CONTROLEERDB. Het uitschakelen van niet-geclusterde indexen is waarschijnlijk niet iets dat u in een productieomgeving wilt doen, tenzij u een onderhoudsperiode zonder activiteit hebt. Dit betekent dat u die niet-geclusterde indexen waarschijnlijk alleen gaat uitschakelen om het knelpunt te verwijderen als uw consistentiecontroles naar een andere server worden verplaatst met behulp van de back-up-copy-restore-CHECKDB-methode.
Een andere manier om dit te doen is om de WITH PHYSICAL_ONLY optie te gebruiken bij het uitvoeren van DBCC CHECKDB, maar dan mis je alle diepgaande logische controles, dus ik ben er geen grote fan van om dat aan te bevelen als de oplossing.