Find tablelocks/deadlocks i Navision & Business Central

Hvad er tabel-låse?

Tabellensperren

…er i første omgang intet ondt. Enhver database benytter sig af dem for at sikre dataintegriteten.

Et eksempel: Bruger A har en debitorpost på skærmen og ændrer nu gaden. Denne handling gemmes herefter i databasen, så den nye gade er permanent gemt. Bruger B arbejder netop på den samme post og vil imidlertid ændre telefonnummeret. Begge handlinger kan faktisk udføres uafhængigt af hinanden.

Men da ændringen fra bruger A altid skriver hele datasættet tilbage, og også ændringen af telefonnummeret fra bruger B skriver hele debitorsættet tilbage (inkl. navn, adresse, vej, nyt telefonnummer, kreditgrænse…), kan følgende ske i dette tilfælde: Bruger A og bruger B får den samme sætning vist på skærmen med den gamle vej og det gamle telefonnummer.

Bruger A ændrer nu kun vejen (intet telefonnummer), og gemmer denne sætning. Bruger B har ikke opdaget noget. Han har stadig det uændrede datasæt med gammel vej på skærmen og ændrer nu Telefonnummer. Nu gemmer han også sin sætning tilbage.

Hvad er konsekvensen? Hans ændring overfører nu det nye telefonnummer…. sammen med den gamle vej til databasen. Korrektionen af A er tabt.

Hvordan kan man forhindre en sådan hverdagssituation? Man arbejder med tablelocks: Bruger A får datasættet på skærmen. I det øjeblik spærrer Navision eller Business Central datasættet for ændringer. Det bliver „låst“. Ikke at forveksle med et adgangsprotokol, så bliver det „logget“ (fra begrebet „Logbog.

Bruger B kan slet ikke se dette datasæt. Først når A er færdig, f.eks. med at kigge eller ændre adressen, får B igen datasættet frigivet på skærmen. Nu starter dette spil forfra for ham.

Hvordan opstår tabel-låse?


„Læsning“ er ALDRIG et problem i Navision eller Business Central! Det er først, når man skriver, at problemet opstår. Hvor sletning, indsættelse og ændring alle falder ind under „skrivning“ (= “ændring“).

Heldigvis er Business Central og Navision markant smartere end mange databasesystemer, der faktisk fungerer som beskrevet ovenfor. Navision bruger optimistisk samtidighedskontrol: I langt de fleste tilfælde vises en post kun og ændres ikke.

Og hvad hvis en adresse alligevel bliver ændret? Statistisk set er det meget usandsynligt, at en anden computer samme datasæt På samme tid bliver ændret. Derfor låser Navision ikke denne post i noget tilfælde, men kontrollerer ved lagring via tidsstemplet, om posten i databasen stadig er den samme version som den post, der lige nu gemmes til ændring.

Dette gøres via det usynligt medførte felt Timestamp i hver post i en Business Central & Navision database. Derfor kan man heller ikke selv oprette et felt, der hedder TimeStamp. „Før i tiden“ kunne det lade sig gøre, en populær „grå hår“-fejl ved overførsel af en native database til SQL.

Hvis Navision slet ikke foretager spærringer, hvor kommer problemet så fra? Ovenstående eksempel er et af hundreder, hvor Navisions udviklere har taget sig af en sådan opgave for brugeren. Men der er også reelle tilfælde, hvor databasen skal være sikker på, at den er i en konsistent tilstand.

At skrive gør ondt…

Her er et nyt eksempel: En bruger bogfører en ordre. Det kan tage 1-2 sekunder. I denne tid skrives vareposter, bogførte følgesedler og bogførte fakturaer, åbne debitorposter...

På samme tid kan en anden bruger ønske at slette denne ordre eller endda denne debitor. Hvis dette var tilladt, ville der i sidste ende eksistere følgesedler, åbne poster og fakturaer for en debitor, som på det tidspunkt slet ikke eksisterer længere. Afhængigt af tidsforskellen ville der på debitorposten slet ikke have været nogen poster, der ville have forbudt sletning!

Business Central eller Navision lægger her langt mere arbejde i for at undgå sådanne ting sikkert, men for vores eksempel er det nok.
Navision ville derfor nu spærre ordren og debitorerne ved bogføring („låse“ dem) for netop at forhindre denne tilstand. I virkeligheden gøres dette anderledes, men det er ikke så vigtigt her.
Så nu er der for en kort periode nogle tabeller, der er låst („åbnet for skrivning“): Ordre, Ordrelinjer, registrerede leveringsseddelhoveder, registrerede leveringsseddelmlinjer... og et par stykker mere.

Hvis ikke så mange mennesker arbejder på systemet, er det heller ikke noget problem: Disse tabeller er jo alligevel fri igen en eller to sekunder senere. (Ikke hos dig? Tager en ordre mange sekunder eller endda minutter at bogføre hos dig? Det er dårligt! Det kan helt sikkert fremskyndes!)

Så stadig intet problem? Netop. Bortset fra… at sådanne transaktioner tager for lang tid, eller låser tabeller i forskellig rækkefølge. Det er netop det, der forårsager deadlocks.

Deadlocks… et blik ind i helvede

Deadlocks: Antag, at et program vil låse debitorer for derefter at kunne indtaste ordrer for dem. Først låser BUSINESS CENTRAL eller NAVISION debitorerne, så... er der et øjebliks forsinkelse. I denne periode starter et andet program, som låser ordrerne og, om nødvendigt, foretager dataændringer i debitorerne.

Nu kommer det første program til det sted, hvor det ud over sine debitorer også vil spærre ordrerne. Det går nu ikke, fordi det anden Programmet allerede har blokeret opgaverne for sig selv. Så venter første Programmet, der kører pænt og ordentligt, venter på, at det også kan blokere debitorerne. Det venter virkelig! Det andet program kommer nu til det sted, hvor det gerne vil ændre debitorerne, altså også blokere dem. Det anmoder databasen om debitorblokering... men de er jo allerede blokeret!

Nu er der opstået en deadlock: Begge programmer venter på den tabel, som det andet program har låst. Låsen er – i princippet – uendelig, da begge programmer venter på frigivelse af den anden tabel fra det første program. Denne situation kaldes en „deadlock“, en død låsning: Den bliver – af sig selv – aldrig løst.

SQL Server genkender dette og afslutter én af de to forespørgsler, så den anden nu kan fortsætte med at arbejde.

Faustregel: Jo dårligere programkode, jo længere programmbehandling og jo mere sandsynligt er der dødt løb. Fordi skriveblokeringerne simpelthen er aktive for længe. Ydelsesoptimering er derfor også altid undgåelse af dødt løb!

Findning af tablelocks/deadlocks

I Navision eller Business Central vises en sådan afbrudt deadlock kun med en simpel fejlmeddelelse:

„Processen kunne ikke færdiggøres, da en post i tabellen „xxx“ blev blokeret af en anden bruger“. Udfør handlingen igen. Denne meddelelse vises altid på den computer, der udløste deadlock tabt hat.

Af og til er "deadlocks" dog også spredt over flere programmer, så selv (SQL-)serveren ikke længere kan fastslå, at det drejer sig om en "deadlock". Her griber f.eks. den maksimale SQL-udførelsestid ind.

Før Navision 2009R2 var det nemt at se i sessions-tabellen, hvilken proces der blokerede en anden. Med et hurtigt blik på den rette skærm kunne man muligvis hurtigt lokalisere problemet... eller starte sin udvidede søgning.

Siden Navision & Business Central 2013 RTC har Microsofts politik dog været at gøre alt så kompliceret og besværligt som muligt. Her skal man så arbejde direkte på SQL-serveren. Her er et par metoder til analyse:

Det starter altid i SQL Server i den aktuelle database med en forespørgsel

Liste over blokerede brugere

Kør sp_who2

SQL viser alle sessioner. En blokeret session viser i kolonnen „Blkby“, om/af hvilken session den er blokeret.
I.d.R. vil man her finde den samme sessions-ID ved flere sessioner, der alle venter på den samme blokerende session.

Et niveau dybere går det på anmodning her med objekterne, som er blokeret. Den resulterende liste er væsentligt mere overskuelig, men måske ikke tilstrækkelig. Især hvis man som djævelen har sørget for, at man kun skriver til midlertidige tabeller før en Page.runmodal, og alligevel giver en grim fejlmeddelelse en låst tabel anledning til bekymring, har man allerede sit at gøre med at finde ud af, hvilken Hvilke tabeller er da blokeret? Ofte hjælper det én allerede på rette spor.

De følgende C/AL-funktioner er begrænset under skrivehandlinger, fordi mindst én tabel bliver låst. Form.RunModal er ikke tilladt under skrivehandlinger. Codeunit.Run er kun tilladt under skrivehandlinger, hvis returværdien ikke bruges. F.eks. er OK := Codeunit.Run() ikke tilladt. Report.RunModal er kun tilladt under skrivehandlinger, hvis RequestForm = FALSE. F.eks. er Report.RunModal(...,FALSE) tilladt. XmlPort.RunModal er kun tilladt under skrivehandlinger, hvis RequestForm = FALSE. F.eks. er XmlPort.RunModal(...,FALSE) tilladt. Brug COMMIT-funktionen til at gemme ændringerne før kaldet, eller omstrukturer koden.

Microsoft Dynamics 365 Business Central

Indhold af fejlmeddelelse: De C/AL-funktioner, der er angivet nedenfor, er begrænsede under skrivehandlinger, da mindst én tabel er spærret. Form.RunModal er ikke tilladt under skrivehandlinger. Codeunit.Run er kun tilladt under skrivehandlinger, når returværdien ikke bruges. F.eks. er OK := Codeunit.Run() ikke tilladt. Report.RunModal er kun tilladt under skrivehandlinger, når RequestForm = FALSE. F.eks. er Report.RunModal(…,FALSE) tilladt. XmlPort.RunModal er kun tilladt under skrivehandlinger, når RequestForm = FALSE. F.eks. er XmlPort.RunModal(…,FALSE) tilladt. Brug COMMIT-funktionen til at gemme ændringerne, før du kalder, eller omstrukturér koden.

-- Liste over alle låse i den aktuelle database Kilde: https://stackoverflow.com/questions/694581/how-to-check-which-locks-are-held-on-a-table
SELECT TL.resource_type AS ResType
      ,TL.resource_description AS ResDescr
      ,TL.request_mode AS ReqMode
      ,TL.request_type AS ReqType
      ,TL.request_status AS ReqStatus
      ,TL.request_owner_type AS ReqOwnerType
      ,TAT.[name] AS TransName
      ,TAT.transaction_begin_time AS TransBegin
      ,DATEDIFF(ss, TAT.transaction_begin_time, GETDATE()) AS TransDura
      ,ES.session_id AS S_Id
      ,ES.login_name AS LoginName
      ,COALESCE(OBJ.name, PAROBJ.name) AS ObjectName
      ,PARIDX.name AS IndexName
      ,ES.host_name AS HostName
      ,ES.program_name AS ProgramName
FROM sys.dm_tran_locks AS TL
     INNER JOIN sys.dm_exec_sessions AS ES
         ON TL.request_session_id = ES.session_id
     LEFT JOIN sys.dm_tran_active_transactions AS TAT
         ON TL.request_owner_id = TAT.transaction_id
            AND TL.request_owner_type = 'TRANSACTION'
     LEFT JOIN sys.objects AS OBJ
         ON TL.resource_associated_entity_id = OBJ.object_id
            AND TL.resource_type = 'OBJECT'
     LEFT JOIN sys.partitions AS PAR
         ON TL.resource_associated_entity_id = PAR.hobt_id
            AND TL.resource_type IN ('PAGE', 'KEY', 'RID', 'HOBT')
     LEFT JOIN sys.objects AS PAROBJ
         ON PAR.object_id = PAROBJ.object_id
     LEFT JOIN sys.indexes AS PARIDX
         ON PAR.object_id = PARIDX.object_id
            AND PAR.index_id = PARIDX.index_id
WHERE TL.resource_database_id = DB_ID()
      AND ES.session_id  @@Spid -- Ekskluder "min" session
      -- valgfrit filter
      AND TL.request_mode  'S' -- Ekskluderer simple delte låse
ORDER BY TL.resource_type
        ,TL.request_mode
        ,TL.request_type
        ,TL.request_status
        ,ObjectName
        ,ES.login_name;



--TSQL-kommandoer
SELECT
       db_name(rsc_dbid) AS 'DATABASE_NAME',
       case rsc_type when 1 then 'null'
                             when 2 then 'DATABASE'
                             NÅR 3 SÅ 'FIL'
                             WHEN 4 THEN 'INDEKS'
                             WHEN 5 THEN 'TABLE'
                             WHEN 6 THEN 'PAGE'
                             NÅR 7 SÅ 'KEY'
                             NÅR 8 SÅ 'EXTEND'
                             WHEN 9 THEN 'RID ( ROW ID)'
                             WHEN 10 THEN 'APPLICATION' end AS 'REQUEST_TYPE',

       CASE req_ownertype WHEN 1 THEN 'TRANSACTION'
                                     WHEN 2 THEN 'CURSOR'
                                     WHEN 3 THEN 'SESSION'
                                     WHEN 4 THEN 'ExSESSION' END AS 'REQUEST_OWNERTYPE',

       OBJECT_NAME(rsc_objid ,rsc_dbid) AS 'OBJECT_NAME',
       PROCESS.HOSTNAME ,
       PROCESS.program_name ,
       PROCESS.nt_domain ,
       PROCESS.nt_username ,
       PROCESS.program_name ,
       SQLTEXT.text
FROM sys.syslockinfo LOCK JOIN
     sys.sysprocesses PROCESS
  ON LOCK.req_spid = PROCESS.spid
CROSS APPLY sys.dm_exec_sql_text(PROCESS.SQL_HANDLE) SQLTEXT
hvor 1=1
og db_name(rsc_dbid) = db_name()



--Lås på et specifikt objekt
UDVÆLG *
FRA sys.dm_tran_locks
WHERE resource_database_id = DB_ID()
AND resource_associated_entity_id = object_id('Specific Table');

databaser belastning

Da deadlocks næsten altid følger lige efter lange programkørsler, og disse næsten altid opstår på grund af dårligt gennemtænkte databaseforespørgsler, er et kig på databasebelastningen også altid helt praktisk:

vælg spid,cmd,waittime,lastwaittype,cpu,physical_io,login_time,last_batch,status,hostname,program_name,nt_username, nt_domain
fra master.dbo.sysprocesses hvor dbid = db_id(‚ØnsketDatabase‘)

eller

VÆLG DISTINCT
navn SOM database_navn,
session_id,
vært_navn,
login_tid,
brugernavn,
læser,
skriver
FRA sys.dm_exec_sessions
LEFT OUTER JOIN sys.dm_tran_locks ON sys.dm_exec_sessions.session_id = sys.dm_tran_locks.request_session_id
INNER JOIN sys.databases ON sys.dm_tran_locks.resource_database_id = sys.databases.database_id
HVOR resource_type ‚DATABASE‘
–OG request_mode LIKE ‚%X%‘
–OG navn =’DieInteressanteDatenbank‘
SORTER EFTER navn

Mens læse- og skrivehandlinger i 5-cifrede områder sjældent er værd at bemærke, bør man kigge nærmere på 6-cifrede, og 7-cifrede adgange bør få alarmklokkerne til at ringe… naturligvis også afhængigt af den hidtidige afviklingstid for den pågældende proces.

1,5 mio. skriveadgange til en EDIfact-forbindelse, der har været online i 12 dage, er et andet tal end 500.000 læseadgange fra en bruger, der loggede ind for 5 minutter siden. Ydelsesfejlfinding kræver megen erfaring og tålmodighed.

I Business Central & Navision Debugger fra 2017

Her er låseovervågning allerede indbygget i debuggeren! Navision eller Business Central kræver dog ændringer i SQL Server for, at den også kan levere de nødvendige data.

Hvis Business Central eller Navision selv har installeret serveren, er disse indstillinger allerede foretaget!

Foretag tilpasningerne i SQL_Server, hvis de endnu ikke er foretaget:
I SSMS Server/Egenskaber -> Tilladelser, for den login, som Navision-tjenesten kører under:
Rediger arrangementets møde: Tildel.
Vis serverstatus: Tildel.

Tildel denne bruger som ejer af den ønskede database via Server/Sikkerhed/Login.
I Business Central og Navision indstillingen aktiver Deadlock Monitoring:

Til fejlfinding af ydeevne kan Aktivitetsovervågningen også være nyttig. Den kan tilgås via MSSMS:

Her er der yderligere værktøjer fra aktivitetsmonitoren