SQL Server query’s op secondaries zijn langzaam

Tijdens één van mijn leertrajecten op SQL Server onderzocht ik het gedrag van query’s op READ ONLY secondaries binnen Always-On clusters. Wat opviel was dat query’s op deze database langzamer draaiden; dus, dezelfde query draait op de primary sneller. Nu gebruik ik in het dagelijks werkleven meer secondary logshipomgevingen, waardoor mijn werk niet direct wordt geraakt, maar als SQL Server Enthousiast werd mijn interesse behoorlijk geprikkeld.

Bij mijn zoektocht op internet, kwam ik gelukkig weer bij Microsoft uit. Dit artikel verklaarde precies de exacte oorzaak van mijn testsituatie.

Hoe zie ik dat het fout gaat?

  • De query voert een index scan uit op een groot gedeelte van een tabel, die een clustered row-store index hebben.
  • De query maakt gebruik van de NOLOCK query hintDe dynamic management view sys.dm_db_index_physical_stats laat een significante fragmentatie van de index zien
  • Loskoppelen van de secondary uit het Always-On Cluster lost de performance kwestie op

Hoe zit dit nu? Wat is de oorzaak?
Op secondary databases binnen een always-on cluster wordt snapshot isolation bij query’s afgewdongen. De optie NOLOCK wordt daarbij genegeerd. Dit zorgt ervoor dat de index scan op volgorde van de index key door SQL Server wordt uitgevoerd Als de clustered index significant gefragmenteerd is, valt SQL Server terug op het lezen van één pagina per IO request. Op de primary database (waar snapshot isolation niet wordt afgedwongen) zal SQL Server nog steeds terugvallen op het lezen van meerdere pagina’s per IO request.

De oplossing?
Om dit probleem voor te blijven, is het verstandig de clustered index van de betrokken tabellen opnieuw op te bouwen. De nieuw opgebouwde indexen zullen dan via de Always-On naar de secondaries worden overgezet, waardoor de query’s net zo snel als op de primary database worden.

De Featured Image komt van deze pagina.

Foutmeldingen in transactie logshipping

Ik kom regelmatig situaties tegen waarbij logshipdatabases (=secondaries) niet meer via logshipping wordt bijgewerkt. Meestal zien we dan in de geschiedenis van de LSRestore-job in de SQL Agent van de database server waar de logshipdatabase op draait een melding die aangeeft dat transactielogs niet meer kunnen worden ingelezen omdat ze ‘out-of-sequence’ of ‘too recent’ zijn. Deze situatie wordt meestal door één van onderstaande scenario’s veroorzaakt:

  • Er is een FULL backup van de database gemaakt zonder de optie COPY_ONLY.
  • Het recovery model is aangepast. Het aanpassen van het recovery model wordt in de SQL Server gelogd als Setting database option RECOVERY to SIMPLE for database ‘[naam van de database]’ (zonder blokhaken).
  • Er heeft een RESTORE plaats gevonden waarbij de database in NonRecovery is gezet
  • Er zijn transactielogs van de brondatabase verwijderd of naar een andere locatie gekopieerd. Wanneer deze transactielogs nog ergens zijn te vinden, kan worden volstaan met het plaatsen van de transactielogs in de folder van waaruit de LSRestore-job de transactielogs inleest. Voor de veiligheid worden deze transactielogs uit de bronfolder gekopieerd en niet verplaatst.

De gebruikte query om inzicht te krijgen in uitgevoerde restore jobs is

SELECT
       rh.destination_database_name AS [Database],
       CASE
             WHEN rh.restore_type = 'D' THEN 'Database'
             WHEN rh.restore_type = 'F' THEN 'File'
             WHEN rh.restore_type = 'I' THEN 'Differential'
             WHEN rh.restore_type = 'L' THEN 'Log'
       ELSE rh.restore_type END AS [Restore Type],
       rh.restore_date AS [Restore Date],
       bmf.physical_device_name AS [Source], 
       rf.destination_phys_name AS [Restore File],
       rh.user_name AS [Restored By]
FROM msdb.dbo.restorehistory rh
INNER JOIN msdb.dbo.backupset bs ON rh.backup_set_id = bs.backup_set_id
INNER JOIN msdb.dbo.restorefile rf ON rh.restore_history_id = rf.restore_history_id
INNER JOIN msdb.dbo.backupmediafamily bmf ON bmf.media_set_id = bs.media_set_id
-- WHERE destination_database_name = '[naam van de database]' -- zonder blokhaken
ORDER BY rh.restore_date DESC
GO

De gebruikte query voor inzicht in gemaakte backups is:

SELECT
       s.database_name,
       m.physical_device_name,
       CAST(CAST(s.backup_size / 1000000 AS INT) AS VARCHAR(14)) + ' ' + 'MB' AS bkSize,
       CAST(DATEDIFF(second, s.backup_start_date,
       s.backup_finish_date) AS VARCHAR(4)) + ' ' + 'Seconds' TimeTaken,
       s.backup_start_date,
       CAST(s.first_lsn AS VARCHAR(50)) AS first_lsn,
       CAST(s.last_lsn AS VARCHAR(50)) AS last_lsn,
       CASE s.[type] WHEN 'D' THEN 'Full'
       WHEN 'I' THEN 'Differential'
       WHEN 'L' THEN 'Transaction Log'
       END AS BackupType,
       s.server_name,
       s.recovery_model
FROM msdb.dbo.backupset s
INNER JOIN msdb.dbo.backupmediafamily m ON s.media_set_id = m.media_set_id
WHERE s.database_name = DB_NAME() -- Remove this line for all the database
ORDER BY backup_start_date DESC, backup_finish_date;

In de meeste gevallen dient de logshipdatabse opnieuw te worden opgebouwd. Alleen wanneer de melding ‘too recent’ zich voordoet en de transactielogs die ontbreken zijn nog beschikbaar, kan de logshipdatabase met de ontbrekende transactielogs worden hersteld.

SQL Server databases – informatie over de restore van een backup

Regelmatig krijg ik de vraag: “Weet jij wanneer de backup van [deze] en [deze] database is gemaakt en wanneer die is teruggezet?”. Het antwoord is ‘ja’… en met deze bijdrage weet u het nu ook. Draai op de SQL Server waar de teruggezette database staat onderstaande query:

SELECT 
   [rs].[destination_database_name], 
   [rs].[restore_date], 
   [bs].[backup_start_date], 
   [bs].[backup_finish_date], 
   [bs].[database_name] as [source_database_name], 
   [bmf].[physical_device_name] as [backup_file_used_for_restore]
FROM msdb..restorehistory rs
INNER JOIN msdb..backupset bs ON [rs].[backup_set_id] = [bs].[backup_set_id]
INNER JOIN msdb..backupmediafamily bmf ON [bs].[media_set_id] = [bmf].[media_set_id] 
ORDER BY [rs].[restore_date] DESC

Zo wordt de gevraagde informatie gevonden.

Met dank aan Thomas LaRock die deze informatie in het Engels in deze bijdrage heeft gedeeld.

SQL Server 2016 CTP3: DROP IF EXISTS (DIE)

Database beheerders en gebruikers die al langer van SQL Server en T-SQL gebruik maken, kennen vast deze veelgebruikte query nog:

IF OBJECT_ID('dbo.Product, 'U') IS NOT NULL
DROP TABLE dbo.Product;

Misschien dat u deze code zelfs nu nog gebruikt. Misschien dat u het (ooit) gemist heeft: Microsoft heeft bij de introductie van SQL Server 2016, CTP3 de functie DROP uitgebreid met IF EXISTS. De nieuwe functie ziet er dan als volgt uit:

DROP TABLE IF EXITS dbo.Product;

Veel makkelijker toch? Deze nieuwe uitbreiding op de functie DROP werkt op heel veel plaatsen, namelijk:

  • DROP AGGREGATE IF EXISTS
  • DROP ASEMBLY IF EXISTS
  • DROP VIEW IF EXISTS
  • DROP DATABASE IF EXISTS
  • DROP DEFAULT IF EXISTS
  • DROP FUNCTION IF EXISTS
  • DROP INDEX IF EXISTS
  • DROP PROCEDURE IF EXISTS
  • DROP ROLE IF EXISTS
  • DROP RULE IF EXISTS
  • DROP SCHEMA IF EXISTS
  • DROP SECURITY POLICY IF EXISTS
  • DROP SEQUENCE IF EXISTS
  • DROP SYNONYM IF EXISTS
  • DROP TABLE IF EXISTS
  • DROP TRIGGER IF EXISTS
  • DROP TYPE IF EXISTS
  • DROP USER IF EXISTS
  • DROP VIEW IF EXISTS

Aanvullend hierop kan “DIE” (DROP IF EXISTS) ook worden toegepast op velden en constraints binnen ALTER TABLE, dus:

  • ALTER TABLE DROP COLUMN IF EXISTS[/li]
  • ALTER TABLE DROP CONSTRAINT IF EXISTS

Zelf vind ik de functie DROP IF EXISTS een stuk handiger dan de scripts die voor SQL Server 2016, CTP3 nodig waren om alleen iets te controleren en vervolgens te verwijderen.

Wie zit er aan mijn SQL Agent jobs?

De HiX Datawarehouse verversing van ChipSoft haalt de data uit een tijdelijk statische HiX secondary database op basis van logshipping op. Om de database statisch te maken, zorgt de software van HiX Datawarehouse voor de correcte afhandeling van de logship procedures. Het is voor gebruikers en beheerders nadrukkelijk niet de bedoeling om zélf de logship, met name de LSRestore job, in- of uit te schakelen. Maar… monitor dat maar eens! Microsoft SQL Server heeft daar zélf geen voorziening voor én het goede nieuws is dat deze voorziening eenvoudig zélf te schrijven is.

Functioneel
Wanneer een SQL Agent job in- of uitgeschakeld wordt, wordt dit in de tabel [dbo].[sysjobs] binnen de msdb database opgeslagen. Deze wijziging kunnen we afvangen en de wijziging kan in een aparte beheertabel worden vastgelegd.

Technisch
Met een AFTER UPDATE trigger op de tabel dbo.sysjobs binnen de msdb database wordt het mogelijk om de wijziging in de job status (ingeschakeld / uitgeschakeld) af te vangen. De gewijzigde informatie wordt vervolgens in een beheerdatabase / beheertabel opgeslagen, zodat hier later naar gekeken kan worden.

De code hiervoor …

USE msdb;
GO
CREATE TRIGGER [dbo].Alert_on_job_stat
   ON  [dbo].[sysjobs]
   AFTER UPDATE
AS 
BEGIN
DECLARE @old_status BIT, @new_status BIT, @job_name VARCHAR(1024)
 
SELECT @old_status = enabled FROM deleted
SELECT @new_status = enabled, @job_name = name FROM inserted
 
  IF(@old_status <> @new_status)
  BEGIN
    INSERT INTO [audit_database].dbo.[audit_tabel] (login_name, job_name, status_, datetime)
    VALUES(ORIGINAL_LOGIN(), @job_name, @new_status, GETDATE())
    
    DECLARE @body_content VARCHAR(1024) = '';
    SET @body_content = 'SQL job : ' + @job_name + ' has been ' + 
              CASE @new_status WHEN 1 THEN 'Enabled' ELSE 'Disabled' END 
              + ' @ ' + CAST(GETDATE() AS VARCHAR(30))

    /* Eventueel een e-mail sturen
    EXEC msdb.dbo.sp_send_dbmail  
    @profile_name = 'SQL Alert', 
    @recipients = 'beheerders@domeinnaam.com',  
    @subject = 'SQL job status Change Alert',  
    @body = @body_content
    */
    ;  
  END
END

Aandachtspunten:

  1. De database audit_database is een zelfgekozen naam en dient uiteraard te worden aangemaakt
  2. De tabelnaam is een zelfgekozen naam en dient uiteraard te worden aangemaakt
  3. E-mail is standaard door mij uitgeschakeld
  4. De e-mail instellingen moeten goed worden geconfigureerd
  5. Gebruikers die SQL Agent Jobs in en uit kunnen schakelen, dienen voldoende schrijfrechten op de audit database en audit tabel te krijgen

Met dank aan Jignesh Raiyani voor het idee en de uitwerking in het Engels; Link naar de webpagina

Veldlengtes (n)varchar en (n)char velden vs. query performance vs. server performance

Vandaag werd ik door mijn collega getriggerd om onderzoek te doen naar extreme lengtes in (n)varchar velden en de impact daarvan op performance. Welnu… riemen vast, want dit is belangrijk.

Management samenvatting
Een goede inrichting en adequate keuze van veldlengtes bij (n)varchar en (n)char velden draagt bij aan een optimaler query plan en daarmee aan een betere query en server performance in het algemeen.

Hoe zit dit precies
U kent het wel: “Laten we dat tekstveld alvast maar wat langer maken, zodat we ook in de toekomst deze velden niet zo snel hoeven te verlengen”. Die beslissing kan impact hebben op de query performance van uw query’s op deze velden. SQL Server kijkt bij het maken van het query plan (die SQL Server bij query’s gebruikt) naar de grootte van de tekstvelden die in de query wordt gebruikt. Op basis daarvan maakt SQL Server een inschatting van het benodigd geheugen. Hoe meer de veldlengte van het veld in de tabel afwijkt van de daadwerkelijke vulling des te meer geheugen zal SQL Server onnodig aan deze query toewijzen. Gelukkig zal SQL Server (2019) u hier in het Actual Query Plan op wijzen bij het meest linkse (SELECT) component. U vindt in het Actual Query Plan op dat component een geel driehoekje met een waarschuwing.

Eén en ander is met onderstaand TestLab verduidelijkt. Deze set met query’s helpt u hopelijk beter te begrijpen wat er precies fout gaat. Dit is overigens geen ‘incident’. De onjuiste bepaling van het benodigd geheugen zal continue fout gaan wanneer de betrokken tabel (of, indien dit zich in meerdere tabellen voordoet, betrokken tabellen) in een query wordt betrokken. Draaien er parallel meerdere query’s, dan zal dit potentieel onnodig veel resources op het geheugen van uw SQL Server gaan leggen; en geheugen is schaars en kostbaar.

Vragen staat vrij!


/*
Auteur : Mickel Reemer
Datum : 23 maart 2022
Thanks to : Erik Darling
Credits by : https://www.brentozar.com/archive/2017/02/memory-grants-data-size/
SQL Server : SQL Server 2019 Developer Edition

Doel van het script:
--------------------
Aantonen dat varchar die te groot zijn aangemaakt
oorzaak zijn excessive memory grants op een SQL Server
*/
-- Zet tijdstatistieken aan
SET STATISTICS TIME ON

-- Verwijder de testtabel wanneer deze al bestaat
DROP TABLE IF EXISTS dbo.MemoryGrants;

-- Maak de testtabel aan
CREATE TABLE dbo.MemoryGrants
(
ID INT PRIMARY KEY CLUSTERED,
Ordering INT,
Field1 VARCHAR(10),
);

-- Voeg de records in, in de tabel
INSERT dbo.MemoryGrants WITH (TABLOCK)
( ID, Ordering, Field1)
SELECT
c,
c % 1000,
REPLICATE('X', c * 10 % 10)
FROM (
SELECT TOP (100000) ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) AS c
FROM sys.messages AS m
CROSS JOIN sys.messages AS m2
) x

-- Zet Actual Query Plan aan en draai de query hieronder
SELECT mg.ID, mg.Ordering, mg.Field1 --Returning a VARCHAR(10)
FROM dbo.MemoryGrants AS mg
ORDER BY mg.Ordering
GO
-- De doorlooptijd is 1751ms. De memory grant is 13MB

/*
Dat gaan we nu nog een keer testen, maar dan met varchar(8000)
*/

-- Verwijder de testtabel opnieuw wanneer deze bestaat
DROP TABLE IF EXISTS dbo.MemoryGrants;

-- Maak de testtabel aan
CREATE TABLE dbo.MemoryGrants
(
ID INT PRIMARY KEY CLUSTERED,
Ordering INT,
Field1 VARCHAR(8000), -- We gaan nu Field1 een lengte van 8000 geven
);

-- Voeg de records in, in de tabel
INSERT dbo.MemoryGrants WITH (TABLOCK)
( ID, Ordering, Field1)
SELECT
c,
c % 1000,
REPLICATE('X', c * 10 % 10)
FROM (
SELECT TOP (100000) ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) AS c
FROM sys.messages AS m
CROSS JOIN sys.messages AS m2
) x

-- Zet Actual Query Plan aan en draai de query hieronder
SELECT mg.ID, mg.Ordering, mg.Field1 --Returning a VARCHAR(10)
FROM dbo.MemoryGrants AS mg
ORDER BY mg.Ordering
GO
-- De memory grant is 574MB. De doorlooptijd is 2706ms

Ongebruikte indexen op tabellen in een database vinden

Het is als database administrator belangrijk periodiek te kijken of er indexen op tabellen staan die door SQL Server nooit meer worden gebruikt. Dit voorkomt dat deze indexen onnodig schijfruimte innemen en/of onnodige overhead (performance) veroorzaken bij het bewerken, toevoegen of verwijderen van records op de tabel. In deze video wordt daar uitgebreider door mij op ingegaan en wordt getoond op welke wijze hier het best een analyse op kan worden gemaakt.

Het bijbehorende script is hier te downloaden.

Overzicht open transacties in SQL Server

Stel je voor dat je op een SQL Server in een database tabel binnen transacties data aan het bewerken bent. Het is dan handig te weten of er nog open transacties zijn, die nog definitief moeten worden gemaakt of terug moeten worden gedraaid. Ook wanneer wordt geprobeerd om de transactielog vrij te maken, is het handig te weten of er nog transacties nodig zijn. Er zijn een aantal manieren om inzicht te krijgen in de transacties die nog open staan.

    DBCC OPENTRAN
    Deze functie wordt vooral gebruikt bij het vrijmaken van ruimte in de transactielogs. Het commando geeft de oudste transactie aan die nog open staat voor verwerking.
    Belangrijk: dit commando geeft alleen informatie over de actief geselecteerde database waarbinnen deze instructie wordt uitgevoerd.

    sys.sysprocesses
    De query …

    SELECT
    * 
    FROM sys.sysprocesses
    WHERE open_tran <> 0
    

    … geeft informatie over open transacties. Deze system view (dus geen tabel!) geeft onder andere informatie over het proces ID waaronder de transactie draait, op welke machine de transactie is gestart ook met welk account. Deze tabel is vooral handig om te identificeren wie, welke transacties open heeft.
    Belangrijk: de informatie wordt serverbreed weergegeven (en dus niet per databases)

    sys.dm_tran_active_transactions
    Onderstaande handige query is een query op de serverbrede view (dus geen tabel!) sys.dm_tran_active_transactions. Deze tabel wordt voornamelijk gebruikt om de status en voortgang van verschillende open transacties in kaart te brengen. De velden zijn in onderstaande query alvast vertaald naar betekenissen (in het Engels).

    select transaction_id, name, transaction_begin_time
     ,case transaction_type 
        when 1 then '1 = Read/write transaction'
        when 2 then '2 = Read-only transaction'
        when 3 then '3 = System transaction'
        when 4 then '4 = Distributed transaction'
    end as transaction_type 
    ,case transaction_state 
        when 0 then '0 = The transaction has not been completely initialized yet'
        when 1 then '1 = The transaction has been initialized but has not started'
        when 2 then '2 = The transaction is active'
        when 3 then '3 = The transaction has ended. This is used for read-only transactions'
        when 4 then '4 = The commit process has been initiated on the distributed transaction'
        when 5 then '5 = The transaction is in a prepared state and waiting resolution'
        when 6 then '6 = The transaction has been committed'
        when 7 then '7 = The transaction is being rolled back'
        when 8 then '8 = The transaction has been rolled back'
    end as transaction_state
    ,case dtc_state 
        when 1 then '1 = ACTIVE'
        when 2 then '2 = PREPARED'
        when 3 then '3 = COMMITTED'
        when 4 then '4 = ABORTED'
        when 5 then '5 = RECOVERED'
    end as dtc_state 
    ,transaction_status, transaction_status2,dtc_status, dtc_isolation_level, filestream_transaction_id
    from sys.dm_tran_active_transactions
    

    Belangrijk: de informatie wordt serverbreed weergegeven (en dus niet per databases)

    Credits
    Met dank aan: Tharif, Sebastian Brosch en Alisson Gomes
    Gebruikte link op StackOverflow: hier
    Microsoft Docs voor meer informatie: hier

Foutmelding in kubusverwerking via een SQL Agent Job

UItgangspunt
Er is een kubusdatabase waarbij meetwaarden worden gevonden die een onbekende dimensiewaarde hebben. Om te voorkomen dat dit een foutmelding tijdens het bijwerken van de data (process database) plaatsvindt, worden in het tabblad “Dimension key errors” binnen Batch Settings Summary gewijzigd. Daarbij worden de volgende keuzes gemaakt:

– Bij fouten in dimensies worde de waarde naar ‘unknown’ geconverteerd
– Er worden in totaal maximaal 500 dimensiefouten geaccepteerd
– Wanneer de dimensiewaardie niet wordt gevonden, gaat het bijwerken van de kubus door
– Wanneer een niet toegestane Null key wordt gevonden, gaat het bijwerken ook door

Grafisch:

Wanneer de kubus op deze wijze via een SQL Agent job wordt bijgewerkt (process database) zal de SQL Agent job met de volgende melding falen:

<Warning WarningCode=”1092354050″ Description=”Server: Operation completed with … problems logged.” … />

De SQL Agent job geeft dus een harde foutmelding. Wees er dus op bedacht dat het aantal en soort fouten bij het instellen van de kubusverversing zijn geaccepteerd en dat de SQL Agent job daarmee eigenlijk onterecht faalt.

Omdat de SQL Agent een onterechte fout geeft, is er een stukje maatwerkcode nodig om de jobs periodiek op juistheid en volledigheid te controleren. Onderstaand T-SQL kan hiervoor worden gebruikt. U zult zelf even moeten kijken welke velden uit deze tabellen bruikbaar voor u zijn.

SELECT
*
FROM msdb.dbo.sysjobs j 
INNER JOIN msdb.dbo.sysjobsteps s ON j.job_id = s.job_id
INNER JOIN msdb.dbo.sysjobhistory h ON s.job_id = h.job_id AND s.step_id = h.step_id

 

SQL Server foutmelding “String or binary data would be truncated” met trace flag 460 analyseren

Wellicht dat u de foutmelding herkent. Met deze foutmelding begint de eindeloze zoektocht naar het juiste veld en record waar dit probleem zich in voordoet.

Eindelijk heeft Microsoft een oplossing hiervoor gemaakt, te weten trace flag 460. Wanneer deze trace flag wordt aangezet, geeft SQL Server meer informatie over de foutmelding, namelijk het veld waar het probleem zich voordoet en het gevonden record.

Ik verwacht dat ontwikkelaars hier de nodige tijd mee zullen besparen. Deze trace flag is beschikbaar in SQL Server 2016 SP2, CU6 en SQL Server 2017, CU12

Meer informatie in deze link van Microsoft: https://support.microsoft.com/en-us/help/4468101/optional-replacement-for-string-or-binary-data-would-be-truncated. Voorbeelden in het Engels zijn beschikbaar via Brent Ozar: https://www.brentozar.com/archive/2019/03/how-to-fix-the-error-string-or-binary-data-would-be-truncated/.