<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Sql Server | DBA von Nebenan</title>
	<atom:link href="https://dbavonnebenan.de/category/sql-server/feed/" rel="self" type="application/rss+xml" />
	<link>https://dbavonnebenan.de</link>
	<description>Sql Server, Performance &#38; PowerShell Automation</description>
	<lastBuildDate>Fri, 06 Feb 2026 11:50:38 +0000</lastBuildDate>
	<language>de</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=6.9.4</generator>

<image>
	<url>https://dbavonnebenan.de/wp-content/uploads/2025/04/cropped-www_logo-32x32.png</url>
	<title>Sql Server | DBA von Nebenan</title>
	<link>https://dbavonnebenan.de</link>
	<width>32</width>
	<height>32</height>
</image> 
	<item>
		<title>Automate SQL Server HA: AD, SPN, and Cluster Setup</title>
		<link>https://dbavonnebenan.de/automate-sql-server-ha-ad-spn-and-cluster-setup_en/</link>
					<comments>https://dbavonnebenan.de/automate-sql-server-ha-ad-spn-and-cluster-setup_en/#respond</comments>
		
		<dc:creator><![CDATA[Gabriel]]></dc:creator>
		<pubDate>Fri, 06 Feb 2026 11:47:28 +0000</pubDate>
				<category><![CDATA[PowerShell]]></category>
		<category><![CDATA[Sql Server]]></category>
		<category><![CDATA[AD]]></category>
		<category><![CDATA[AlwaysOn]]></category>
		<category><![CDATA[Deployment]]></category>
		<category><![CDATA[eng]]></category>
		<category><![CDATA[GMSA]]></category>
		<category><![CDATA[SPN]]></category>
		<category><![CDATA[WSFC]]></category>
		<guid isPermaLink="false">https://dbavonnebenan.de/?p=669</guid>

					<description><![CDATA[<p>Production-Ready Windows Cluster Deployment: Automation of Failover Cluster, gMSA, Kerberos and local rights via PowerShell.</p>
<p>The post <a href="https://dbavonnebenan.de/automate-sql-server-ha-ad-spn-and-cluster-setup_en/">Automate SQL Server HA: AD, SPN, and Cluster Setup</a> first appeared on <a href="https://dbavonnebenan.de">DBA von Nebenan</a>.</p>]]></description>
										<content:encoded><![CDATA[<div style="border: 1px dotted #eb2b14; border-radius: 6px; background-color: #eeeeee; padding-top: var(--wp--preset--spacing--30); padding-left: var(--wp--preset--spacing--30); padding-right: var(--wp--preset--spacing--30); padding-bottom: var(--wp--preset--spacing--30); " class="ub-styled-box ub-bordered-box wp-block-ub-styled-box" id="ub-styled-box-11670a24-effc-43d1-9b6f-f1421b3d4c12">
<p class="has-open-sauce-sans-font-family" style="margin-top:0;margin-right:0;margin-bottom:0;margin-left:0" id="ub-styled-box-bordered-content-">What awaits you here?</p>



<ul style="margin-top:0;margin-right:0;margin-bottom:0;margin-left:0" class="wp-block-list has-open-sauce-sans-font-family">
<li><a href="#WSFC" title="">Windows Server Failover Cluster Deployment with Extended AD and DNS Permissions</a></li>



<li><a href="#GMSA" title="">Identity Management: Group Managed Service Accounts (gMSA)</a></li>



<li><a href="#KERBEROS" title="">Kerberos and Service Principal Names (SPN)</a></li>



<li><a href="#LSP" title="">Local Security Policies</a></li>



<li>Background knowledge on why configurations are done the way they are</li>
</ul>


</div>


<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>


https://github.com/gabrielkoehl/DBAScriptBox


<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<h2 class="wp-block-heading" id="0-intro" style="margin-top:var(--wp--preset--spacing--50);margin-bottom:var(--wp--preset--spacing--50)">Intro</h2>



<p>Who hasn&#8217;t been there? Before you can even start installing SQL Server, you&#8217;ve either repetitively clicked through pages of bad GUIs or, if available, juggled a loose collection of scripts to semi-automatically set up all dependencies and best practices.</p>



<div class="wp-block-columns is-layout-flex wp-container-core-columns-is-layout-28f84493 wp-block-columns-is-layout-flex">
<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:90%">
<p>Even worse is the case when, as a consultant, you don&#8217;t have Domain Admin access (which is how it should be) and you have to guide the customer — who may have never done this before — via Teams &amp; Co. If this isn&#8217;t already a planned installation with knowledge transfer, something like this creates pain. <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f92d.png" alt="🤭" class="wp-smiley" style="height: 1em; max-height: 1em;" /></p>
</div>



<div class="wp-block-column is-vertically-aligned-top is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:10%">
<figure class="wp-block-image aligncenter size-full is-resized"><img fetchpriority="high" decoding="async" width="264" height="329" src="https://dbavonnebenan.de/wp-content/uploads/2025/06/emote-4.png" alt="PAIN Emote" class="wp-image-516" style="aspect-ratio:0.8024665981500514;width:65px;height:auto" srcset="https://dbavonnebenan.de/wp-content/uploads/2025/06/emote-4.png 264w, https://dbavonnebenan.de/wp-content/uploads/2025/06/emote-4-241x300.png 241w" sizes="(max-width: 264px) 100vw, 264px" /></figure>
</div>
</div>



<p>Accordingly, I&#8217;ve now gathered all my loose scripts and developed a function for each topic area that automates all these tasks and logs them for archival purposes. Thanks also to Claude for the support. Nobody reviews and documents code better than Claude. <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f60b.png" alt="😋" class="wp-smiley" style="height: 1em; max-height: 1em;" /></p>



<p>Now you might ask: Aren&#8217;t there dbatools and DSC? Sure, I like using those too. But transparent, traceable scripts without framework overhead convey knowledge better — especially when aspiring DBAs lack the big-picture perspective. Operating SQL Server starts at the bare metal, goes through storage, Windows Server, Active Directory, and ends at a Certificate Authority. And for critical infrastructure companies or small mid-sized businesses with 3 clusters, I don&#8217;t want to start a fundamental discussion about dbatools or DSC right from the beginning.</p>



<p>Let&#8217;s start with a production-ready Windows Server Failover Cluster — including CNO/VCO permissions, DNS registration, and quorum configuration. Everything repeatable, everything logged, everything traceable.</p>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<h2 class="wp-block-heading" id="1-das-lab-szenario" style="margin-top:var(--wp--preset--spacing--50);margin-bottom:var(--wp--preset--spacing--50)">The Lab Scenario</h2>



<p style="margin-bottom:var(--wp--preset--spacing--40)">To make the steps tangible, the following environment and naming conventions are used</p>



<ul class="wp-block-list">
<li><strong>Nodes</strong>
<ul class="wp-block-list">
<li><code>LAB-NODE01</code> (192.168.100.12)</li>



<li><code>LAB-NODE02</code> (192.168.100.13)</li>
</ul>
</li>



<li><strong>Cluster Name</strong>
<ul class="wp-block-list">
<li><code>MSSQL-CL-01</code> (192.168.100.14)</li>
</ul>
</li>



<li><strong>SQL Instance</strong>
<ul class="wp-block-list">
<li><code>LAB22A</code></li>
</ul>
</li>



<li><strong>Domain</strong>
<ul class="wp-block-list">
<li><code>lab.local</code></li>
</ul>
</li>



<li><strong>Service Accounts</strong> Group Managed Service Accounts (gMSA)
<ul class="wp-block-list">
<li><code>g-LAB22A-oltp</code></li>



<li><code>g-LAB22A-agnt</code></li>
</ul>
</li>
</ul>



<hr class="wp-block-separator has-alpha-channel-opacity" style="margin-top:var(--wp--preset--spacing--60);margin-bottom:var(--wp--preset--spacing--60)"/>



<h2 class="wp-block-heading" id="WSFC" style="margin-top:var(--wp--preset--spacing--50);margin-bottom:var(--wp--preset--spacing--50)">Windows Server Failover Cluster Deployment </h2>



<div class="wp-block-columns is-layout-flex wp-container-core-columns-is-layout-c901c877 wp-block-columns-is-layout-flex" style="border-width:1px;border-top-left-radius:2px;border-top-right-radius:2px;border-bottom-left-radius:2px;border-bottom-right-radius:2px;margin-top:var(--wp--preset--spacing--20);margin-bottom:var(--wp--preset--spacing--20);padding-top:var(--wp--preset--spacing--30);padding-right:var(--wp--preset--spacing--80);padding-bottom:var(--wp--preset--spacing--30);padding-left:var(--wp--preset--spacing--80)">
<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:100%">
<div class="wp-block-columns is-layout-flex wp-container-core-columns-is-layout-28f84493 wp-block-columns-is-layout-flex">
<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow">
<p class="has-text-align-center"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2692.png" alt="⚒" class="wp-smiley" style="height: 1em; max-height: 1em;" /><br><a href="https://github.com/gabrielkoehl/DBAScriptBox/blob/main/windows/system/cluster_manageSQLCluster.ps1" target="_blank" rel="noopener" title="cluster_manageSQLCluster.ps1"><code>cluster_manageSQLCluster.ps1</code></a></p>
</div>



<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow">
<p class="has-text-align-center"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f4d3.png" alt="📓" class="wp-smiley" style="height: 1em; max-height: 1em;" /><br><code><a href="https://github.com/gabrielkoehl/DBAScriptBox/blob/main/windows/system/cluster_manageSQLCluster.md" target="_blank" rel="noopener" title="cluster_manageSQLCluster.md">cluster_manageSQLCluster.md</a></code></p>
</div>
</div>
</div>
</div>



<div style="height:14px" aria-hidden="true" class="wp-block-spacer"></div>



<p class="has-text-align-left">The WSFC forms the foundation for AlwaysOn Availability Groups. The script manages the entire lifecycle from feature installation to witness configuration.</p>



<h3 class="wp-block-heading has-text-align-left" id="3-feature-installation-amp-cluster-erstellung" style="margin-top:var(--wp--preset--spacing--70);margin-bottom:var(--wp--preset--spacing--50)">Feature Installation &amp; Cluster Creation</h3>



<p style="margin-top:var(--wp--preset--spacing--50);margin-bottom:var(--wp--preset--spacing--50)">First, the <code>Failover-Clustering</code> feature is installed on the individual nodes and the cluster is created. An important aspect of automation is the Cluster Name Object (CNO): It needs write permissions in DNS and Active Directory to be able to automatically create listener records later. </p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: powershell; auto-links: false; title: ; notranslate">
.\cluster_manageSQLCluster.ps1 `
    -Operation InstallFeature `
    -Nodes &quot;LAB-NODE01&quot;,&quot;LAB-NODE02&quot; `
    -LogPath &quot;C:\Temp\Cluster_Feature_$(Get-Date -Format &#039;yyyyMMdd_HHmmss&#039;).log&quot;
</pre></div>


<p class="has-text-align-left" style="margin-top:var(--wp--preset--spacing--50);margin-bottom:var(--wp--preset--spacing--50)">Afterwards, the cluster is created. The script additionally sets the corresponding permissions in AD and DNS.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: powershell; title: ; notranslate">
.\cluster_manageSQLCluster.ps1 `
    -Operation CreateCluster `
    -ClusterName &quot;MSSQL-CL-01&quot; `
    -Nodes &quot;LAB-NODE01&quot;,&quot;LAB-NODE02&quot; `
    -StaticIP &quot;192.168.100.14&quot; `
    -DnsDomain &quot;lab.local&quot; `
    -TargetOU &quot;OU=lab_computer,DC=lab,DC=local&quot; `
    -LogPath &quot;C:\Temp\Cluster_Create_$(Get-Date -Format &#039;yyyyMMdd_HHmmss&#039;).log&quot;
</pre></div>


<h3 class="wp-block-heading" id="4-file-share-witness-quorum" style="margin-top:var(--wp--preset--spacing--70);margin-bottom:var(--wp--preset--spacing--50)">File Share Witness (Quorum)</h3>



<p style="margin-top:var(--wp--preset--spacing--50);margin-bottom:var(--wp--preset--spacing--50)">For a 2-node cluster, a witness is mandatory to avoid split-brain scenarios. Here, a file share on the domain controller (<code>\\LAB-DC\quorum$</code>) is used. </p>



<p style="margin-top:var(--wp--preset--spacing--50);margin-bottom:var(--wp--preset--spacing--50)"><strong>Manual Intermediate Step</strong><br>The Cluster Computer Object (<code>MSSQL-CL-01$</code>) requires <strong>Full Control</strong> at both the share and NTFS permission level of the witness path (<code>\\LAB-DC\quorum$</code>). This must be ensured before running the following command.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: powershell; title: ; notranslate">
.\cluster_manageSQLCluster.ps1 `
    -Operation SetWitness `
    -ClusterName &quot;MSSQL-CL-01&quot; `
    -WitnessShare &quot;\\LAB-DC\quorum$&quot; `
    -LogPath &quot;C:\Temp\Cluster_Witness_$(Get-Date -Format &#039;yyyyMMdd_HHmmss&#039;).log&quot;
</pre></div>


<h3 class="wp-block-heading" id="5-verwendete-microsoft-cmdlets" style="margin-top:var(--wp--preset--spacing--70);margin-bottom:var(--wp--preset--spacing--50)">Microsoft Cmdlets Used</h3>



<p style="margin-top:var(--wp--preset--spacing--50);margin-bottom:var(--wp--preset--spacing--50)">The script primarily uses the <a href="https://learn.microsoft.com/en-us/powershell/module/failoverclusters/" target="_blank" rel="noopener" title="FailoverClusters"><code>FailoverClusters</code></a> module</p>



<ul style="margin-top:var(--wp--preset--spacing--50);margin-bottom:var(--wp--preset--spacing--50)" class="wp-block-list">
<li><code>New-Cluster</code>
<ul class="wp-block-list">
<li>Creates the cluster, adds the nodes, and sets the management IP</li>
</ul>
</li>



<li><code>Set-ClusterQuorum</code>
<ul class="wp-block-list">
<li>Configures the witness mode (here: File Share Witness)</li>
</ul>
</li>



<li><code>Install-WindowsFeature</code>
<ul class="wp-block-list">
<li>Installs the RSAT tools and the cluster service</li>
</ul>
</li>
</ul>



<hr class="wp-block-separator has-alpha-channel-opacity" style="margin-top:var(--wp--preset--spacing--70);margin-bottom:var(--wp--preset--spacing--70)"/>



<h2 class="wp-block-heading" id="GMSA" style="margin-top:var(--wp--preset--spacing--50);margin-bottom:var(--wp--preset--spacing--50)">Identity Management: gMSA</h2>



<div class="wp-block-columns is-layout-flex wp-container-core-columns-is-layout-c901c877 wp-block-columns-is-layout-flex" style="border-width:1px;border-top-left-radius:2px;border-top-right-radius:2px;border-bottom-left-radius:2px;border-bottom-right-radius:2px;margin-top:var(--wp--preset--spacing--20);margin-bottom:var(--wp--preset--spacing--20);padding-top:var(--wp--preset--spacing--30);padding-right:var(--wp--preset--spacing--80);padding-bottom:var(--wp--preset--spacing--30);padding-left:var(--wp--preset--spacing--80)">
<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:100%">
<div class="wp-block-columns is-layout-flex wp-container-core-columns-is-layout-28f84493 wp-block-columns-is-layout-flex">
<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow">
<p class="has-text-align-center"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2692.png" alt="⚒" class="wp-smiley" style="height: 1em; max-height: 1em;" /><br><a href="https://github.com/gabrielkoehl/DBAScriptBox/blob/main/windows/security/security_createGMSA.ps1" target="_blank" rel="noopener" title="security_createGMSA.ps1"><code>security_createGMSA.ps1</code></a></p>
</div>



<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow">
<p class="has-text-align-center"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f4d3.png" alt="📓" class="wp-smiley" style="height: 1em; max-height: 1em;" /><br><a href="https://github.com/gabrielkoehl/DBAScriptBox/blob/main/windows/security/security_createGMSA.md" target="_blank" rel="noopener" title="security_createGMSA.md"><code>security_createGMSA.md</code></a></p>
</div>
</div>
</div>
</div>



<p style="margin-top:var(--wp--preset--spacing--50);margin-bottom:var(--wp--preset--spacing--50)">Using standard users as service accounts can lead to expired passwords and downtime. The recommended method for SQL Server is <a href="https://learn.microsoft.com/en-us/windows-server/identity/ad-ds/manage/group-managed-service-accounts/group-managed-service-accounts/group-managed-service-accounts-overview" target="_blank" rel="noopener" title="gMSA"><code>Group Managed Service Accounts (gMSA)</code></a>. Here, the Key Distribution Service (KDS) of Active Directory handles password rotation; nobody needs to know the password.</p>



<h3 class="wp-block-heading" style="margin-top:var(--wp--preset--spacing--70);margin-right:0;margin-bottom:var(--wp--preset--spacing--50);margin-left:0">KDS Root Key</h3>



<p style="margin-top:var(--wp--preset--spacing--50);margin-bottom:var(--wp--preset--spacing--50)">Before the first gMSA can be created, a KDS Root Key must exist once per forest. If a key already exists, the creation will be skipped.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: powershell; title: ; notranslate">
.\security_createGMSA.ps1 `
    -InitializeKDS `
    -LogPath &quot;C:\Temp\KDS_Init.log&quot;
</pre></div>


<h3 class="wp-block-heading" style="margin-top:var(--wp--preset--spacing--70);margin-right:0;margin-bottom:var(--wp--preset--spacing--50);margin-left:0">gMSA Creation</h3>



<p style="margin-top:var(--wp--preset--spacing--50);margin-bottom:var(--wp--preset--spacing--50)">Instead of assigning servers directly to a gMSA, it is best practice to create a security group (<code>SQL_LAB_CL01</code>) that contains the computer accounts of the cluster nodes. In addition to setting up the actual gMSA, this script can also create the security groups and handle the assignments. And always keep the descriptions of AD objects well-maintained. </p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: powershell; title: ; notranslate">
.\security_createGMSA.ps1 `
    -AccountName @( `
        @(&quot;g-LAB22A-oltp&quot;, &quot;SQL Server Engine Account for LAB22A&quot;), `
        @(&quot;g-LAB22A-agnt&quot;, &quot;SQL Server Agent Account for LAB22A&quot;) ) `
    -SecurityGroupName &quot;SQL_LAB_CL01&quot; `
    -SecurityGroupDescription &quot;SQL Server Cluster CL01 gMSA Group&quot; `
    -ServerNames &quot;LAB-NODE01&quot;,&quot;LAB-NODE02&quot; `
    -CreateSecurityGroup `
    -SecurityGroupOU &quot;OU=lab_secgrp,DC=lab,DC=local&quot; `
    -PasswordIntervalDays 90 `
    -LogPath &quot;C:\Temp\gMSA_Creation_$(Get-Date -Format &#039;yyyyMMdd_HHmmss&#039;).log&quot;
</pre></div>


<p style="margin-top:var(--wp--preset--spacing--50);margin-bottom:0">After this step, a restart of the nodes is required for the new group membership to take effect.</p>



<h3 class="wp-block-heading" style="margin-top:var(--wp--preset--spacing--70);margin-right:0;margin-bottom:var(--wp--preset--spacing--50);margin-left:0">Local Registration</h3>



<p style="margin-top:var(--wp--preset--spacing--50);margin-bottom:var(--wp--preset--spacing--50)">After creation, the gMSAs must be registered locally on the nodes. The following script can be used for this purpose.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: powershell; title: ; notranslate">
# Required for local setup (Local Admin)
Add-WindowsFeature -Name RSAT-AD-PowerShell

$gMSAs = @(
    &#039;g-LAB22A-oltp&#039;,
    &#039;g-LAB22A-agnt&#039;
)

foreach ($gMSA in $gMSAs) {

    if (Test-ADServiceAccount -Identity $gMSA -ErrorAction SilentlyContinue) {
        try {
            Install-ADServiceAccount -Identity $gMSA -ErrorAction Stop
            Write-Host &quot;$gMSA installed&quot; -ForegroundColor Green
        } catch {
            Write-Host &quot;$gMSA - Installation error: $($_.Exception.Message)&quot; -ForegroundColor Red
        }
    } else {
        Write-Host &quot;$gMSA does not exist&quot; -ForegroundColor Yellow
    }
}
</pre></div>


<p style="margin-top:var(--wp--preset--spacing--50);margin-bottom:0">After this step, a restart of the nodes is required for the new group membership to take effect.</p>



<h3 class="wp-block-heading" id="5-verwendete-microsoft-cmdlets" style="margin-top:var(--wp--preset--spacing--70);margin-bottom:var(--wp--preset--spacing--50)">Microsoft Cmdlets Used</h3>



<p style="margin-top:var(--wp--preset--spacing--50);margin-bottom:var(--wp--preset--spacing--50)">The script primarily uses the <a href="https://learn.microsoft.com/en-us/powershell/module/activedirectory/?view=windowsserver2025-ps" target="_blank" rel="noopener" title="ActiveDirectory"><code>ActiveDirectory</code></a> module</p>



<ul style="margin-top:var(--wp--preset--spacing--50);margin-bottom:var(--wp--preset--spacing--50)" class="wp-block-list">
<li><code>Add-KdsRootKey</code>
<ul class="wp-block-list">
<li>Creates the root key for the Group Key Distribution Service</li>
</ul>
</li>



<li><code>New-ADServiceAccount</code>
<ul class="wp-block-list">
<li>Creates the gMSA object in AD</li>
</ul>
</li>



<li><code>Set-ADServiceAccount</code>
<ul class="wp-block-list">
<li>Configures which computers (PrincipalsAllowedToRetrieveManagedPassword) are allowed to read the password</li>
</ul>
</li>
</ul>



<hr class="wp-block-separator has-alpha-channel-opacity" style="margin-top:var(--wp--preset--spacing--70);margin-bottom:var(--wp--preset--spacing--70)"/>



<h2 class="wp-block-heading" id="KERBEROS" style="margin-top:var(--wp--preset--spacing--50);margin-bottom:var(--wp--preset--spacing--50)">Kerberos and Service Principal Names (SPN)</h2>



<div class="wp-block-columns is-layout-flex wp-container-core-columns-is-layout-c901c877 wp-block-columns-is-layout-flex" style="border-width:1px;border-top-left-radius:2px;border-top-right-radius:2px;border-bottom-left-radius:2px;border-bottom-right-radius:2px;margin-top:var(--wp--preset--spacing--20);margin-bottom:var(--wp--preset--spacing--20);padding-top:var(--wp--preset--spacing--30);padding-right:var(--wp--preset--spacing--80);padding-bottom:var(--wp--preset--spacing--30);padding-left:var(--wp--preset--spacing--80)">
<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:100%">
<div class="wp-block-columns is-layout-flex wp-container-core-columns-is-layout-28f84493 wp-block-columns-is-layout-flex">
<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow">
<p class="has-text-align-center"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2692.png" alt="⚒" class="wp-smiley" style="height: 1em; max-height: 1em;" /><br><code><a href="https://github.com/gabrielkoehl/DBAScriptBox/blob/main/windows/security/security_setSqlSpn.ps1" target="_blank" rel="noopener" title="cluster_manageSQLCluster.ps1">security_setSqlSpn.ps1</a></code></p>
</div>



<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow">
<p class="has-text-align-center"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f4d3.png" alt="📓" class="wp-smiley" style="height: 1em; max-height: 1em;" /><br><code><a href="https://github.com/gabrielkoehl/DBAScriptBox/blob/main/windows/security/security_setSqlSpn.md" target="_blank" rel="noopener" title="">security_setSqlSpn</a></code><a href="https://github.com/gabrielkoehl/DBAScriptBox/blob/main/windows/security/security_setSqlSpn.md" title=""><code>.</code>md</a></p>
</div>
</div>
</div>
</div>



<p style="margin-top:var(--wp--preset--spacing--50);margin-bottom:var(--wp--preset--spacing--50)">A gMSA does not have the right to write its own SPNs in AD by default. When the SQL service starts, the registration often fails, leading to NTLM fallbacks. Even though I prefer to set SPNs in a controlled and manual manner, the automatic registration must be technically possible to avoid fallbacks.</p>



<p style="margin-bottom:var(--wp--preset--spacing--50)">The script solves this problem by granting the gMSA the rights in AD to manage its own SPNs, in addition to creating the SPNs. It sets ACLs on the nodes for <code>Read</code>, <code>Write</code>, and <code>Validated write</code> on the <code>servicePrincipalName</code> attribute.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: powershell; title: ; notranslate">
.\security_setSqlSpn.ps1 `
    -ServiceAccount &quot;LAB\g-LAB22A-oltp&quot; `
    -InstanceName &quot;LAB22A&quot; `
    -Port 51101 `
    -Hostnames &quot;LAB-NODE01&quot;,&quot;LAB-NODE02&quot; `
    -Domain &quot;lab.local&quot; `
    -IsGMSA `
    -SetADPermissions `
    -LogPath &quot;C:\Temp\SPN_AlwaysOn_$(Get-Date -Format &#039;yyyyMMdd_HHmmss&#039;).log&quot;
</pre></div>


<h3 class="wp-block-heading" id="5-verwendete-microsoft-cmdlets" style="margin-top:var(--wp--preset--spacing--70);margin-bottom:var(--wp--preset--spacing--50)">Microsoft Cmdlets Used</h3>



<p style="margin-top:var(--wp--preset--spacing--50);margin-bottom:var(--wp--preset--spacing--50)">On one hand, <a href="https://learn.microsoft.com/en-us/windows-server/administration/windows-commands/setspn" target="_blank" rel="noopener" title="setspn.exe"><code>setspn.exe</code></a> is used for SPN management, and on the other hand, the <a href="https://learn.microsoft.com/en-us/powershell/module/activedirectory/?view=windowsserver2025-ps" target="_blank" rel="noopener" title=""><code>ActiveDirectory</code></a> module is used again to verify accounts and extract SIDs.</p>



<p style="margin-top:var(--wp--preset--spacing--50);margin-bottom:var(--wp--preset--spacing--50)">Since none of the modules can manipulate Active Directory permissions, the .NET Framework <a href="https://learn.microsoft.com/en-us/dotnet/api/system.directoryservices?view=windowsdesktop-10.0" target="_blank" rel="noopener" title="System.DirectoryServices"><code>System.DirectoryServices</code></a> is used directly for this purpose, which provides various classes to completely turn AD inside out. </p>



<hr class="wp-block-separator has-alpha-channel-opacity" style="margin-top:var(--wp--preset--spacing--70);margin-bottom:var(--wp--preset--spacing--70)"/>



<h2 class="wp-block-heading" id="LSP" style="margin-top:var(--wp--preset--spacing--50);margin-bottom:var(--wp--preset--spacing--50)">Local Security Policies</h2>



<div class="wp-block-columns is-layout-flex wp-container-core-columns-is-layout-c901c877 wp-block-columns-is-layout-flex" style="border-width:1px;border-top-left-radius:2px;border-top-right-radius:2px;border-bottom-left-radius:2px;border-bottom-right-radius:2px;margin-top:var(--wp--preset--spacing--20);margin-bottom:var(--wp--preset--spacing--20);padding-top:var(--wp--preset--spacing--30);padding-right:var(--wp--preset--spacing--80);padding-bottom:var(--wp--preset--spacing--30);padding-left:var(--wp--preset--spacing--80)">
<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:100%">
<div class="wp-block-columns is-layout-flex wp-container-core-columns-is-layout-28f84493 wp-block-columns-is-layout-flex">
<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow">
<p class="has-text-align-center"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2692.png" alt="⚒" class="wp-smiley" style="height: 1em; max-height: 1em;" /><br><code><a href="https://github.com/gabrielkoehl/DBAScriptBox/blob/main/windows/security/security_setSqlLocalSec.ps1" target="_blank" rel="noopener" title="security_setSqlLocalSec.ps1">security_setSqlLocalSec.ps1</a></code></p>
</div>



<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow">
<p class="has-text-align-center"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f4d3.png" alt="📓" class="wp-smiley" style="height: 1em; max-height: 1em;" /><br><a href="https://github.com/gabrielkoehl/DBAScriptBox/blob/main/windows/security/security_setSqlLocalSec.md" target="_blank" rel="noopener" title=""><code>security_setSqlLocalSec.md</code></a></p>
</div>
</div>
</div>
</div>



<p style="margin-top:var(--wp--preset--spacing--50);margin-bottom:var(--wp--preset--spacing--50)">As everywhere in IT, SQL Server should also follow the <a href="https://en.wikipedia.org/wiki/Principle_of_least_privilege" target="_blank" rel="noopener" title="PoLP"><code>PoLP</code></a>, which is why no service account gets local administrator privileges. For SQL Server to still run flawlessly and performantly, the service account needs specific rights in the operating system (&#8222;User Rights Assignment&#8220;). Anyone who has done this manually a few times will be very happy about this solution. This must be executed locally on the respective node.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: powershell; title: ; notranslate">
# ===== Set Engine Permissions for gMSA =====

    .\security_setSqlLocalSec.ps1 `
        -ServiceAccount &quot;g-LAB22A-oltp$&quot; `
        -ServiceType &quot;engine&quot; `
        -IsGMSA `
        -LogPath &quot;C:\Temp\LAB22A_Engine_$(Get-Date -Format &#039;yyyyMMdd_HHmmss&#039;).log&quot;

# ===== Set Agent Permissions for gMSA =====

    .\security_setSqlLocalSec.ps1 `
        -ServiceAccount &quot;g-LAB22A-agnt$&quot; `
        -ServiceType &quot;agent&quot; `
        -IsGMSA `
        -LogPath &quot;C:\Temp\LAB22A_Agent_$(Get-Date -Format &#039;yyyyMMdd_HHmmss&#039;).log&quot;
</pre></div>


<h3 class="wp-block-heading" style="margin-top:var(--wp--preset--spacing--70);margin-bottom:var(--wp--preset--spacing--50)">Microsoft Cmdlets Used</h3>



<p style="margin-top:var(--wp--preset--spacing--50);margin-bottom:var(--wp--preset--spacing--50)">Since PowerShell has no native cmdlets for editing local security policies, the Windows tool <a href="https://learn.microsoft.com/en-us/windows-server/administration/windows-commands/secedit" target="_blank" rel="noopener" title="secedit.exe"><code>secedit.exe</code></a> is used and its export is manipulated.</p>



<hr class="wp-block-separator has-alpha-channel-opacity" style="margin-top:var(--wp--preset--spacing--70);margin-bottom:var(--wp--preset--spacing--70)"/>



<h2 class="wp-block-heading" style="margin-top:var(--wp--preset--spacing--50);margin-bottom:var(--wp--preset--spacing--50)">Summary</h2>



<p style="margin-top:var(--wp--preset--spacing--50);margin-bottom:var(--wp--preset--spacing--50)">With these four building blocks, the foundation for a SQL Server installation has been laid. It wasn&#8217;t just an installation that was performed, but a valid, documented configuration that considers identity (gMSA), network (DNS/SPN), and OS performance. In the next step, the actual SQL Server instance can be installed.</p>



<p>Also check out the linked MD files — they contain background information on best practices and further articles from Microsoft</p>



<figure class="wp-block-image aligncenter size-full" style="margin-top:var(--wp--preset--spacing--60);margin-bottom:var(--wp--preset--spacing--60)"><img decoding="async" width="307" height="328" src="https://dbavonnebenan.de/wp-content/uploads/2025/05/emotes_2.png" alt="good to know emote" class="wp-image-345" srcset="https://dbavonnebenan.de/wp-content/uploads/2025/05/emotes_2.png 307w, https://dbavonnebenan.de/wp-content/uploads/2025/05/emotes_2-281x300.png 281w" sizes="(max-width: 307px) 100vw, 307px" /></figure><p>The post <a href="https://dbavonnebenan.de/automate-sql-server-ha-ad-spn-and-cluster-setup_en/">Automate SQL Server HA: AD, SPN, and Cluster Setup</a> first appeared on <a href="https://dbavonnebenan.de">DBA von Nebenan</a>.</p>]]></content:encoded>
					
					<wfw:commentRss>https://dbavonnebenan.de/automate-sql-server-ha-ad-spn-and-cluster-setup_en/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>SQL Server HA: AD, SPN und Cluster Setup automatisieren</title>
		<link>https://dbavonnebenan.de/automate-sql-server-ha-ad-spn-and-cluster-setup_de/</link>
					<comments>https://dbavonnebenan.de/automate-sql-server-ha-ad-spn-and-cluster-setup_de/#respond</comments>
		
		<dc:creator><![CDATA[Gabriel]]></dc:creator>
		<pubDate>Fri, 06 Feb 2026 11:20:00 +0000</pubDate>
				<category><![CDATA[PowerShell]]></category>
		<category><![CDATA[Sql Server]]></category>
		<category><![CDATA[AD]]></category>
		<category><![CDATA[AlwaysOn]]></category>
		<category><![CDATA[DE]]></category>
		<category><![CDATA[Deployment]]></category>
		<category><![CDATA[GMSA]]></category>
		<category><![CDATA[SPN]]></category>
		<category><![CDATA[WSFC]]></category>
		<guid isPermaLink="false">https://dbavonnebenan.de/?p=641</guid>

					<description><![CDATA[<p>Production-Ready Windows Cluster Deployment: Automatisierung von Failover Cluster, gMSA, Kerberos und lokalen Rechten via PowerShell.</p>
<p>The post <a href="https://dbavonnebenan.de/automate-sql-server-ha-ad-spn-and-cluster-setup_de/">SQL Server HA: AD, SPN und Cluster Setup automatisieren</a> first appeared on <a href="https://dbavonnebenan.de">DBA von Nebenan</a>.</p>]]></description>
										<content:encoded><![CDATA[<div style="border: 1px dotted #eb2b14; border-radius: 6px; background-color: #eeeeee; padding-top: var(--wp--preset--spacing--30); padding-left: var(--wp--preset--spacing--30); padding-right: var(--wp--preset--spacing--30); padding-bottom: var(--wp--preset--spacing--30); " class="ub-styled-box ub-bordered-box wp-block-ub-styled-box" id="ub-styled-box-8bf7c8f5-ed36-44d1-82b0-6849bd1507a5">
<p class="has-open-sauce-sans-font-family" style="margin-top:0;margin-right:0;margin-bottom:0;margin-left:0" id="ub-styled-box-bordered-content-">Was erwartet dich hier?</p>



<ul style="margin-top:0;margin-right:0;margin-bottom:0;margin-left:0" class="wp-block-list has-open-sauce-sans-font-family">
<li><a href="#WSFC" title="">Windows Server Failover Cluster Deployment mit erweiterten AD- und DNS-Berechtigungen</a></li>



<li><a href="#GMSA" title="">Identity Management: Group Managed Service Accounts (gMSA)</a></li>



<li><a href="#KERBEROS" title="">Kerberos und Service Principal Names (SPN)</a></li>



<li><a href="#LSP" title="">Lokale Sicherheitsrichtlinien (Local Security Policies)</a></li>



<li>Hintergrundwissen warum Konfigurationen gemacht werden wie sie gemacht werden</li>
</ul>


</div>


<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>


https://github.com/gabrielkoehl/DBAScriptBox


<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<h2 class="wp-block-heading" id="0-intro" style="margin-top:var(--wp--preset--spacing--50);margin-bottom:var(--wp--preset--spacing--50)">Intro</h2>



<p>Wer kennt es nicht? Bevor man überhaupt mit der Installation von SQL Server loslegen kann, hat man sich entweder repetitiv durch seitenweise schlechte GUIs geklickt oder wenn verfügbar, mit einer losen Sammlung an Scripten jongliert und semiautomatisch alle Abhängigkeiten und Best Practices eingerichtet.</p>



<div class="wp-block-columns is-layout-flex wp-container-core-columns-is-layout-28f84493 wp-block-columns-is-layout-flex">
<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:80%">
<p>Noch schlimmer ist der Fall, wenn man als Berater gar keinen Domain Admin hat (was auch so sein sollte) und man den Kunden, der dies vielleicht noch nie gemacht hat, per Teams &amp; Co anleiten muss. Wenn dies nicht eh eine geplante Installation mit Know-how-Transfer ist, erzeugt so etwas Schmerz. <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f92d.png" alt="🤭" class="wp-smiley" style="height: 1em; max-height: 1em;" /></p>
</div>



<div class="wp-block-column is-vertically-aligned-center is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:20%">
<figure class="wp-block-image aligncenter size-full is-resized"><img decoding="async" width="264" height="329" src="https://dbavonnebenan.de/wp-content/uploads/2025/06/emote-4.png" alt="PAIN Emote" class="wp-image-516" style="aspect-ratio:0.8024665981500514;width:75px;height:auto" srcset="https://dbavonnebenan.de/wp-content/uploads/2025/06/emote-4.png 264w, https://dbavonnebenan.de/wp-content/uploads/2025/06/emote-4-241x300.png 241w" sizes="(max-width: 264px) 100vw, 264px" /></figure>
</div>
</div>



<p>Dementsprechend habe ich nun mal all meine losen Scripte zusammengewürfelt und für jeden Themenbereich eine recht umfangreiche Funktion entwickelt, welche all diese Tasks automatisiert und für Archivzwecke loggt. Danke auch an Claude für die Unterstützung. Keiner kontrolliert und dokumentiert Code besser als Claude. <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f60b.png" alt="😋" class="wp-smiley" style="height: 1em; max-height: 1em;" /></p>



<p>Nun könnte man sich fragen: Es gibt doch dbatools und DSC? Klar, nutze ich auch gerne. Aber transparente, nachvollziehbare Scripts ohne Framework-Overhead vermitteln Wissen besser &#8211; gerade wenn angehenden DBAs der Blick fürs Ganze fehlt. Betrieb von SQL Server fängt auf dem Blech an, geht über Storage, Windows Server, Active Directory und hört bei einer Certificate Authority auf. Und bei KRITIS-Unternehmen oder kleinen Mittelständlern mit 3 Clustern will ich zu Beginn keine Grundsatzdiskussion über dbatools oder DSC führen.</p>



<p>Los geht&#8217;s mit einem Production-Ready Windows Server Failover Cluster &#8211; inklusive CNO/VCO-Berechtigungen, DNS-Registrierung und Quorum-Konfiguration. Alles wiederholbar, alles geloggt, alles nachvollziehbar.</p>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<h2 class="wp-block-heading" id="1-das-lab-szenario" style="margin-top:var(--wp--preset--spacing--50);margin-bottom:var(--wp--preset--spacing--50)">Das Lab Szenario</h2>



<p style="margin-bottom:var(--wp--preset--spacing--40)">Um die Schritte greifbar zu machen, wird folgende Umgebung und Naming-Conventions genutzt</p>



<ul class="wp-block-list">
<li><strong>Nodes</strong>
<ul class="wp-block-list">
<li><code>LAB-NODE01</code> (192.168.100.12)</li>



<li><code>LAB-NODE02</code> (192.168.100.13)</li>
</ul>
</li>



<li><strong>Cluster Name</strong>
<ul class="wp-block-list">
<li><code>MSSQL-CL-01</code> (192.168.100.14)</li>
</ul>
</li>



<li><strong>SQL Instanz</strong>
<ul class="wp-block-list">
<li><code>LAB22A</code></li>
</ul>
</li>



<li><strong>Domain</strong>
<ul class="wp-block-list">
<li><code>lab.local</code></li>
</ul>
</li>



<li><strong>Service Accounts</strong> Group Managed Service Accounts (gMSA)
<ul class="wp-block-list">
<li><code>g-LAB22A-oltp</code></li>



<li><code>g-LAB22A-agnt</code></li>
</ul>
</li>
</ul>



<hr class="wp-block-separator has-alpha-channel-opacity" style="margin-top:var(--wp--preset--spacing--60);margin-bottom:var(--wp--preset--spacing--60)"/>



<h2 class="wp-block-heading" id="WSFC" style="margin-top:var(--wp--preset--spacing--50);margin-bottom:var(--wp--preset--spacing--50)">Windows Server Failover Cluster Deployment </h2>



<div class="wp-block-columns is-layout-flex wp-container-core-columns-is-layout-c901c877 wp-block-columns-is-layout-flex" style="border-width:1px;border-top-left-radius:2px;border-top-right-radius:2px;border-bottom-left-radius:2px;border-bottom-right-radius:2px;margin-top:var(--wp--preset--spacing--20);margin-bottom:var(--wp--preset--spacing--20);padding-top:var(--wp--preset--spacing--30);padding-right:var(--wp--preset--spacing--80);padding-bottom:var(--wp--preset--spacing--30);padding-left:var(--wp--preset--spacing--80)">
<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:100%">
<div class="wp-block-columns is-layout-flex wp-container-core-columns-is-layout-28f84493 wp-block-columns-is-layout-flex">
<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow">
<p class="has-text-align-center"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2692.png" alt="⚒" class="wp-smiley" style="height: 1em; max-height: 1em;" /><br><a href="https://github.com/gabrielkoehl/DBAScriptBox/blob/main/windows/system/cluster_manageSQLCluster.ps1" target="_blank" rel="noopener" title="cluster_manageSQLCluster.ps1"><code>cluster_manageSQLCluster.ps1</code></a></p>
</div>



<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow">
<p class="has-text-align-center"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f4d3.png" alt="📓" class="wp-smiley" style="height: 1em; max-height: 1em;" /><br><code><a href="https://github.com/gabrielkoehl/DBAScriptBox/blob/main/windows/system/cluster_manageSQLCluster.md" target="_blank" rel="noopener" title="cluster_manageSQLCluster.md">cluster_manageSQLCluster.md</a></code></p>
</div>
</div>
</div>
</div>



<div style="height:14px" aria-hidden="true" class="wp-block-spacer"></div>



<p class="has-text-align-left">Der WSFC bildet das Fundament für AlwaysOn Availability Groups. Das Script steuert den gesamten Lifecycle von der Feature-Installation bis zur Witness-Konfiguration.</p>



<h3 class="wp-block-heading has-text-align-left" id="3-feature-installation-amp-cluster-erstellung" style="margin-top:var(--wp--preset--spacing--70);margin-bottom:var(--wp--preset--spacing--50)">Feature Installation &amp; Cluster Erstellung</h3>



<p style="margin-top:var(--wp--preset--spacing--50);margin-bottom:var(--wp--preset--spacing--50)">Zunächst wird das Feature <code>Failover-Clustering</code> auf den einzelnen Nodes installiert und der Cluster erstellt. Ein wichtiger Aspekt bei der Automatisierung ist das Cluster-Objekt (CNO): Es benötigt Schreibrechte im DNS und Active Directory um später automatisiert Listener-Records anlegen zu können. </p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: powershell; auto-links: false; title: ; notranslate">
.\cluster_manageSQLCluster.ps1 `
    -Operation InstallFeature `
    -Nodes &quot;LAB-NODE01&quot;,&quot;LAB-NODE02&quot; `
    -LogPath &quot;C:\Temp\Cluster_Feature_$(Get-Date -Format &#039;yyyyMMdd_HHmmss&#039;).log&quot;
</pre></div>


<p class="has-text-align-left" style="margin-top:var(--wp--preset--spacing--50);margin-bottom:var(--wp--preset--spacing--50)">Anschließend wird der Cluster erstellt. Das Script setzt zusätzlich die entsprechenden Berechtigungen in AD und DNS.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: powershell; title: ; notranslate">
.\cluster_manageSQLCluster.ps1 `
    -Operation CreateCluster `
    -ClusterName &quot;MSSQL-CL-01&quot; `
    -Nodes &quot;LAB-NODE01&quot;,&quot;LAB-NODE02&quot; `
    -StaticIP &quot;192.168.100.14&quot; `
    -DnsDomain &quot;lab.local&quot; `
    -TargetOU &quot;OU=lab_computer,DC=lab,DC=local&quot; `
    -LogPath &quot;C:\Temp\Cluster_Create_$(Get-Date -Format &#039;yyyyMMdd_HHmmss&#039;).log&quot;
</pre></div>


<h3 class="wp-block-heading" id="4-file-share-witness-quorum" style="margin-top:var(--wp--preset--spacing--70);margin-bottom:var(--wp--preset--spacing--50)">File Share Witness (Quorum)</h3>



<p style="margin-top:var(--wp--preset--spacing--50);margin-bottom:var(--wp--preset--spacing--50)">Für einen 2-Node Cluster ist ein Witness zwingend erforderlich, um Split-Brain-Szenarien zu vermeiden. Hier wird ein File Share auf dem Domain Controller (<code>\\LAB-DC\quorum$</code>) genutzt. </p>



<p style="margin-top:var(--wp--preset--spacing--50);margin-bottom:var(--wp--preset--spacing--50)"><strong>Manueller Zwischenschritt</strong><br>Das Cluster-Computer-Objekt (<code>MSSQL-CL-01$</code>) benötigt <strong>Full Control</strong> auf Ebene der Share- und NTFS-Berechtigungen des Witness-Pfads (<code>\\LAB-DC\quorum$</code>). Dies muss vor dem folgenden Befehl sichergestellt sein.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: powershell; title: ; notranslate">
.\cluster_manageSQLCluster.ps1 `
    -Operation SetWitness `
    -ClusterName &quot;MSSQL-CL-01&quot; `
    -WitnessShare &quot;\\LAB-DC\quorum$&quot; `
    -LogPath &quot;C:\Temp\Cluster_Witness_$(Get-Date -Format &#039;yyyyMMdd_HHmmss&#039;).log&quot;
</pre></div>


<h3 class="wp-block-heading" id="5-verwendete-microsoft-cmdlets" style="margin-top:var(--wp--preset--spacing--70);margin-bottom:var(--wp--preset--spacing--50)">Verwendete Microsoft Cmdlets</h3>



<p style="margin-top:var(--wp--preset--spacing--50);margin-bottom:var(--wp--preset--spacing--50)">Das Script nutzt primär das <a href="https://learn.microsoft.com/en-us/powershell/module/failoverclusters/" target="_blank" rel="noopener" title="FailoverClusters"><code>FailoverClusters</code></a> Modul</p>



<ul style="margin-top:var(--wp--preset--spacing--50);margin-bottom:var(--wp--preset--spacing--50)" class="wp-block-list">
<li><code>New-Cluster</code>
<ul class="wp-block-list">
<li>Erstellt den Cluster, bindet die Nodes ein und setzt die Management-IP</li>
</ul>
</li>



<li><code>Set-ClusterQuorum</code>
<ul class="wp-block-list">
<li>Konfiguriert den Witness-Modus (hier: File Share Witness)</li>
</ul>
</li>



<li><code>Install-WindowsFeature</code>
<ul class="wp-block-list">
<li>Installiert die RSAT-Tools und den Cluster-Dienst</li>
</ul>
</li>
</ul>



<hr class="wp-block-separator has-alpha-channel-opacity" style="margin-top:var(--wp--preset--spacing--70);margin-bottom:var(--wp--preset--spacing--70)"/>



<h2 class="wp-block-heading" id="GMSA" style="margin-top:var(--wp--preset--spacing--50);margin-bottom:var(--wp--preset--spacing--50)">Identity Management: gMSA</h2>



<div class="wp-block-columns is-layout-flex wp-container-core-columns-is-layout-c901c877 wp-block-columns-is-layout-flex" style="border-width:1px;border-top-left-radius:2px;border-top-right-radius:2px;border-bottom-left-radius:2px;border-bottom-right-radius:2px;margin-top:var(--wp--preset--spacing--20);margin-bottom:var(--wp--preset--spacing--20);padding-top:var(--wp--preset--spacing--30);padding-right:var(--wp--preset--spacing--80);padding-bottom:var(--wp--preset--spacing--30);padding-left:var(--wp--preset--spacing--80)">
<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:100%">
<div class="wp-block-columns is-layout-flex wp-container-core-columns-is-layout-28f84493 wp-block-columns-is-layout-flex">
<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow">
<p class="has-text-align-center"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2692.png" alt="⚒" class="wp-smiley" style="height: 1em; max-height: 1em;" /><br><a href="https://github.com/gabrielkoehl/DBAScriptBox/blob/main/windows/security/security_createGMSA.ps1" target="_blank" rel="noopener" title="security_createGMSA.ps1"><code>security_createGMSA.ps1</code></a></p>
</div>



<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow">
<p class="has-text-align-center"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f4d3.png" alt="📓" class="wp-smiley" style="height: 1em; max-height: 1em;" /><br><a href="https://github.com/gabrielkoehl/DBAScriptBox/blob/main/windows/security/security_createGMSA.md" target="_blank" rel="noopener" title="security_createGMSA.md"><code>security_createGMSA.md</code></a></p>
</div>
</div>
</div>
</div>



<p style="margin-top:var(--wp--preset--spacing--50);margin-bottom:var(--wp--preset--spacing--50)">Die Nutzung von Standard-Usern als Service Accounts kann zu abgelaufenen Passwörtern und Downtime führen. Die empfohlene Methode für SQL Server sind <a href="https://learn.microsoft.com/en-us/windows-server/identity/ad-ds/manage/group-managed-service-accounts/group-managed-service-accounts/group-managed-service-accounts-overview" target="_blank" rel="noopener" title="gMSA"><code>Group Managed Service Accounts (gMSA)</code></a>. Hierbei übernimmt der Key Distribution Service (KDS) des Active Directory die Passwortrotation; Niemand muss das Passwort kennen.</p>



<h3 class="wp-block-heading" style="margin-top:var(--wp--preset--spacing--70);margin-right:0;margin-bottom:var(--wp--preset--spacing--50);margin-left:0">KDS Root Key</h3>



<p style="margin-top:var(--wp--preset--spacing--50);margin-bottom:var(--wp--preset--spacing--50)">Bevor der erste gMSA erstellt werden kann, muss einmalig pro Forest ein KDS Root Key existieren. Sollte bereits ein Key existieren, wird die Erstellung übersprungen.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: powershell; title: ; notranslate">
.\security_createGMSA.ps1 `
    -InitializeKDS `
    -LogPath &quot;C:\Temp\KDS_Init.log&quot;
</pre></div>


<h3 class="wp-block-heading" style="margin-top:var(--wp--preset--spacing--70);margin-right:0;margin-bottom:var(--wp--preset--spacing--50);margin-left:0">gMSA Erstellung</h3>



<p style="margin-top:var(--wp--preset--spacing--50);margin-bottom:var(--wp--preset--spacing--50)">Anstatt Server direkt einem gMSA zuzuordnen, ist es Best Practice, eine Security Group zu erstellen (<code>SQL_LAB_CL01</code>), welche die Computerkonten der Cluster-Nodes enthält. Neben der Einrichtung der eigentlichen gMSA kann dieses Script auch die Security Gruppen erstellen und die Zuordnungen vornehmen. Und immer schön die Beschreibungen bei AD-Objekten pflegen. </p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: powershell; title: ; notranslate">
.\security_createGMSA.ps1 `
    -AccountName @( `
        @(&quot;g-LAB22A-oltp&quot;, &quot;SQL Server Engine Account for LAB22A&quot;), `
        @(&quot;g-LAB22A-agnt&quot;, &quot;SQL Server Agent Account for LAB22A&quot;) ) `
    -SecurityGroupName &quot;SQL_LAB_CL01&quot; `
    -SecurityGroupDescription &quot;SQL Server Cluster CL01 gMSA Group&quot; `
    -ServerNames &quot;LAB-NODE01&quot;,&quot;LAB-NODE02&quot; `
    -CreateSecurityGroup `
    -SecurityGroupOU &quot;OU=lab_secgrp,DC=lab,DC=local&quot; `
    -PasswordIntervalDays 90 `
    -LogPath &quot;C:\Temp\gMSA_Creation_$(Get-Date -Format &#039;yyyyMMdd_HHmmss&#039;).log&quot;
</pre></div>


<p style="margin-top:var(--wp--preset--spacing--50);margin-bottom:0">Nach diesem Schritt ist ein Neustart der Nodes erforderlich, damit die neue Gruppenmitgliedschaft wirksam wird.</p>



<h3 class="wp-block-heading" style="margin-top:var(--wp--preset--spacing--70);margin-right:0;margin-bottom:var(--wp--preset--spacing--50);margin-left:0">Lokale Registrierung</h3>



<p style="margin-top:var(--wp--preset--spacing--50);margin-bottom:var(--wp--preset--spacing--50)">Nach der Erstellung müssen die gMSA lokal auf den Nodes registriert werden. Hierzu kann folgendes  Script genutzt werden.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: powershell; title: ; notranslate">
# Wichtig für lokale Einrichtung ( Local Admin )
Add-WindowsFeature -Name RSAT-AD-PowerShell

$gMSAs = @(
    &#039;g-LAB22A-oltp&#039;,
    &#039;g-LAB22A-agnt&#039;
)

foreach ($gMSA in $gMSAs) {

    if (Test-ADServiceAccount -Identity $gMSA -ErrorAction SilentlyContinue) {
        try {
            Install-ADServiceAccount -Identity $gMSA -ErrorAction Stop
            Write-Host &quot;$gMSA installiert&quot; -ForegroundColor Green
        } catch {
            Write-Host &quot;$gMSA - Fehler bei Installation: $($_.Exception.Message)&quot; -ForegroundColor Red
        }
    } else {
        Write-Host &quot;$gMSA existiert nicht&quot; -ForegroundColor Yellow
    }
}
</pre></div>


<p style="margin-top:var(--wp--preset--spacing--50);margin-bottom:0">Nach diesem Schritt ist ein Neustart der Nodes erforderlich, damit die neue Gruppenmitgliedschaft (TGT) wirksam wird.</p>



<h3 class="wp-block-heading" id="5-verwendete-microsoft-cmdlets" style="margin-top:var(--wp--preset--spacing--70);margin-bottom:var(--wp--preset--spacing--50)">Verwendete Microsoft Cmdlets</h3>



<p style="margin-top:var(--wp--preset--spacing--50);margin-bottom:var(--wp--preset--spacing--50)">Das Script nutzt primär das <a href="https://learn.microsoft.com/en-us/powershell/module/activedirectory/?view=windowsserver2025-ps" target="_blank" rel="noopener" title="ActiveDirectory"><code>ActiveDirectory</code></a> Modul</p>



<ul style="margin-top:var(--wp--preset--spacing--50);margin-bottom:var(--wp--preset--spacing--50)" class="wp-block-list">
<li><code>Add-KdsRootKey</code>
<ul class="wp-block-list">
<li>Erzeugt den Root Key für den Group Key Distribution Service</li>
</ul>
</li>



<li><code>New-ADServiceAccount</code>
<ul class="wp-block-list">
<li>Erstellt das gMSA Objekt im AD</li>
</ul>
</li>



<li><code>Set-ADServiceAccount</code>
<ul class="wp-block-list">
<li>Konfiguriert, welche Computer (PrincipalsAllowedToRetrieveManagedPassword) das Passwort lesen dürfen</li>
</ul>
</li>
</ul>



<hr class="wp-block-separator has-alpha-channel-opacity" style="margin-top:var(--wp--preset--spacing--70);margin-bottom:var(--wp--preset--spacing--70)"/>



<h2 class="wp-block-heading" id="KERBEROS" style="margin-top:var(--wp--preset--spacing--50);margin-bottom:var(--wp--preset--spacing--50)">Kerberos und Service Principal Names (SPN)</h2>



<div class="wp-block-columns is-layout-flex wp-container-core-columns-is-layout-c901c877 wp-block-columns-is-layout-flex" style="border-width:1px;border-top-left-radius:2px;border-top-right-radius:2px;border-bottom-left-radius:2px;border-bottom-right-radius:2px;margin-top:var(--wp--preset--spacing--20);margin-bottom:var(--wp--preset--spacing--20);padding-top:var(--wp--preset--spacing--30);padding-right:var(--wp--preset--spacing--80);padding-bottom:var(--wp--preset--spacing--30);padding-left:var(--wp--preset--spacing--80)">
<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:100%">
<div class="wp-block-columns is-layout-flex wp-container-core-columns-is-layout-28f84493 wp-block-columns-is-layout-flex">
<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow">
<p class="has-text-align-center"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2692.png" alt="⚒" class="wp-smiley" style="height: 1em; max-height: 1em;" /><br><code><a href="https://github.com/gabrielkoehl/DBAScriptBox/blob/main/windows/security/security_setSqlSpn.ps1" target="_blank" rel="noopener" title="cluster_manageSQLCluster.ps1">security_setSqlSpn.ps1</a></code></p>
</div>



<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow">
<p class="has-text-align-center"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f4d3.png" alt="📓" class="wp-smiley" style="height: 1em; max-height: 1em;" /><br><code><a href="https://github.com/gabrielkoehl/DBAScriptBox/blob/main/windows/security/security_setSqlSpn.md" target="_blank" rel="noopener" title="">security_setSqlSpn</a></code><a href="https://github.com/gabrielkoehl/DBAScriptBox/blob/main/windows/security/security_setSqlSpn.md" title=""><code>.</code>md</a></p>
</div>
</div>
</div>
</div>



<p style="margin-top:var(--wp--preset--spacing--50);margin-bottom:var(--wp--preset--spacing--50)">Ein gMSA besitzt standardmäßig nicht das Recht, seine eigenen SPNs im AD zu schreiben. Startet der SQL Dienst, schlägt die Registrierung oft fehl, was zu NTLM-Fallbacks führt. Auch wenn ich SPNs bevorzugt kontrolliert und manuell setze, muss die automatische Registrierung technisch möglich sein, um Fallbacks zu vermeiden.</p>



<p style="margin-bottom:var(--wp--preset--spacing--50)">Das Script löst dieses Problem, indem es zusätzlich zur SPN Erstellung dem gMSA die Rechte im AD gewährt, seine eigenen SPNs zu verwalten. Es setzt ACLs der Nodes für <code>Read</code>, <code>Write</code> und <code>Validated write</code> auf das Attribut <code>servicePrincipalName</code>.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: powershell; title: ; notranslate">
.\security_setSqlSpn.ps1 `
    -ServiceAccount &quot;LAB\g-LAB22A-oltp&quot; `
    -InstanceName &quot;LAB22A&quot; `
    -Port 51101 `
    -Hostnames &quot;LAB-NODE01&quot;,&quot;LAB-NODE02&quot; `
    -Domain &quot;lab.local&quot; `
    -IsGMSA `
    -SetADPermissions `
    -LogPath &quot;C:\Temp\SPN_AlwaysOn_$(Get-Date -Format &#039;yyyyMMdd_HHmmss&#039;).log&quot;
</pre></div>


<h3 class="wp-block-heading" id="5-verwendete-microsoft-cmdlets" style="margin-top:var(--wp--preset--spacing--70);margin-bottom:var(--wp--preset--spacing--50)">Verwendete Microsoft Cmdlets</h3>



<p style="margin-top:var(--wp--preset--spacing--50);margin-bottom:var(--wp--preset--spacing--50)">Zum einen wird <a href="https://learn.microsoft.com/en-us/windows-server/administration/windows-commands/setspn" target="_blank" rel="noopener" title="setspn.exe"><code>setspn.exe</code></a> für Verwaltung der SPN genutzt, zum anderen wieder das <a href="https://learn.microsoft.com/en-us/powershell/module/activedirectory/?view=windowsserver2025-ps" target="_blank" rel="noopener" title=""><code>ActiveDirectory</code></a> Modul, um Accounts zu verifizieren und die SIDs zu extrahieren.</p>



<p style="margin-top:var(--wp--preset--spacing--50);margin-bottom:var(--wp--preset--spacing--50)">Da keines der Module Active Directory Permsission manipulieren kann, wird hierfür direkt das .NET Framework <a href="https://learn.microsoft.com/en-us/dotnet/api/system.directoryservices?view=windowsdesktop-10.0" target="_blank" rel="noopener" title="System.DirectoryServices"><code>System.DirectoryServices</code></a> genutzt, welches diverse Klassen bereitstellt, um das AD komplett auf Links zu drehen. </p>



<hr class="wp-block-separator has-alpha-channel-opacity" style="margin-top:var(--wp--preset--spacing--70);margin-bottom:var(--wp--preset--spacing--70)"/>



<h2 class="wp-block-heading" id="LSP" style="margin-top:var(--wp--preset--spacing--50);margin-bottom:var(--wp--preset--spacing--50)">Lokale Sicherheitsrichtlinien (Local Security Policies)</h2>



<div class="wp-block-columns is-layout-flex wp-container-core-columns-is-layout-c901c877 wp-block-columns-is-layout-flex" style="border-width:1px;border-top-left-radius:2px;border-top-right-radius:2px;border-bottom-left-radius:2px;border-bottom-right-radius:2px;margin-top:var(--wp--preset--spacing--20);margin-bottom:var(--wp--preset--spacing--20);padding-top:var(--wp--preset--spacing--30);padding-right:var(--wp--preset--spacing--80);padding-bottom:var(--wp--preset--spacing--30);padding-left:var(--wp--preset--spacing--80)">
<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:100%">
<div class="wp-block-columns is-layout-flex wp-container-core-columns-is-layout-28f84493 wp-block-columns-is-layout-flex">
<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow">
<p class="has-text-align-center"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2692.png" alt="⚒" class="wp-smiley" style="height: 1em; max-height: 1em;" /><br><code><a href="https://github.com/gabrielkoehl/DBAScriptBox/blob/main/windows/security/security_setSqlLocalSec.ps1" target="_blank" rel="noopener" title="security_setSqlLocalSec.ps1">security_setSqlLocalSec.ps1</a></code></p>
</div>



<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow">
<p class="has-text-align-center"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f4d3.png" alt="📓" class="wp-smiley" style="height: 1em; max-height: 1em;" /><br><a href="https://github.com/gabrielkoehl/DBAScriptBox/blob/main/windows/security/security_setSqlLocalSec.md" target="_blank" rel="noopener" title=""><code>security_setSqlLocalSec.md</code></a></p>
</div>
</div>
</div>
</div>



<p style="margin-top:var(--wp--preset--spacing--50);margin-bottom:var(--wp--preset--spacing--50)">Wie überall in der IT sollte auch beim Sql Server nach dem <a href="https://en.wikipedia.org/wiki/Principle_of_least_privilege" target="_blank" rel="noopener" title="PoLP"><code>PoLP</code></a> gearbeitet werden, weshalb kein Service Account den lokalen Administrator bekommt.Damit SQL Server trotzdem fehlerfrei und performant läuft, benötigt der Service Account spezifische Rechte im Betriebssystem (&#8222;User Rights Assignment&#8220;). Wer dies ein paar mal manuell gemacht hat, wird sich sehr über diese Lösung freuen. Diese muss lokal auf dem jeweiligen Node ausgeführt werden.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: powershell; title: ; notranslate">
# ===== Set Engine Permissions for gMSA =====

    .\security_setSqlLocalSec.ps1 `
        -ServiceAccount &quot;g-LAB22A-oltp$&quot; `
        -ServiceType &quot;engine&quot; `
        -IsGMSA `
        -LogPath &quot;C:\Temp\LAB22A_Engine_$(Get-Date -Format &#039;yyyyMMdd_HHmmss&#039;).log&quot;

# ===== Set Agent Permissions for gMSA =====

    .\security_setSqlLocalSec.ps1 `
        -ServiceAccount &quot;g-LAB22A-agnt$&quot; `
        -ServiceType &quot;agent&quot; `
        -IsGMSA `
        -LogPath &quot;C:\Temp\LAB22A_Agent_$(Get-Date -Format &#039;yyyyMMdd_HHmmss&#039;).log&quot;
</pre></div>


<h3 class="wp-block-heading" style="margin-top:var(--wp--preset--spacing--70);margin-bottom:var(--wp--preset--spacing--50)">Verwendete Microsoft Cmdlets</h3>



<p style="margin-top:var(--wp--preset--spacing--50);margin-bottom:var(--wp--preset--spacing--50)">Da PowerShell keine nativen Cmdlets besitzt, um lokale Sicherheitsrichtlinien (LSA) zu bearbeiten, wird das Windows-Tool <a href="https://learn.microsoft.com/en-us/windows-server/administration/windows-commands/secedit" target="_blank" rel="noopener" title="secedit.exe"><code>secedit.exe</code></a> genutzt und der Export manipuliert.</p>



<hr class="wp-block-separator has-alpha-channel-opacity" style="margin-top:var(--wp--preset--spacing--70);margin-bottom:var(--wp--preset--spacing--70)"/>



<h2 class="wp-block-heading" style="margin-top:var(--wp--preset--spacing--50);margin-bottom:var(--wp--preset--spacing--50)">Summary</h2>



<p style="margin-top:var(--wp--preset--spacing--50);margin-bottom:var(--wp--preset--spacing--50)">Mit diesen vier Bausteinen wurde das Fundament für eine Sql Server Installation gelegt. Es wurde nicht nur eine Installation durchgeführt, sondern eine valide, dokumentierte Konfiguration geschaffen, die Identität (gMSA), Netzwerk (DNS/SPN) und OS-Performance (LSA) berücksichtigt. Im nächsten Schritt kann die eigentliche SQL Server Instanz installiert werden.</p>



<p>Schaut euch auch die verlinkten MD-Files an, dort stehen Hintergrundinfos zu den Best Practices und weiterführende Artikel von Microsoft</p>



<figure class="wp-block-image aligncenter size-full" style="margin-top:var(--wp--preset--spacing--60);margin-bottom:var(--wp--preset--spacing--60)"><img loading="lazy" decoding="async" width="307" height="328" src="https://dbavonnebenan.de/wp-content/uploads/2025/05/emotes_2.png" alt="good to know emote" class="wp-image-345" srcset="https://dbavonnebenan.de/wp-content/uploads/2025/05/emotes_2.png 307w, https://dbavonnebenan.de/wp-content/uploads/2025/05/emotes_2-281x300.png 281w" sizes="auto, (max-width: 307px) 100vw, 307px" /></figure><p>The post <a href="https://dbavonnebenan.de/automate-sql-server-ha-ad-spn-and-cluster-setup_de/">SQL Server HA: AD, SPN und Cluster Setup automatisieren</a> first appeared on <a href="https://dbavonnebenan.de">DBA von Nebenan</a>.</p>]]></content:encoded>
					
					<wfw:commentRss>https://dbavonnebenan.de/automate-sql-server-ha-ad-spn-and-cluster-setup_de/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Measuring SQL Server Disk Latency Correctly – snapDiskLatency</title>
		<link>https://dbavonnebenan.de/sql-server-disk-latency-en/</link>
					<comments>https://dbavonnebenan.de/sql-server-disk-latency-en/#respond</comments>
		
		<dc:creator><![CDATA[Gabriel]]></dc:creator>
		<pubDate>Thu, 17 Jul 2025 14:21:36 +0000</pubDate>
				<category><![CDATA[Sql Server]]></category>
		<category><![CDATA[TSql]]></category>
		<category><![CDATA[diskio]]></category>
		<category><![CDATA[DISKSPD]]></category>
		<category><![CDATA[eng]]></category>
		<category><![CDATA[latency]]></category>
		<category><![CDATA[PERFORMANCE]]></category>
		<guid isPermaLink="false">https://dbavonnebenan.de/?p=579</guid>

					<description><![CDATA[<p>How to get correct SQL Server Disk Latency values instead of falsified DMV data. Open Source snapDiskLatency solution for long-term analysis.</p>
<p>The post <a href="https://dbavonnebenan.de/sql-server-disk-latency-en/">Measuring SQL Server Disk Latency Correctly – snapDiskLatency</a> first appeared on <a href="https://dbavonnebenan.de">DBA von Nebenan</a>.</p>]]></description>
										<content:encoded><![CDATA[<div style="border: 1px dotted #eb2b14; border-radius: 0%; background-color: #eeeeee; padding-top: var(--wp--preset--spacing--30); padding-left: var(--wp--preset--spacing--30); padding-right: var(--wp--preset--spacing--30); padding-bottom: var(--wp--preset--spacing--30); " class="ub-styled-box ub-bordered-box wp-block-ub-styled-box" id="ub-styled-box-ed9a98aa-0cd4-41e3-8f7e-56d29b672df9">
<p class="has-open-sauce-sans-font-family" style="margin-top:0;margin-right:0;margin-bottom:0;margin-left:0" id="ub-styled-box-bordered-content-">What&#8217;s available here?</p>



<ul style="margin-top:0;margin-right:0;margin-bottom:0;margin-left:0" class="wp-block-list has-open-sauce-sans-font-family">
<li><a href="#DISKSPD" title="">DISKSPD Templates and Wrapper</a></li>



<li><a href="#sqlfilestats" title="">How can I display the latencies of individual database files?</a></li>



<li><a href="#snapdisk" title="">Long-term analysis of latencies with snapDiskLatency</a></li>
</ul>


</div>


<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>


https://github.com/gabrielkoehl/DBAScriptBox


<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<h2 class="wp-block-heading" style="margin-top:var(--wp--preset--spacing--50);margin-bottom:var(--wp--preset--spacing--50)">Intro</h2>



<p>Currently, I&#8217;m involved in a code review at a client. We&#8217;re examining not only the application but also all interfaces. This naturally includes the SQL Server landscape, which I set up at the beginning of the year.</p>



<div class="wp-block-columns is-layout-flex wp-container-core-columns-is-layout-28f84493 wp-block-columns-is-layout-flex">
<div class="wp-block-column is-vertically-aligned-center is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:85%">
<p class="has-open-sauce-sans-font-family">The result was more findings in my direction than expected, and I immediately felt challenged. I was able to refute most of it, some topics were the client&#8217;s responsibility, and one topic caught my interest purely out of curiosity.</p>



<p class="has-text-align-center" style="margin-top:var(--wp--preset--spacing--50);margin-bottom:var(--wp--preset--spacing--50)"><strong>Read- and Write Latencies of TempDB</strong> ( and every other DB )</p>
</div>



<div class="wp-block-column is-vertically-aligned-center is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:15%">
<figure class="wp-block-image aligncenter size-full is-resized"><img loading="lazy" decoding="async" width="264" height="329" src="https://dbavonnebenan.de/wp-content/uploads/2025/06/emote-4.png" alt="" class="wp-image-516" style="width:131px;height:auto" srcset="https://dbavonnebenan.de/wp-content/uploads/2025/06/emote-4.png 264w, https://dbavonnebenan.de/wp-content/uploads/2025/06/emote-4-241x300.png 241w" sizes="auto, (max-width: 264px) 100vw, 264px" /></figure>
</div>
</div>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<h2 class="wp-block-heading" style="margin-top:var(--wp--preset--spacing--50);margin-bottom:var(--wp--preset--spacing--50)">Background</h2>



<p>The client doesn&#8217;t operate their own data center, so they&#8217;re dependent on the hoster, who unfortunately isn&#8217;t very communicative. Therefore, I had to get a picture of the totally overcommitted hardware through various other means and challenge the hoster. This was also the reason why I was quite precisely informed about what the hoster&#8217;s hosts and storage systems were capable of delivering.</p>



<p>That&#8217;s why I was very surprised that 32ms Write Latency was listed as a finding. No question, that&#8217;s a bad value for AllFlash NVMe, but it just stood there in the report without differentiation or deeper analysis. Just the statement &#8222;bad storage.&#8220; So I unpacked my tools, took another look at it, and also developed a new solution that I want to share with you.</p>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<h2 class="wp-block-heading" id="DISKSPD" style="margin-top:var(--wp--preset--spacing--50);margin-bottom:0">DISKSPD</h2>



<p style="margin-bottom:var(--wp--preset--spacing--50)"><em><strong>Repo</strong>: perf_diskspeed/DISKSPD</em><br><strong><em>Microsoft</em>:</strong> <a href="https://github.com/microsoft/diskspd" target="_blank" rel="noopener" title="">https://github.com/microsoft/diskspd</a></p>



<p>With DISKSPD, you should always start on unknown systems; often a large portion of problems is hidden here. At this client, for example, a DB with high workload was running on spindles, with latencies in the .2 percentile of 15 seconds latency, plus 14MB throughput. I don&#8217;t need to worry about indexes etc. anymore.</p>



<p>To quickly get results, I built a wrapper for DISKSPD that always executes 3 standard tests:</p>



<ul class="wp-block-list">
<li>SQL Server Workload</li>



<li>Random 64KB Workload</li>



<li>Sequential 64KB Workload</li>
</ul>



<p>The files are stored separately in the _Output subfolder and thus provide quite good hints about storage problems. Now these were, as expected, great values that correspond to NVMe storage. So let&#8217;s continue to SQL Server and see what it tells us. welche NVMe Storage entsprechen. Also weiter zu Sql Server und wir schauen mal was uns dieser so sagt.</p>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<h2 class="wp-block-heading" id="sqlfilestats" style="margin-top:var(--wp--preset--spacing--50);margin-bottom:var(--wp--preset--spacing--50)">sys.dm_io_virtual_file_stats</h2>



<p>SQL Server provides a view where all file metrics are cleanly listed.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: sql; title: ; notranslate">
select * from sys.dm_io_virtual_file_stats(null,null)
</pre></div>


<ul class="wp-block-list">
<li><a href="https://learn.microsoft.com/en-us/sql/relational-databases/system-dynamic-management-views/sys-dm-io-virtual-file-stats-transact-sql?view=sql-server-ver17">Microsoft Learn</a></li>
</ul>



<figure class="wp-block-image size-full" style="margin-top:var(--wp--preset--spacing--50);margin-bottom:var(--wp--preset--spacing--50)"><img loading="lazy" decoding="async" width="1117" height="458" src="https://dbavonnebenan.de/wp-content/uploads/2025/07/disklat_dmv.png" alt="sys.dm_io_virtual_file_stats" class="wp-image-569" srcset="https://dbavonnebenan.de/wp-content/uploads/2025/07/disklat_dmv.png 1117w, https://dbavonnebenan.de/wp-content/uploads/2025/07/disklat_dmv-300x123.png 300w, https://dbavonnebenan.de/wp-content/uploads/2025/07/disklat_dmv-1024x420.png 1024w, https://dbavonnebenan.de/wp-content/uploads/2025/07/disklat_dmv-768x315.png 768w" sizes="auto, (max-width: 1117px) 100vw, 1117px" /></figure>



<p>Based on this, I built an analysis script that gives me more info and I can also draw further conclusions like ReadWrite Ratio, probable IOPS based on the number of operations, and also quantities. This allows you to build storage systems quite well for specific workloads.</p>



<div class="wp-block-columns is-layout-flex wp-container-core-columns-is-layout-28f84493 wp-block-columns-is-layout-flex">
<div class="wp-block-column is-vertically-aligned-center is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:80%">
<p>And this value also showed me 32ms Write Latency of the TempDB. Then I started wondering, how does this value even arise?</p>
</div>



<div class="wp-block-column is-vertically-aligned-center is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:20%">
<figure class="wp-block-image aligncenter size-full is-resized"><img loading="lazy" decoding="async" width="261" height="323" src="https://dbavonnebenan.de/wp-content/uploads/2025/07/emotes_question.png" alt="Question" class="wp-image-575" style="width:125px;height:auto" srcset="https://dbavonnebenan.de/wp-content/uploads/2025/07/emotes_question.png 261w, https://dbavonnebenan.de/wp-content/uploads/2025/07/emotes_question-242x300.png 242w" sizes="auto, (max-width: 261px) 100vw, 261px" /></figure>
</div>
</div>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading" style="margin-top:var(--wp--preset--spacing--50);margin-bottom:var(--wp--preset--spacing--50)">Basic Calculation</h3>



<p>SQL Server measures I/O latency through simple division of cumulative wait times by the number of operations:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: sql; title: ; notranslate">
-- Read Latency in ms
io_stall_read_ms / num_of_reads = durchschnittliche Read-Latenz

-- Write Latency in ms  
io_stall_write_ms / num_of_writes = durchschnittliche Write-Latenz
</pre></div>


<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<p>That&#8217;s it!!! And now it should be clear that the point-in-time use of this DMV is not really meaningful, on the contrary. The values can, as with the review company that specializes in such processes, lead to false conclusions and action recommendations. Scary.</p>



<p>If the storage system had a hard hiccup since the start of SQL Server, the metrics are contaminated. According to my research, you can&#8217;t flush these, only by taking the DB offline, which I usually avoid in productive environments.</p>



<p>After a few more rounds in my head, the solution came quite quickly. These are all snapshot values, I just need the delta between two values and I have real values. And I can also put the whole thing in a time series and have a long-term analysis. Mindblowing <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f61d.png" alt="😝" class="wp-smiley" style="height: 1em; max-height: 1em;" /></p>



<p>Google also quickly showed corresponding solutions that I used as a base idea. Thanks to Paul Randal ( <a href="https://www.sqlskills.com/blogs/paul/capturing-io-latencies-period-time/" target="_blank" rel="noopener" title="">sqlskills.com</a> )</p>



<p>For analyzing a complete week, this is also not suitable. So I extended the whole thing to a long-term analysis with persistent table.</p>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<h2 class="wp-block-heading" id="snapdisk" style="margin-top:var(--wp--preset--spacing--50);margin-bottom:0">The Solution &#8211;&gt; snapDiskLatency</h2>



<p><em><strong>Repo</strong>: </em>perf_diskspeed/snapDiskLatency</p>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading" style="margin-top:var(--wp--preset--spacing--50);margin-bottom:var(--wp--preset--spacing--50)">What does snapDiskLatency do?</h3>



<p>The problem is well-known &#8211; the standard DMV <code>sys.dm_io_virtual_file_stats</code> shows cumulative values since SQL Server startup. One single storage hiccup weeks ago and the latency values are no longer representative.</p>



<p>This solves the problem with two simple stored procedures:</p>



<p>The first collects the raw data from the DMV hourly and writes it to a permanent table. Why hourly? Shorter intervals lead to unreliable delta calculations &#8211; too little I/O between snapshots makes the values inaccurate. However, this can be individually adjusted since the agent triggers the procedure.</p>



<figure class="wp-block-image size-full" style="margin-top:var(--wp--preset--spacing--50);margin-bottom:var(--wp--preset--spacing--50)"><img loading="lazy" decoding="async" width="1360" height="382" src="https://dbavonnebenan.de/wp-content/uploads/2025/07/disklat_raw.png" alt="snapDiskLatency collect" class="wp-image-567" srcset="https://dbavonnebenan.de/wp-content/uploads/2025/07/disklat_raw.png 1360w, https://dbavonnebenan.de/wp-content/uploads/2025/07/disklat_raw-300x84.png 300w, https://dbavonnebenan.de/wp-content/uploads/2025/07/disklat_raw-1024x288.png 1024w, https://dbavonnebenan.de/wp-content/uploads/2025/07/disklat_raw-768x216.png 768w" sizes="auto, (max-width: 1360px) 100vw, 1360px" /></figure>



<p>The second does the delta calculations between snapshots and creates the result. Thus, real latency values are generated for defined time periods, without historical hiccups, which can then be compared with other metrics.</p>



<figure class="wp-block-image size-full" style="margin-top:var(--wp--preset--spacing--50);margin-bottom:var(--wp--preset--spacing--50)"><img loading="lazy" decoding="async" width="1298" height="326" src="https://dbavonnebenan.de/wp-content/uploads/2025/07/disklat_report-2.png" alt="snapDiskLatency report" class="wp-image-571" srcset="https://dbavonnebenan.de/wp-content/uploads/2025/07/disklat_report-2.png 1298w, https://dbavonnebenan.de/wp-content/uploads/2025/07/disklat_report-2-300x75.png 300w, https://dbavonnebenan.de/wp-content/uploads/2025/07/disklat_report-2-1024x257.png 1024w, https://dbavonnebenan.de/wp-content/uploads/2025/07/disklat_report-2-768x193.png 768w" sizes="auto, (max-width: 1298px) 100vw, 1298px" /></figure>



<p>Here we see, for example, in the <a href="https://dbavonnebenan.de/sql-server-truncate-vs-delete-dml-or-ddl-en/" title="TRUNCATE TABLE – DDL vs DML – deep dive in operations and practical tips">TRUNCATE TABLE</a> an outlier (NVMe is also underlying here). On the LAB system there&#8217;s no load, DOXIS is just a table with 2 columns and was artificially worked with workload.<br></p>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading" style="margin-top:var(--wp--preset--spacing--50);margin-bottom:var(--wp--preset--spacing--50)"><strong>The Result</strong></h3>



<p>Instead of possibly contaminated values:</p>



<ul class="wp-block-list">
<li>Average latencies per interval</li>



<li>Read/Write ratios</li>



<li>Breakdown by database and file type</li>
</ul>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading" style="margin-top:var(--wp--preset--spacing--50);margin-bottom:var(--wp--preset--spacing--50)"><strong>Setup</strong></h3>



<p>Create Agent Job, run hourly, done. The complete instructions are in the README, copy &amp; paste &amp; enjoy</p>



<figure class="wp-block-image aligncenter size-full is-resized"><img loading="lazy" decoding="async" width="246" height="470" src="https://dbavonnebenan.de/wp-content/uploads/2025/05/emotes_standing_1.png" alt="happy" class="wp-image-330" style="width:125px;height:auto" srcset="https://dbavonnebenan.de/wp-content/uploads/2025/05/emotes_standing_1.png 246w, https://dbavonnebenan.de/wp-content/uploads/2025/05/emotes_standing_1-157x300.png 157w" sizes="auto, (max-width: 246px) 100vw, 246px" /></figure>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<h2 class="wp-block-heading" style="margin-top:var(--wp--preset--spacing--50);margin-bottom:var(--wp--preset--spacing--50)">Diagrams</h2>



<p>Since every good proprietary database monitoring tool does this and presents it graphically, I want to at least show one possibility in Excel in the result. For this, the result of the DataSource or Copy must be pivoted and you can easily recognize your daily peaks.</p>



<figure class="wp-block-image size-full" style="margin-top:var(--wp--preset--spacing--50);margin-bottom:var(--wp--preset--spacing--50)"><img loading="lazy" decoding="async" width="1617" height="604" src="https://dbavonnebenan.de/wp-content/uploads/2025/07/disklat_graph.png" alt="" class="wp-image-572" srcset="https://dbavonnebenan.de/wp-content/uploads/2025/07/disklat_graph.png 1617w, https://dbavonnebenan.de/wp-content/uploads/2025/07/disklat_graph-300x112.png 300w, https://dbavonnebenan.de/wp-content/uploads/2025/07/disklat_graph-1024x382.png 1024w, https://dbavonnebenan.de/wp-content/uploads/2025/07/disklat_graph-768x287.png 768w, https://dbavonnebenan.de/wp-content/uploads/2025/07/disklat_graph-1536x574.png 1536w" sizes="auto, (max-width: 1617px) 100vw, 1617px" /></figure><p>The post <a href="https://dbavonnebenan.de/sql-server-disk-latency-en/">Measuring SQL Server Disk Latency Correctly – snapDiskLatency</a> first appeared on <a href="https://dbavonnebenan.de">DBA von Nebenan</a>.</p>]]></content:encoded>
					
					<wfw:commentRss>https://dbavonnebenan.de/sql-server-disk-latency-en/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>SQL Server Disk Latency richtig messen &#8211; snapDiskLatency</title>
		<link>https://dbavonnebenan.de/sql-server-disk-latency-de/</link>
					<comments>https://dbavonnebenan.de/sql-server-disk-latency-de/#respond</comments>
		
		<dc:creator><![CDATA[Gabriel]]></dc:creator>
		<pubDate>Thu, 17 Jul 2025 14:06:35 +0000</pubDate>
				<category><![CDATA[Sql Server]]></category>
		<category><![CDATA[TSql]]></category>
		<category><![CDATA[DE]]></category>
		<category><![CDATA[diskio]]></category>
		<category><![CDATA[DISKSPD]]></category>
		<category><![CDATA[latency]]></category>
		<category><![CDATA[PERFORMANCE]]></category>
		<guid isPermaLink="false">https://dbavonnebenan.de/?p=563</guid>

					<description><![CDATA[<p>Wie du richtige SQL Server Disk Latency Werte erhältst statt verfälschter DMV-Daten. Open Source snapDiskLatency Lösung für Langzeitanalyse.</p>
<p>The post <a href="https://dbavonnebenan.de/sql-server-disk-latency-de/">SQL Server Disk Latency richtig messen – snapDiskLatency</a> first appeared on <a href="https://dbavonnebenan.de">DBA von Nebenan</a>.</p>]]></description>
										<content:encoded><![CDATA[<div style="border: 1px dotted #eb2b14; border-radius: 0%; background-color: #eeeeee; padding-top: var(--wp--preset--spacing--30); padding-left: var(--wp--preset--spacing--30); padding-right: var(--wp--preset--spacing--30); padding-bottom: var(--wp--preset--spacing--30); " class="ub-styled-box ub-bordered-box wp-block-ub-styled-box" id="ub-styled-box-ba0a6049-a2c1-44fc-b531-47f63f78c8b4">
<p style="margin-top:0;margin-right:0;margin-bottom:0;margin-left:0" id="ub-styled-box-bordered-content-">Was erwartet dich hier?</p>



<ul style="margin-top:0;margin-right:0;margin-bottom:0;margin-left:0" class="wp-block-list">
<li><a href="#DISKSPD" title="DISKSPD Templates und Wrapper">DISKSPD Templates und Wrapper</a></li>



<li><a href="#sqlfilestats" title="">Wie kann ich die Latenzen der einzelnen Datenbank Files anzeigen lassen?</a></li>



<li><a href="#snapdisk" title="Langzeit Analyse der Latenzen!!!">Langzeit Analyse der Latenzen!!!</a></li>
</ul>


</div>


<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>


https://github.com/gabrielkoehl/DBAScriptBox


<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<h2 class="wp-block-heading" style="margin-top:var(--wp--preset--spacing--50);margin-bottom:var(--wp--preset--spacing--50)">Intro</h2>



<p>Aktuell bin ich bei einem Kunden an einem Code Review beteiligt. Dabei beleuchten wir neben der Applikation auch alle Schnittstellen.. Dazu gehört natürlich auch die Sql Server Landschaft, welche ich Anfang des Jahres aufgesetzt habe.</p>



<div class="wp-block-columns is-layout-flex wp-container-core-columns-is-layout-28f84493 wp-block-columns-is-layout-flex">
<div class="wp-block-column is-vertically-aligned-center is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:85%">
<p>Das Ergebnis waren doch mehr Findings in meine Richtung als erwartet und ich fühlte mich sofort gechallenged. Das Meiste konnte ich widerlegen, bei einigen Themen war der Kunde in der Pflicht und ein Thema hat mich rein interessenhalber gepackt.</p>



<p class="has-text-align-center" style="margin-top:var(--wp--preset--spacing--50);margin-bottom:var(--wp--preset--spacing--50)"><strong>Read- und Write Latenzen der TempDB</strong> ( und jeder anderen DB )</p>
</div>



<div class="wp-block-column is-vertically-aligned-center is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:15%">
<figure class="wp-block-image aligncenter size-full is-resized"><img loading="lazy" decoding="async" width="264" height="329" src="https://dbavonnebenan.de/wp-content/uploads/2025/06/emote-4.png" alt="" class="wp-image-516" style="width:131px;height:auto" srcset="https://dbavonnebenan.de/wp-content/uploads/2025/06/emote-4.png 264w, https://dbavonnebenan.de/wp-content/uploads/2025/06/emote-4-241x300.png 241w" sizes="auto, (max-width: 264px) 100vw, 264px" /></figure>
</div>
</div>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<h2 class="wp-block-heading" style="margin-top:var(--wp--preset--spacing--50);margin-bottom:var(--wp--preset--spacing--50)">Vorgeschichte</h2>



<p>Der Kunde betreibt kein eigenes Rechenzentrum, ist somit vom Hoster abhängig, welcher leider nicht sehr kommunikativ ist. Somit musste ich mir auf diverse andere Wege ein Bild von der total overcommitteten Hardware machen und den Hoster Challengen. Dies war auch der Grund, warum ich ziemlich genau im Bilde war, was die Hosts und Storage Systeme des Hosters im Stande waren zu leisten.</p>



<p>Deshalb hat es mich doch sehr gewundert, dass als Finding 32ms Write Latency aufgeführt waren. Keine Frage, das ist ein schlechter Wert für AllFlash NVMe aber der stand da einfach so im Report, ohne Differenzierung oder tiefere Analysen. Nur die Aussage schlechter Storage. Also habe ich meine Werkzeuge ausgepackt, mir das noch mal angeschaut und auch eine neue Lösung entwickelt, die ich mit euch teilen möchte.</p>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<h2 class="wp-block-heading" id="DISKSPD" style="margin-top:var(--wp--preset--spacing--50);margin-bottom:0">DISKSPD</h2>



<p style="margin-bottom:var(--wp--preset--spacing--50)"><em><strong>Repo</strong>: perf_diskspeed/DISKSPD</em><br><strong><em>Microsoft</em>:</strong> <a href="https://github.com/microsoft/diskspd">https://github.com/microsoft/diskspd</a></p>



<p>Mit DISKSPD sollte man auf unbekannten Systemen immer anfangen, oft liegt hier schon ein großer Anteil an Problemen versteckt. Bei diesem Kudnen lief zum Beispiel eine DB mit hohem Workload auf Spindeln, mit Latenzen in der .2 Perzentile mit 15 Sekunden Latenz, dazu 14MB Durchsatz. Da brauche ich mir um Indexe usw. keine Gedanken mehr machen.</p>



<p>Um schnell an Ergebnisse zu kommen, habe ich mir einen Wrapper für DISKSP gebaut, welcher immer 3 Standardtests ausführt</p>



<ul class="wp-block-list">
<li>Sql Server Workload</li>



<li>Random 64KB Workload</li>



<li>Sequenziell 64KB Workload</li>
</ul>



<p>Die Dateien werden separat im Unterordner _Output gespeichert und ermöglichen somit recht gute Hinweise auf Storage-Probleme. Nun waren das wie erwartet super Werte, welche NVMe Storage entsprechen. Also weiter zu Sql Server und wir schauen mal was uns dieser so sagt.</p>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<h2 class="wp-block-heading" id="sqlfilestats" style="margin-top:var(--wp--preset--spacing--50);margin-bottom:var(--wp--preset--spacing--50)">sys.dm_io_virtual_file_stats</h2>



<p>Sql Server bietet eine View, in der alle Metriken der Files sauber aufgelistet werden.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: sql; title: ; notranslate">
select * from sys.dm_io_virtual_file_stats(null,null)
</pre></div>


<ul class="wp-block-list">
<li><a href="https://learn.microsoft.com/en-us/sql/relational-databases/system-dynamic-management-views/sys-dm-io-virtual-file-stats-transact-sql?view=sql-server-ver17" target="_blank" rel="noopener" title="">microsoft.com</a></li>
</ul>



<figure class="wp-block-image size-full" style="margin-top:var(--wp--preset--spacing--50);margin-bottom:var(--wp--preset--spacing--50)"><img loading="lazy" decoding="async" width="1117" height="458" src="https://dbavonnebenan.de/wp-content/uploads/2025/07/disklat_dmv.png" alt="sys.dm_io_virtual_file_stats" class="wp-image-569" srcset="https://dbavonnebenan.de/wp-content/uploads/2025/07/disklat_dmv.png 1117w, https://dbavonnebenan.de/wp-content/uploads/2025/07/disklat_dmv-300x123.png 300w, https://dbavonnebenan.de/wp-content/uploads/2025/07/disklat_dmv-1024x420.png 1024w, https://dbavonnebenan.de/wp-content/uploads/2025/07/disklat_dmv-768x315.png 768w" sizes="auto, (max-width: 1117px) 100vw, 1117px" /></figure>



<p>Darauf basierend habe ich ein Analyse Script gebaut, welches mir mehr Infos gibt und ich auch weitere Rückschlüsse wie ReadWrite Ratio, wahrscheinliche IOPS auf Basis der Anzahl der Vorgänge und auch Mengen. Damit kann man schon ganz gut Storage Systeme zu speziellen Workloads aufbauen.</p>



<div class="wp-block-columns is-layout-flex wp-container-core-columns-is-layout-28f84493 wp-block-columns-is-layout-flex">
<div class="wp-block-column is-vertically-aligned-center is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:80%">
<p>Und auch dieser Wert hat mir 32ms Write Latency der TempDB angezeigt. Dann habe ich angefangen mich zu fragen, wie entstehet dieser Wert überhaupt? </p>
</div>



<div class="wp-block-column is-vertically-aligned-center is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:20%">
<figure class="wp-block-image aligncenter size-full is-resized"><img loading="lazy" decoding="async" width="261" height="323" src="https://dbavonnebenan.de/wp-content/uploads/2025/07/emotes_question.png" alt="Question" class="wp-image-575" style="width:125px;height:auto" srcset="https://dbavonnebenan.de/wp-content/uploads/2025/07/emotes_question.png 261w, https://dbavonnebenan.de/wp-content/uploads/2025/07/emotes_question-242x300.png 242w" sizes="auto, (max-width: 261px) 100vw, 261px" /></figure>
</div>
</div>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading" style="margin-top:var(--wp--preset--spacing--50);margin-bottom:var(--wp--preset--spacing--50)">Grundlegende Berechnung der Werte</h3>



<p>SQL Server misst I/O-Latenz durch einfache Division der kumulativen Wartezeiten durch die Anzahl der Operationen:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: sql; title: ; notranslate">
-- Read Latency in ms
io_stall_read_ms / num_of_reads = durchschnittliche Read-Latenz

-- Write Latency in ms  
io_stall_write_ms / num_of_writes = durchschnittliche Write-Latenz
</pre></div>


<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<p>Thats it!!! Und nun sollte klar sein, dass die punktuelle Nutzung dieser DMV nicht wirklich aussagekräftig ist, im Gegenteil. Die Werte können wie bei dem Review Unternehmen, was sich auf solche Prozesse spezialisiert hat, zu falschen Rückschlüssen und Handlungsempfehlungen führen. Gruselig.</p>



<p>Wenn das Storagesystem seit dem Start von Sql Server mal hart Schluckauf hatte, sind die Metriken verunreiningt. Laut meinen Recherchen kann man diese nicht flushen, nur durch das Offline nehmen der DB, was ich üblicherweise in produktiven Umgebungen vermeide.</p>



<p>Nach ein paar weiteren Runden in meinem Kopf kam recht schnell die Lösung. Das sind alles SnapShot Values, ich brauche ja nur das Delta zwischen zwei Werten und schon habe ich reale Werte. Und das Ganze kann ich auch noch in eine Zeitreihe packen und habe eine Langzeitanalyse.  Mindblowing <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f61d.png" alt="😝" class="wp-smiley" style="height: 1em; max-height: 1em;" /></p>



<p>Google hat auch schnell entsprechende Lösungen aufgezeigt, die ich als Basis Idee genutzt habe. Danke an Paul Randal ( <a href="https://www.sqlskills.com/blogs/paul/capturing-io-latencies-period-time/">sqlskills.com</a> )</p>



<p>Für die Analyse einer kompletten Woche ist dies aber auch nicht geeignet. Somit habe ich das Ganze auf eine Langzeitanalyse mit persistenten Table erweitert.</p>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<h2 class="wp-block-heading" id="snapdisk" style="margin-top:var(--wp--preset--spacing--50);margin-bottom:0">Die Lösung &#8211;&gt; snapDiskLatency</h2>



<p><em><strong>Repo</strong>: </em>perf_diskspeed/snapDiskLatency</p>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading" style="margin-top:var(--wp--preset--spacing--50);margin-bottom:var(--wp--preset--spacing--50)">Was macht snapDiskLatency?</h3>



<p>Das Problem ist bekannt &#8211; die Standard-DMV <code>sys.dm_io_virtual_file_stats</code> zeigt kumulative Werte seit dem SQL Server Start. Ein einziger Storage-Schluckauf vor Wochen und die Latenzwerte sind nicht mehr repräsentativ. </p>



<p><strong>snapDiskLatency</strong> löst das Problem mit zwei simplen Stored Procedures:</p>



<p>Die Erste sammelt stündlich die Rohdaten aus der DMV und schreibt sie in einen permanenten Table. Warum stündlich? Kürzere Intervalle führen zu unzuverlässigen Delta-Berechnungen &#8211; zu wenig I/O zwischen den Snapshots macht die Werte ungenau. Dies kann aber individuell angepasst werden, da der Agent die Prozedur triggert.</p>



<figure class="wp-block-image size-full" style="margin-top:var(--wp--preset--spacing--50);margin-bottom:var(--wp--preset--spacing--50)"><img loading="lazy" decoding="async" width="1360" height="382" src="https://dbavonnebenan.de/wp-content/uploads/2025/07/disklat_raw.png" alt="snapDiskLatency collect" class="wp-image-567" srcset="https://dbavonnebenan.de/wp-content/uploads/2025/07/disklat_raw.png 1360w, https://dbavonnebenan.de/wp-content/uploads/2025/07/disklat_raw-300x84.png 300w, https://dbavonnebenan.de/wp-content/uploads/2025/07/disklat_raw-1024x288.png 1024w, https://dbavonnebenan.de/wp-content/uploads/2025/07/disklat_raw-768x216.png 768w" sizes="auto, (max-width: 1360px) 100vw, 1360px" /></figure>



<p>Die Zweite macht die Delta-Berechnungen zwischen den Snapshots und erstellt das Resultset. Somit werden reale Latenzwerte für definierte Zeiträume erzeugt, ohne historischen Schluckauf, welche dann mit anderen Metriken abgeglichen werden können.</p>



<figure class="wp-block-image size-full" style="margin-top:var(--wp--preset--spacing--50);margin-bottom:var(--wp--preset--spacing--50)"><img loading="lazy" decoding="async" width="1298" height="326" src="https://dbavonnebenan.de/wp-content/uploads/2025/07/disklat_report-2.png" alt="snapDiskLatency report" class="wp-image-571" srcset="https://dbavonnebenan.de/wp-content/uploads/2025/07/disklat_report-2.png 1298w, https://dbavonnebenan.de/wp-content/uploads/2025/07/disklat_report-2-300x75.png 300w, https://dbavonnebenan.de/wp-content/uploads/2025/07/disklat_report-2-1024x257.png 1024w, https://dbavonnebenan.de/wp-content/uploads/2025/07/disklat_report-2-768x193.png 768w" sizes="auto, (max-width: 1298px) 100vw, 1298px" /></figure>



<p>Hier sehen wir zum Beispiel im <a href="https://dbavonnebenan.de/sql-server-truncate-vs-delete-dml-or-ddl-de/" title="TRUNCATE TABLE Operationen im Detail – ROLLBACK ist doch möglich">TRUNCATE Table</a> einen Aureißer ( hier liegt auch NVMe drunter ). Auf dem LAB System ist keine Last, DOXIS ist nur ein Table mit 2 Spalten und wurde künstlich mit Workload bearbeitet.</p>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading" style="margin-top:var(--wp--preset--spacing--50);margin-bottom:var(--wp--preset--spacing--50)"><strong>Das Ergebnis</strong></h3>



<p>Statt möglicherweise verunreinigten Werten</p>



<ul class="wp-block-list">
<li>Durchschnittliche Latenzen pro Intervall</li>



<li>Read/Write-Verhältnisse</li>



<li>Aufschlüsselung nach Datenbank und File-Type</li>
</ul>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading" style="margin-top:var(--wp--preset--spacing--50);margin-bottom:var(--wp--preset--spacing--50)"><strong>Setup</strong></h3>



<p>Agent Job erstellen, stündlich laufen lassen, fertig. Die komplette Anleitung steht in der README, copy &amp; paste &amp; freuen</p>



<figure class="wp-block-image aligncenter size-full is-resized"><img loading="lazy" decoding="async" width="246" height="470" src="https://dbavonnebenan.de/wp-content/uploads/2025/05/emotes_standing_1.png" alt="happy" class="wp-image-330" style="width:125px;height:auto" srcset="https://dbavonnebenan.de/wp-content/uploads/2025/05/emotes_standing_1.png 246w, https://dbavonnebenan.de/wp-content/uploads/2025/05/emotes_standing_1-157x300.png 157w" sizes="auto, (max-width: 246px) 100vw, 246px" /></figure>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<h2 class="wp-block-heading" style="margin-top:var(--wp--preset--spacing--50);margin-bottom:var(--wp--preset--spacing--50)">Diagramme</h2>



<p>Da dies auch jedes gute propritäre DatabaseMonitoringTool macht und hübsch grafisch darstellt, will ich zumindest eine Möglichkeit in Excel im Ergebnis zeigen. Dazu muss das Ergebnis der DataSource oder Copy pivotiert werden und schon kann man gut seine Daily Peaks erkennen.</p>



<figure class="wp-block-image size-full" style="margin-top:var(--wp--preset--spacing--50);margin-bottom:var(--wp--preset--spacing--50)"><img loading="lazy" decoding="async" width="1617" height="604" src="https://dbavonnebenan.de/wp-content/uploads/2025/07/disklat_graph.png" alt="" class="wp-image-572" srcset="https://dbavonnebenan.de/wp-content/uploads/2025/07/disklat_graph.png 1617w, https://dbavonnebenan.de/wp-content/uploads/2025/07/disklat_graph-300x112.png 300w, https://dbavonnebenan.de/wp-content/uploads/2025/07/disklat_graph-1024x382.png 1024w, https://dbavonnebenan.de/wp-content/uploads/2025/07/disklat_graph-768x287.png 768w, https://dbavonnebenan.de/wp-content/uploads/2025/07/disklat_graph-1536x574.png 1536w" sizes="auto, (max-width: 1617px) 100vw, 1617px" /></figure><p>The post <a href="https://dbavonnebenan.de/sql-server-disk-latency-de/">SQL Server Disk Latency richtig messen – snapDiskLatency</a> first appeared on <a href="https://dbavonnebenan.de">DBA von Nebenan</a>.</p>]]></content:encoded>
					
					<wfw:commentRss>https://dbavonnebenan.de/sql-server-disk-latency-de/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>TRUNCATE TABLE &#8211; DDL vs DML &#8211; deep dive in operations and practical tips</title>
		<link>https://dbavonnebenan.de/sql-server-truncate-vs-delete-dml-or-ddl-en/</link>
					<comments>https://dbavonnebenan.de/sql-server-truncate-vs-delete-dml-or-ddl-en/#respond</comments>
		
		<dc:creator><![CDATA[Gabriel]]></dc:creator>
		<pubDate>Mon, 26 May 2025 13:14:32 +0000</pubDate>
				<category><![CDATA[Sql Server]]></category>
		<category><![CDATA[TSql]]></category>
		<category><![CDATA[eng]]></category>
		<category><![CDATA[PERFORMANCE]]></category>
		<category><![CDATA[ROLLBACK]]></category>
		<category><![CDATA[TABLE]]></category>
		<category><![CDATA[TRANSACTION]]></category>
		<category><![CDATA[TRUNCATE]]></category>
		<guid isPermaLink="false">https://dbavonnebenan.de/?p=323</guid>

					<description><![CDATA[<p>Recently, I stumbled across a LinkedIn post about truncate vs. delete, just like many others out there. Typically, random members post simple SQL Server tips, nicely packaged in graphics and kept general. Although for beginners this is absolutely legitimate &#8211; since these simple entry points can help many newcomers understand and provide a great starting [&#8230;]</p>
<p>The post <a href="https://dbavonnebenan.de/sql-server-truncate-vs-delete-dml-or-ddl-en/">TRUNCATE TABLE – DDL vs DML – deep dive in operations and practical tips</a> first appeared on <a href="https://dbavonnebenan.de">DBA von Nebenan</a>.</p>]]></description>
										<content:encoded><![CDATA[<p>Recently, I stumbled across a LinkedIn post about truncate vs. delete, just like many others out there. Typically, random members post simple SQL Server tips, nicely packaged in graphics and kept general. Although for beginners this is absolutely legitimate &#8211; since<strong> </strong>these simple entry points can help many newcomers understand and provide a great starting point &#8211; nevertheless, the fact that the same topics then go viral across various accounts is clearly another matter entirely.</p>



<p>Anyway. If you&#8217;re a bit further along in the topic and already know that the pronunciation isn&#8217;t &#8222;SQL&#8220; but &#8222;sequel&#8220; <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f60a.png" alt="😊" class="wp-smiley" style="height: 1em; max-height: 1em;" />, you should fundamentally question these posts, as they mostly generalize.</p>



<p>This was also the case in a post about the top 3 SQL mistakes that &#8222;everyone&#8220; makes. And here we are, getting to the point.</p>



<div style="height:32px" aria-hidden="true" class="wp-block-spacer"></div>



<figure class="wp-block-image aligncenter size-full"><a href="https://www.linkedin.com/posts/samiul-fahim_sql-dataengineering-techtips-activity-7328642722653786112-OBfj?utm_source=share&amp;utm_medium=member_desktop&amp;rcm=ACoAACt1yIMBIZu3k0gdHLx7c_luIAnHYYBMtuc"><img loading="lazy" decoding="async" width="391" height="158" src="https://dbavonnebenan.de/wp-content/uploads/2025/05/2025-05-26_09-15.png" alt="TRUNCATE cant rolled back" class="wp-image-324" srcset="https://dbavonnebenan.de/wp-content/uploads/2025/05/2025-05-26_09-15.png 391w, https://dbavonnebenan.de/wp-content/uploads/2025/05/2025-05-26_09-15-300x121.png 300w" sizes="auto, (max-width: 391px) 100vw, 391px" /></a></figure>



<p class="has-text-align-center"><a href="https://www.linkedin.com/posts/samiul-fahim_sql-dataengineering-techtips-activity-7328642722653786112-OBfj?utm_source=share&amp;utm_medium=member_desktop&amp;rcm=ACoAACt1yIMBIZu3k0gdHLx7c_luIAnHYYBMtuc" target="_blank" rel="noreferrer noopener">LinkedIn</a></p>



<div style="height:32px" aria-hidden="true" class="wp-block-spacer"></div>



<p>The same happened with <a href="https://www.linkedin.com/in/vedrankesegic/">Vedran Kesegic</a>, who gave the hint that TRUNCATE is apparently rollback-capable when encapsulated in a transaction. Then <a href="https://www.linkedin.com/in/saastamoinen/">Carsten Saastamoinen-Jakobsen</a> jumped in and things got wild. So wild that the whole topic hooked me so much that I did a deep dive into it. Furthermore, so much knowledge dropped in this exchange that it would be a shame if it disappeared in a comment section.</p>



<div style="height:32px" aria-hidden="true" class="wp-block-spacer"></div>



<h2 class="wp-block-heading">What to expect here</h2>



<p>I compared the exchange and looked at the core statements. I also texted separately with both of them a bit more, and Carsten gave me excerpts from one of his publications where the topic is covered technically.</p>



<p>Technically speaking, both agree, even if they don&#8217;t say it <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f923.png" alt="🤣" class="wp-smiley" style="height: 1em; max-height: 1em;" />. But what was really wild in the end was the question:</p>



<div style="height:32px" aria-hidden="true" class="wp-block-spacer"></div>



<figure class="wp-block-image aligncenter size-full"><img loading="lazy" decoding="async" width="595" height="159" src="https://dbavonnebenan.de/wp-content/uploads/2025/05/ddl_dml.png" alt="DML or DDL ??" class="wp-image-326" srcset="https://dbavonnebenan.de/wp-content/uploads/2025/05/ddl_dml.png 595w, https://dbavonnebenan.de/wp-content/uploads/2025/05/ddl_dml-300x80.png 300w" sizes="auto, (max-width: 595px) 100vw, 595px" /></figure>



<div style="height:32px" aria-hidden="true" class="wp-block-spacer"></div>



<div class="wp-block-columns is-layout-flex wp-container-core-columns-is-layout-28f84493 wp-block-columns-is-layout-flex">
<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:100%">
<div class="wp-block-group is-nowrap is-layout-flex wp-container-core-group-is-layout-6c531013 wp-block-group-is-layout-flex">
<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="246" height="470" src="https://dbavonnebenan.de/wp-content/uploads/2025/05/emotes_standing_1.png" alt="" class="wp-image-330" srcset="https://dbavonnebenan.de/wp-content/uploads/2025/05/emotes_standing_1.png 246w, https://dbavonnebenan.de/wp-content/uploads/2025/05/emotes_standing_1-157x300.png 157w" sizes="auto, (max-width: 246px) 100vw, 246px" /></figure>



<p>I can say it upfront. This question is an philosophical one. I was able to form an opinion based on my research, more on that later, but basically it&#8217;s up to everyone to decide for themselves.</p>
</div>
</div>
</div>



<div style="height:32px" aria-hidden="true" class="wp-block-spacer"></div>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<div style="height:32px" aria-hidden="true" class="wp-block-spacer"></div>



<h2 class="wp-block-heading">The Discussion &#8211; DML or DDL ?</h2>



<h3 class="wp-block-heading">Vedran Kesegic</h3>



<p><strong>Core Position: TRUNCATE is DDL</strong></p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>&#8222;TRUNCATE modifies internal system tables sys.sysallocunits and sys.sysrowsets. DELETE modifies indexes and stats&#8220;</p>
</blockquote>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>&#8222;DML writes data (rows in user tables), while DDL writes metadata (rows in system tables, data that describes other/user objects). Truncate happens to write metadata (system tables) and thus is classified as DDL.&#8220;</p>
</blockquote>



<p><strong>Technical Arguments</strong></p>



<ul class="wp-block-list">
<li>Uses SCH-M (Schema Modification) lock</li>



<li>Modifies system metadata tables</li>



<li>Microsoft documentation classifies it as DDL</li>



<li>Logs page deallocations, not row deletions</li>
</ul>



<p><strong>Key Statement</strong></p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>&#8222;TRUNCATE modifies internal system tables sys.sysallocunits and sys.sysrowsets. DELETE modifies indexes and stats &#8211; see attached&#8220;</p>
</blockquote>



<div style="height:50px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading">Carsten Saastamoinen-Jakobsen</h3>



<p><strong>Core Position: TRUNCATE is DML</strong></p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>&#8222;It is not an argument to call TRUNCATE a DDL statement because a certain lock is used! When statements are categorized, it must be based on what happens and not how a given database system solves the task.&#8220;</p>
</blockquote>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>&#8222;We may have to recognize that a previous error categorization should not continue to call TRUNCATE a DDL, but recognize that it is a DML.&#8220;</p>
</blockquote>



<p><strong>Logical Arguments</strong></p>



<ul class="wp-block-list">
<li>Data is deleted, structure is preserved</li>



<li>No table definition changes occur</li>



<li>Follows semantic rules of data manipulation</li>



<li>Can delete only some partitions (not all data)</li>
</ul>



<p><strong>Key Statement</strong></p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>&#8222;I say that MS is right in the documentations because they are not telling that TRUNCATE is as DDL, because they know that TRUNCATE is a DML!&#8220;</p>



<p><mark style="background-color:rgba(0, 0, 0, 0)" class="has-inline-color has-denary-color"><em>NOTE Gabriel <br></em></mark><em>I think this is really a bit questionable because the documentation is absolutely clear</em> <em>on this point</em> &#8211;&gt; <strong><em>DDL </em></strong>(<a href="https://learn.microsoft.com/en-us/sql/t-sql/statements/statements?view=sql-server-ver17#data-definition-language" target="_blank" rel="noreferrer noopener">microsoft.com</a>)<br></p>
</blockquote>



<p><strong>Quote from publication</strong></p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>&#8222;As of version 2016, it is possible to delete only some of the data in a table with TRUNCATE, so it is even more cor­rect to call it manipulation than definition, because it is changing some of the data but not changing the structure.&#8220;</p>
</blockquote>



<div style="height:50px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading">Points of Agreement</h3>



<ul class="wp-block-list">
<li>TRUNCATE can be rolled back in a transaction
<ul class="wp-block-list">
<li>general demo at <a href="https://blog.sqlauthority.com/2010/03/04/sql-server-rollback-truncate-command-in-transaction/" target="_blank" rel="noreferrer noopener">sqlauthority.com</a></li>



<li>deep dive demo <a href="https://www.sqlshack.com/the-internals-of-sql-truncate-and-sql-delete-statements/" target="_blank" rel="noreferrer noopener">sqlshack.com</a></li>
</ul>
</li>



<li>TRUNCATE is a logged operation</li>



<li>TRUNCATE is faster than DELETE for large datasets</li>



<li>Both understand the technical implementation</li>
</ul>



<div style="height:50px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading">Core Disagreement</h3>



<ul class="wp-block-list">
<li><strong>Classification criteria</strong>
<ul class="wp-block-list">
<li>Implementation details</li>
</ul>
</li>



<li><strong>Authority</strong>
<ul class="wp-block-list">
<li>Vendor documentation (Vedran) vs. Logical reasoning (Carsten)</li>
</ul>
</li>



<li><strong>Perspective</strong>
<ul class="wp-block-list">
<li>How it works internally (Vedran) vs. What it does functionally (Carsten)</li>
</ul>
</li>
</ul>



<div style="height:50px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading">My 50 Cent</h3>



<p>So as you can see, technically it fits, but how do you view the whole thing? Maybe it&#8217;s good that I&#8217;m not preloaded with as much knowledge as both of them because from my perspective it&#8217;s quite clear: <strong>DDL!!</strong></p>



<div class="wp-block-group is-nowrap is-layout-flex wp-container-core-group-is-layout-6c531013 wp-block-group-is-layout-flex">
<figure class="wp-block-image size-full is-resized"><img loading="lazy" decoding="async" width="292" height="342" src="https://dbavonnebenan.de/wp-content/uploads/2025/05/emotes_1.png" alt="" class="wp-image-344" style="width:98px;height:auto" srcset="https://dbavonnebenan.de/wp-content/uploads/2025/05/emotes_1.png 292w, https://dbavonnebenan.de/wp-content/uploads/2025/05/emotes_1-256x300.png 256w" sizes="auto, (max-width: 292px) 100vw, 292px" /></figure>



<p>Contrary to Carsten&#8217;s statement that no system tables are changed, Vedran&#8217;s statement is absolutely correct. See why&#8230; </p>
</div>



<p>&#8230; and of course because MS says it&#8217;s DDL <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f60a.png" alt="😊" class="wp-smiley" style="height: 1em; max-height: 1em;" /> ( <a href="https://learn.microsoft.com/en-us/sql/t-sql/statements/statements?view=sql-server-ver17#data-definition-language" target="_blank" rel="noreferrer noopener">microsoft.com</a> )</p>



<p>And partitioning is DML… I don&#8217;t know, here too it&#8217;s at least system columns that define the partitioning.</p>



<p></p>



<div style="height:32px" aria-hidden="true" class="wp-block-spacer"></div>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<div style="height:32px" aria-hidden="true" class="wp-block-spacer"></div>



<h2 class="wp-block-heading">How TRUNCATE works</h2>



<p>I&#8217;d like to say I&#8217;ll describe it simply, but that&#8217;s just my normal description. The topic is really complex <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/270c.png" alt="✌" class="wp-smiley" style="height: 1em; max-height: 1em;" /></p>



<div style="height:32px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading">1 &#8211; TRUNCATE Command Initiated</h3>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: sql; title: ; notranslate">
TRUNCATE TABLE dbo.MyTable;
</pre></div>


<ul class="wp-block-list">
<li><strong>First action</strong>: Acquire <strong>SCH-M (Schema Modification)</strong> lock on entire table</li>



<li><strong>Effect</strong>: Table is completely locked &#8211; no reads, no writes, nothing</li>
</ul>



<div style="height:32px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading">2 &#8211; Identify Extents</h3>



<ul class="wp-block-list">
<li>DEEP DIVE Extents &#8211;&gt; <a href="https://learn.microsoft.com/en-us/sql/relational-databases/pages-and-extents-architecture-guide?view=sql-server-ver17" target="_blank" rel="noreferrer noopener">microsoft.com</a></li>



<li>SQL Server reads IAM pages</li>



<li>Lists all extents belonging to the table</li>



<li><strong>Still holding</strong>: SCH-M lock on table</li>
</ul>



<div style="height:32px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading">3-  Lock Individual Extents</h3>



<ul class="wp-block-list">
<li>Places <strong>X (Exclusive)</strong> locks on each extent</li>



<li>These are regular data locks, NOT schema locks</li>



<li><strong>Now holding</strong>:
<ul class="wp-block-list">
<li>SCH-M lock on table (prevents access)</li>



<li>X locks on extents (marks them for deallocation)</li>
</ul>
</li>
</ul>



<div style="height:32px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading">4 &#8211; Log the Operation</h3>



<ul class="wp-block-list">
<li>Writes to transaction log: &#8222;Deallocate extents 8:8, 8:16, etc.&#8220;</li>



<li><strong>Still holding</strong>: Both lock types</li>
</ul>



<div style="height:32px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading">5a &#8211; If COMMIT</h3>



<ul class="wp-block-list">
<li>Updates IAM &#8211; marks extents as free</li>



<li><strong>Releases</strong> all X locks on extents</li>



<li><strong>Releases</strong> SCH-M lock on table</li>



<li>Table is accessible again</li>
</ul>



<div style="height:32px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading">5b &#8211; If ROLLBACK</h3>



<ul class="wp-block-list">
<li>Discards deallocation markers</li>



<li><strong>Releases</strong> all X locks on extents</li>



<li><strong>Releases</strong> SCH-M lock on table</li>



<li>Everything unchanged</li>
</ul>



<div style="height:32px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading">Why Two Different Lock Types?</h3>



<p><strong>SCH-M Lock:</strong></p>



<ul class="wp-block-list">
<li>&#8222;Nobody touch this table AT ALL&#8220;</li>



<li>Typical for DDL operations</li>



<li>Prevents even metadata queries</li>
</ul>



<p><strong>X Locks on Extents:</strong></p>



<ul class="wp-block-list">
<li>&#8222;These specific data blocks are being modified&#8220;</li>



<li>Typical for data operations</li>



<li>Ensures extents aren&#8217;t reused until commit/rollback by other sessions</li>
</ul>



<div style="height:32px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading">My humble Opinion</h3>



<p>Given this knowledge, the case is actually clear for me, but how do you see it? Indeed, here no user data is being manipulated; instead, system-level assignments are being adjusted. Consequently, I&#8217;ll allow myself, despite my new half-knowledge, to make the following comparison:</p>



<div class="wp-block-group is-nowrap is-layout-flex wp-container-core-group-is-layout-6c531013 wp-block-group-is-layout-flex">
<figure class="wp-block-image size-full is-resized"><img loading="lazy" decoding="async" width="307" height="328" src="https://dbavonnebenan.de/wp-content/uploads/2025/05/emotes_2.png" alt="" class="wp-image-345" style="width:131px;height:auto" srcset="https://dbavonnebenan.de/wp-content/uploads/2025/05/emotes_2.png 307w, https://dbavonnebenan.de/wp-content/uploads/2025/05/emotes_2-281x300.png 281w" sizes="auto, (max-width: 307px) 100vw, 307px" /></figure>



<p>&#8222;I also don&#8217;t say I&#8217;m deleting text in a Word document when I quick format the disk and only delete the block allocation.&#8220;</p>
</div>



<p><br>Yes, I think that&#8217;s a nice comparison&#8230; Case closed, now a few technically interesting aspects and solutions</p>



<div style="height:32px" aria-hidden="true" class="wp-block-spacer"></div>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<div style="height:32px" aria-hidden="true" class="wp-block-spacer"></div>



<h2 class="wp-block-heading">Technical Part, Hacks and How2</h2>



<div style="height:32px" aria-hidden="true" class="wp-block-spacer"></div>



<figure class="wp-block-image aligncenter size-full is-resized"><img loading="lazy" decoding="async" width="643" height="388" src="https://dbavonnebenan.de/wp-content/uploads/2025/05/meme_trunc.jpg" alt="" class="wp-image-348" style="width:325px;height:auto" srcset="https://dbavonnebenan.de/wp-content/uploads/2025/05/meme_trunc.jpg 643w, https://dbavonnebenan.de/wp-content/uploads/2025/05/meme_trunc-300x181.jpg 300w" sizes="auto, (max-width: 643px) 100vw, 643px" /></figure>



<p class="has-text-align-center has-medium-font-size">&#8230; practical, not technical <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f609.png" alt="😉" class="wp-smiley" style="height: 1em; max-height: 1em;" /></p>



<div style="height:32px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading">TRUNCATE with Survivor Table (Temporary Table)</h3>



<p>Data that is still required is simply moved to a temporary table and retrieved after TRUNCATE</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: sql; title: ; notranslate">
-- Step 1: Create temp table with rows to keep
	SELECT * INTO #SurvivorRows 
	FROM 
		MyTable 
	WHERE
		DateColumn &gt;= &#039;2024-01-01&#039;

-- Step 2: Truncate the entire table
	TRUNCATE TABLE MyTable

-- Step 3: Re-insert survivor rows
	INSERT INTO MyTable
	SELECT * FROM #SurvivorRows

-- Step 4: Clean up
	DROP TABLE #SurvivorRows
</pre></div>


<div style="height:32px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading">TRUNCATE with PARTITION option (2016++)</h3>



<p>Since SQL Server 2016 (every edition) it is possible to specify partitions or ranges of partitions</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: sql; title: ; notranslate">
-- Truncate specific partitions
	TRUNCATE TABLE MyPartitionedTable 
	     WITH (PARTITIONS (2, 4 TO 6))

-- Check partition status
	SELECT 
		 partition_number
		,rows 
	FROM
		sys.partitions 
	WHERE 
		OBJECT_NAME(OBJECT_ID) = &#039;MyPartitionedTable&#039;
</pre></div>


<div style="height:32px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading">TRUNCATE is always a transaction&#8230;</h3>



<p>&#8230;. and must be considered during RESTORING. Carsten illustrated this whole thing very nicely in his publication. I&#8217;m just describing it, that has to suffice at this point <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f61d.png" alt="😝" class="wp-smiley" style="height: 1em; max-height: 1em;" /></p>



<p>Even though we only write <strong>TRUNCATE TABLE</strong>, SQL Server still uses <strong>BEGIN</strong> and <strong>COMMIT TRANSACTION</strong>, which of course is recorded in the TLog. Accordingly, during a POINT IN TIME RECOVERY, the data in the table remains available until the TLog with the corresponding TRANSACTION is restored, or the time of the TRUNCATE transaction has been exceeded.</p>



<p>So you can also restore the data <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f44c.png" alt="👌" class="wp-smiley" style="height: 1em; max-height: 1em;" /></p>



<div style="height:32px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading">Continuously read Transaction log file data with fn_dblog and fn_dump_dblog</h3>



<p>Vedran wrote in one of his posts that there&#8217;s an undocumented function where you can read log records at runtime. There&#8217;s also a very cool article about this on sqlshack.com</p>



<div style="height:32px" aria-hidden="true" class="wp-block-spacer"></div>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<p class="has-text-align-center"><em><mark style="background-color:rgba(0, 0, 0, 0)" class="has-inline-color has-septenary-color">NOT recommended for production but only for test / education.</mark></em></p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<div style="height:32px" aria-hidden="true" class="wp-block-spacer"></div>


https://www.sqlshack.com/how-to-continuously-read-transaction-log-file-data-directly-in-a-sql-server-database-with-fn_dblog-and-fn_dump_dblog/


<div style="height:32px" aria-hidden="true" class="wp-block-spacer"></div>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<div style="height:32px" aria-hidden="true" class="wp-block-spacer"></div>



<h2 class="wp-block-heading">Finally</h2>



<div class="wp-block-group is-nowrap is-layout-flex wp-container-core-group-is-layout-6c531013 wp-block-group-is-layout-flex">
<figure class="wp-block-image size-full is-resized"><img loading="lazy" decoding="async" width="284" height="334" src="https://dbavonnebenan.de/wp-content/uploads/2025/05/emotes_3.png" alt="" class="wp-image-355" style="width:124px;height:auto" srcset="https://dbavonnebenan.de/wp-content/uploads/2025/05/emotes_3.png 284w, https://dbavonnebenan.de/wp-content/uploads/2025/05/emotes_3-255x300.png 255w" sizes="auto, (max-width: 284px) 100vw, 284px" /></figure>



<p>Let&#8217;s get to my initially mentioned point that all these SQL Server tips from randoms should be questioned. Because the statement that <strong>truncate is not rollback safe</strong> is simply wrong. The path to this realization was long for me, initiated by the conversation between Vedran and Carsten. I really learned a lot left and right, which I also wanted to record here again for posterity.</p>
</div>



<p>In the end, it doesn&#8217;t matter whether it&#8217;s <strong>DDL</strong> or <strong>DML</strong>. In the general SQL Server standard, for example, it&#8217;s described as DML, most RDBMSs in dialect with DDL. We&#8217;ll just agree on <strong>DDL</strong> and that&#8217;s it. Much more exciting is what happens under the hood.</p>



<div style="height:32px" aria-hidden="true" class="wp-block-spacer"></div>



<p><strong>Best comment from Vedran <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f923.png" alt="🤣" class="wp-smiley" style="height: 1em; max-height: 1em;" /></strong></p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="has-overpass-mono-font-family">&#8222;Or you are trying to argue with SQL Certified Master about SQL? Good luck Mr :)&#8220;</p>
</blockquote>



<div style="height:32px" aria-hidden="true" class="wp-block-spacer"></div>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<div style="height:32px" aria-hidden="true" class="wp-block-spacer"></div>



<h2 class="wp-block-heading">Links from the text</h2>


https://learn.microsoft.com/en-us/sql/t-sql/statements/statements?view=sql-server-ver17#data-definition-language

https://learn.microsoft.com/en-us/sql/t-sql/statements/truncate-table-transact-sql?view=sql-server-ver17

https://blog.sqlauthority.com/2010/03/04/sql-server-rollback-truncate-command-in-transaction/

https://www.sqlshack.com/the-internals-of-sql-truncate-and-sql-delete-statements/

https://www.sqlshack.com/how-to-continuously-read-transaction-log-file-data-directly-in-a-sql-server-database-with-fn_dblog-and-fn_dump_dblog/

https://en.wikipedia.org/wiki/Truncate_(SQL)

https://learn.microsoft.com/en-us/sql/relational-databases/pages-and-extents-architecture-guide?view=sql-server-ver17


<div style="height:32px" aria-hidden="true" class="wp-block-spacer"></div>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<div style="height:32px" aria-hidden="true" class="wp-block-spacer"></div>



<h2 class="wp-block-heading">Full Conversation</h2>



<details class="wp-block-details is-layout-flow wp-block-details-is-layout-flow"><summary>Expand</summary>
<p><strong>Vedran Kesegic</strong></p>



<p>In sql server, truncate can be rolled back (if encapsulated within transaction). It is a logged operation.</p>



<p><strong>Carsten Saastamoinen-Jakobsen</strong></p>



<p>With DELETE we can specify the WHERE clause and some or all rows can be removed. All deleted rows are logged with BEFORE IMAGE/the data values. With TRUNCATE we can delete all rows or only the rows in one or more partitions, if we have a partition table. The only information logged is the pageid. The physical removing/releasing to unused pages happens in a background task after COMMIT. No data is stored in the log, so less and faster! And it is not possible to restore to point in time because no data is logged. The pageid in the log can already have being &#8218;released&#8216; to unused pages and therefore already used to data from another object &#8211; table/index. Before COMMIT the pages is still allocated to the table and still with data. If no explicit transaction is specified the transaction is already committed!</p>



<p><strong>Vedran Kesegic</strong></p>



<p>&#8222;No data is stored in the log&#8220; and &#8222;not possible to restore to point in time&#8220; is both incorrect for TRUNCATE. The difference is, truncate logs only deallocation of pages, which is multiple orders of magnitude less (basically insignifficant) compared to DELETE which logs data that is deleted. That is why truncate is &#8222;instant&#8220;. If you delete eg. 10GB of data, you wait for 10GB to be written to transaction log, which takes time. Truncate logs maybe 1KB for deallocation info, which is instant. If you do not want to delete all rows, you can still use TRUNCATE by storing survivor rows aside before truncating, and inserting them back after truncate. Even better, you can use SWITCH into some dummy table (even with not-partitioned tables) to remove rows quickly, and then insert survivor rows back from that dummy into original table. That is the fastest way to delete when a smaller percent of rows needs to survive.</p>



<p><strong>Vedran Kesegic</strong></p>



<p>Obvious difference is that truncate removes all rows from table (&#8222;truncate table&#8220;) or from partition(s), when using &#8222;trancate table xy with partitions (&#8230;)&#8220;. You cannot choose which rows to delete, they all are gone. With delete you can have a WHERE clause to choose which rows to delete. Delete is more appropriate if you need to delete a small percentage of table rows (or partition rows).</p>



<p><strong>Vedran Kesegic</strong></p>



<p>Truncate is a metadata operation, requires a different lock (SCH_M, schema modification lock). If also has some restrictins regarding foreign keys etc. Check the documentation for the list of limitations: https://learn.microsoft.com/en-us/sql/t-sql/statements/truncate-table-transact-sql</p>



<p><strong>Carsten Saastamoinen-Jakobsen</strong></p>



<p>It is not an argument to call TRUNCATE a DDL statement because a certain lock is used! When statements are categorized, it must be based on what happens and not how a given database system solves the task. Data is deleted, the structure is preserved and only some data may be deleted. When one or more partitions are deleted, the condition is also well-known, but just limited. In previous versions of database systems &#8211; and perhaps still &#8211; a TRUNCATE was performed by a DROP TABLE + CREATE TABLE. Hence DDL rights. It just shows that it is problematic to categorize based on how it is performed and not based on what actually happens. If we look at the documentation for SQL Server, it is not mentioned that TRUNCATE is a DDL. We may have to recognize that a previous error categorization should not continue to call TRUNCATE a DDL, but recognize that it is a DML. We will also be free from erroneous claims about only deleting all rows, that space is not released, that the transaction cannot be rolled back, &#8230;. A DELETE operation without a user-defined transaction cannot be rolled back either! But DELETE is DML and not DDL. TRUNCATE is &#8218;created&#8216; for having af fast way of deleting rows, and extended for only deleting some of the rows &#8211; DML!!!</p>



<p><strong>Vedran Kesegic</strong></p>



<p>No, the SCH-M lock (the &#8222;king of all locks&#8220;) is not the sole reason why TRUNCATE is DDL. DML writes data (rows in user tables), while DDL writes metadata (rows in system tables, data that describes other/user objects). Truncate happens to write metadata (system tables) and thus is classified as DDL. See the doc: https://learn.microsoft.com/en-us/previous-versions/sql/sql-server-2012/ff848799(v=sql.110)</p>



<p><strong>Carsten Saastamoinen-Jakobsen</strong></p>



<p>Can you tell which data is written to the system tables that is not written when removing rows by DELETE? The only additional information is, that with TRUNCATE of all rows without specifying partitions is, that the &#8218;identity&#8216; is reset to the initial values. Removing some or all rows with specifying partitions, this value is not reset to the initial value. Empty page are handled in the same way as empty page after DELETE! All indexes are preserved, the table definition is NOT changed, &#8230;.</p>



<p><strong>Vedran Kesegic</strong></p>



<p>Better than that &#8211; you can see for yourself! The exact difference and details are present in transaction log records after you do TRUNCATE and compare that to log records that DELETE generates. Use fn_dblog() function, but only on the test system because it is undocumented and might have an impact.</p>



<p>Or you are trying to say that Microsoft is wrong in documentation? If so, report it to MS so doc can be fixed. But in this case there is nothing to fix.</p>



<p>Or you are trying to argue with SQL Certified Master about SQL? Good luck Mr <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f642.png" alt="🙂" class="wp-smiley" style="height: 1em; max-height: 1em;" /></p>



<p><strong>Carsten Saastamoinen-Jakobsen</strong></p>



<p>I say that MS is right in the documentations because they are not telling that TRUNCATE is as DDL, because they know that TRUNCATE is a DML!. Of course there is less in the log &#8211; it is the idea wit TRUNCATE!!!!!!!! Just te functionality and te reason for implementing TRUNCATE as an alternative to DELETE. But the log is NOT the system tables!!!!! So please tell which system tables are updated as they are updated when CRAETE, ALTER and DROP but NOT updated when INSERT, DELETE or UPDATE.</p>



<p><strong>Vedran Kesegic</strong></p>



<p>TRUNCATE modifies internal system tables sys.sysallocunits and sys.sysrowsets. DELETE modifies indexes and stats &#8211; see attached pictures. You could do that yourself using fn_dblog as instructed, but I did that for you so you don&#8217;t have to. If you want to know more about deep SQL Server internals you can attend one of my trainings, or trainings of other SQL Masters like Brent Ozar, Paul Randal (sqlskills), etc.</p>



<p><strong>Carsten Saastamoinen-Jakobsen</strong></p>



<p>Of course they are modified as INSERT DELETE and UPDATE modify this informations. But INSERT, DELETE and UPDATE are still only DML and not DDL. And I am not impressed of your job by showing me this information. If you was showing the same for the other DML statements it would be fine. So maybe you should modify your course material and also shows the same for the other DML statements!!!!</p>
</details>



<p></p><p>The post <a href="https://dbavonnebenan.de/sql-server-truncate-vs-delete-dml-or-ddl-en/">TRUNCATE TABLE – DDL vs DML – deep dive in operations and practical tips</a> first appeared on <a href="https://dbavonnebenan.de">DBA von Nebenan</a>.</p>]]></content:encoded>
					
					<wfw:commentRss>https://dbavonnebenan.de/sql-server-truncate-vs-delete-dml-or-ddl-en/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>TRUNCATE TABLE Operationen im Detail &#8211; ROLLBACK ist doch möglich</title>
		<link>https://dbavonnebenan.de/sql-server-truncate-vs-delete-dml-or-ddl-de/</link>
					<comments>https://dbavonnebenan.de/sql-server-truncate-vs-delete-dml-or-ddl-de/#respond</comments>
		
		<dc:creator><![CDATA[Gabriel]]></dc:creator>
		<pubDate>Mon, 26 May 2025 08:16:15 +0000</pubDate>
				<category><![CDATA[Sql Server]]></category>
		<category><![CDATA[TSql]]></category>
		<category><![CDATA[DE]]></category>
		<category><![CDATA[PERFORMANCE]]></category>
		<category><![CDATA[ROLLBACK]]></category>
		<category><![CDATA[TABLE]]></category>
		<category><![CDATA[TRANSACTION]]></category>
		<category><![CDATA[TRUNCATE]]></category>
		<guid isPermaLink="false">https://dbavonnebenan.de/?p=428</guid>

					<description><![CDATA[<p>Kürzlich bin ich über einen LinkedIn-Post zu TRUNCATE vs. DELETE gestolpert, wie so viele andere auch. Typischerweise posten zufällige Mitglieder einfache SQL Server Tipps, schön verpackt in Grafiken und allgemein gehalten. Obwohl das für Anfänger absolut legitim ist &#8211; da diese einfachen Einstiegspunkte vielen Neulingen helfen können zu verstehen und einen großartigen Startpunkt bieten &#8211; [&#8230;]</p>
<p>The post <a href="https://dbavonnebenan.de/sql-server-truncate-vs-delete-dml-or-ddl-de/">TRUNCATE TABLE Operationen im Detail – ROLLBACK ist doch möglich</a> first appeared on <a href="https://dbavonnebenan.de">DBA von Nebenan</a>.</p>]]></description>
										<content:encoded><![CDATA[<p>Kürzlich bin ich über einen LinkedIn-Post zu TRUNCATE vs. DELETE gestolpert, wie so viele andere auch. Typischerweise posten zufällige Mitglieder einfache SQL Server Tipps, schön verpackt in Grafiken und allgemein gehalten. Obwohl das für Anfänger absolut legitim ist &#8211; da<strong> </strong>diese einfachen Einstiegspunkte vielen Neulingen helfen können zu verstehen und einen großartigen Startpunkt bieten &#8211; ist die Tatsache, dass dieselben Themen dann viral über verschiedene Accounts gehen, klar eine ganz andere Sache.</p>



<p>Wie auch immer. Wenn du beim Thema schon etwas weiter bist und bereits weißt, dass die Aussprache nicht &#8222;SQL&#8220; sondern &#8222;Sequel&#8220; ist <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f60a.png" alt="😊" class="wp-smiley" style="height: 1em; max-height: 1em;" />, solltest du diese Posts grundsätzlich hinterfragen, da sie meist verallgemeinern.</p>



<p>So war es auch bei einem Post über die Top 3 SQL-Fehler, die &#8222;jeder&#8220; macht. Und hier kommen wir zum Punkt &#8211; TRUNCATE TABLE und seine Besonderheiten.</p>



<div style="height:32px" aria-hidden="true" class="wp-block-spacer"></div>



<figure class="wp-block-image aligncenter size-full"><a href="https://www.linkedin.com/posts/samiul-fahim_sql-dataengineering-techtips-activity-7328642722653786112-OBfj?utm_source=share&amp;utm_medium=member_desktop&amp;rcm=ACoAACt1yIMBIZu3k0gdHLx7c_luIAnHYYBMtuc"><img loading="lazy" decoding="async" width="391" height="158" src="https://dbavonnebenan.de/wp-content/uploads/2025/05/2025-05-26_09-15.png" alt="TRUNCATE kann nicht zurückgerollt werden" class="wp-image-324" srcset="https://dbavonnebenan.de/wp-content/uploads/2025/05/2025-05-26_09-15.png 391w, https://dbavonnebenan.de/wp-content/uploads/2025/05/2025-05-26_09-15-300x121.png 300w" sizes="auto, (max-width: 391px) 100vw, 391px" /></a></figure>



<p class="has-text-align-center"><a href="https://www.linkedin.com/posts/samiul-fahim_sql-dataengineering-techtips-activity-7328642722653786112-OBfj?utm_source=share&amp;utm_medium=member_desktop&amp;rcm=ACoAACt1yIMBIZu3k0gdHLx7c_luIAnHYYBMtuc" target="_blank" rel="noreferrer noopener">LinkedIn</a></p>



<div style="height:32px" aria-hidden="true" class="wp-block-spacer"></div>



<p>Dasselbe passierte mit <a href="https://www.linkedin.com/in/vedrankesegic/">Vedran Kesegic</a>, der den Hinweis gab, dass TRUNCATE TABLE anscheinend rollback-fähig ist, wenn es in einer Transaktion gekapselt wird. Dann sprang <a href="https://www.linkedin.com/in/saastamoinen/">Carsten Saastamoinen-Jakobsen</a> ein und die Sache wurde wild. So wild, dass mich das ganze Thema dermaßen gepackt hat, dass ich einen Deep Dive gemacht habe. Außerdem fiel in diesem Austausch so viel Wissen ab, dass es schade wäre, wenn es in einem Kommentarbereich verschwindet.</p>



<div style="height:32px" aria-hidden="true" class="wp-block-spacer"></div>



<h2 class="wp-block-heading">Was dich hier erwartet &#8211; TRUNCATE TABLE im Detail</h2>



<p>Ich habe den Austausch verglichen und mir die Kernaussagen angeschaut. Ich habe auch separat mit beiden etwas mehr getextet, und Carsten gab mir Auszüge aus einer seiner Publikationen, wo das Thema technisch behandelt wird.</p>



<p>Technisch gesehen sind sich beide einig, auch wenn sie es nicht sagen <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f923.png" alt="🤣" class="wp-smiley" style="height: 1em; max-height: 1em;" />. Aber was am Ende wirklich wild war, war die Frage zum TRUNCATE TABLE Befehl:</p>



<div style="height:32px" aria-hidden="true" class="wp-block-spacer"></div>



<figure class="wp-block-image aligncenter size-full"><img loading="lazy" decoding="async" width="595" height="159" src="https://dbavonnebenan.de/wp-content/uploads/2025/05/ddl_dml.png" alt="DML oder DDL - TRUNCATE TABLE Klassifizierung" class="wp-image-326" srcset="https://dbavonnebenan.de/wp-content/uploads/2025/05/ddl_dml.png 595w, https://dbavonnebenan.de/wp-content/uploads/2025/05/ddl_dml-300x80.png 300w" sizes="auto, (max-width: 595px) 100vw, 595px" /></figure>



<div style="height:32px" aria-hidden="true" class="wp-block-spacer"></div>



<div class="wp-block-columns is-layout-flex wp-container-core-columns-is-layout-28f84493 wp-block-columns-is-layout-flex">
<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:100%">
<div class="wp-block-group is-nowrap is-layout-flex wp-container-core-group-is-layout-6c531013 wp-block-group-is-layout-flex">
<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="246" height="470" src="https://dbavonnebenan.de/wp-content/uploads/2025/05/emotes_standing_1.png" alt="" class="wp-image-330" srcset="https://dbavonnebenan.de/wp-content/uploads/2025/05/emotes_standing_1.png 246w, https://dbavonnebenan.de/wp-content/uploads/2025/05/emotes_standing_1-157x300.png 157w" sizes="auto, (max-width: 246px) 100vw, 246px" /></figure>



<p>Ich kann es vorwegnehmen. Diese Frage zur TRUNCATE TABLE Klassifizierung ist eine philosophische. Ich konnte mir basierend auf meiner Recherche eine Meinung bilden, mehr dazu später, aber grundsätzlich liegt es an jedem selbst zu entscheiden.</p>
</div>
</div>
</div>



<div style="height:32px" aria-hidden="true" class="wp-block-spacer"></div>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<div style="height:32px" aria-hidden="true" class="wp-block-spacer"></div>



<h2 class="wp-block-heading">Die Diskussion &#8211; TRUNCATE TABLE als DML oder DDL?</h2>



<h3 class="wp-block-heading">Vedran Kesegic</h3>



<p><strong>Kernposition: TRUNCATE TABLE ist DDL</strong></p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>&#8222;TRUNCATE modifies internal system tables sys.sysallocunits and sys.sysrowsets. DELETE modifies indexes and stats&#8220;</p>
</blockquote>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>&#8222;DML writes data (rows in user tables), while DDL writes metadata (rows in system tables, data that describes other/user objects). Truncate happens to write metadata (system tables) and thus is classified as DDL.&#8220;</p>
</blockquote>



<p><strong>Technische Argumente für TRUNCATE TABLE als DDL</strong></p>



<ul class="wp-block-list">
<li>Verwendet SCH-M (Schema Modification) Lock</li>



<li>Modifiziert System-Metadaten-Tabellen</li>



<li>Microsoft-Dokumentation klassifiziert es als DDL</li>



<li>Loggt Page-Deallocations, nicht Row-Deletions</li>
</ul>



<p><strong>Kernaussage zum TRUNCATE TABLE Befehl</strong></p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>&#8222;TRUNCATE modifies internal system tables sys.sysallocunits and sys.sysrowsets. DELETE modifies indexes and stats &#8211; see attached&#8220;</p>
</blockquote>



<div style="height:50px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading">Carsten Saastamoinen-Jakobsen</h3>



<p><strong>Kernposition: TRUNCATE TABLE ist DML</strong></p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>&#8222;It is not an argument to call TRUNCATE a DDL statement because a certain lock is used! When statements are categorized, it must be based on what happens and not how a given database system solves the task.&#8220;</p>
</blockquote>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>&#8222;We may have to recognize that a previous error categorization should not continue to call TRUNCATE a DDL, but recognize that it is a DML.&#8220;</p>
</blockquote>



<p><strong>Logische Argumente für TRUNCATE TABLE als DML</strong></p>



<ul class="wp-block-list">
<li>Daten werden gelöscht, Struktur bleibt erhalten</li>



<li>Keine Änderungen an der Tabellendefinition</li>



<li>Folgt semantischen Regeln der Datenmanipulation</li>



<li>Kann nur bestimmte Partitionen löschen (nicht alle Daten)</li>
</ul>



<p><strong>Kernaussage zur TRUNCATE TABLE Klassifizierung</strong></p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>&#8222;I say that MS is right in the documentations because they are not telling that TRUNCATE is as DDL, because they know that TRUNCATE is a DML!&#8220;</p>



<p><mark style="background-color:rgba(0, 0, 0, 0)" class="has-inline-color has-denary-color"><em>ANMERKUNG Gabriel <br></em></mark><em>Ich denke, das ist wirklich etwas fragwürdig, weil die Dokumentation absolut klar</em> <em>zu diesem Punkt ist</em> &#8211;&gt; <strong><em>DDL </em></strong>(<a href="https://learn.microsoft.com/en-us/sql/t-sql/statements/statements?view=sql-server-ver17#data-definition-language" target="_blank" rel="noreferrer noopener">microsoft.com</a>)<br></p>
</blockquote>



<p><strong>Zitat aus der Publikation zum TRUNCATE TABLE</strong></p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>&#8222;As of version 2016, it is possible to delete only some of the data in a table with TRUNCATE, so it is even more cor­rect to call it manipulation than definition, because it is changing some of the data but not changing the structure.&#8220;</p>
</blockquote>



<div style="height:50px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading">Einigungspunkte bei TRUNCATE TABLE</h3>



<ul class="wp-block-list">
<li>TRUNCATE TABLE kann in einer Transaktion zurückgerollt werden
<ul class="wp-block-list">
<li>allgemeine Demo bei <a href="https://blog.sqlauthority.com/2010/03/04/sql-server-rollback-truncate-command-in-transaction/" target="_blank" rel="noreferrer noopener">sqlauthority.com</a></li>



<li>detaillierte Demo <a href="https://www.sqlshack.com/the-internals-of-sql-truncate-and-sql-delete-statements/" target="_blank" rel="noreferrer noopener">sqlshack.com</a></li>
</ul>
</li>



<li>TRUNCATE TABLE ist eine geloggte Operation</li>



<li>TRUNCATE TABLE ist schneller als DELETE bei großen Datensätzen</li>



<li>Beide verstehen die technische Implementierung</li>
</ul>



<div style="height:50px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading">Hauptstreitpunkt bei TRUNCATE TABLE</h3>



<ul class="wp-block-list">
<li><strong>Klassifizierungskriterien für TRUNCATE TABLE</strong>
<ul class="wp-block-list">
<li>Implementierungsdetails</li>
</ul>
</li>



<li><strong>Autorität</strong>
<ul class="wp-block-list">
<li>Vendor-Dokumentation (Vedran) vs. Logische Begründung (Carsten)</li>
</ul>
</li>



<li><strong>Perspektive zum TRUNCATE TABLE Befehl</strong>
<ul class="wp-block-list">
<li>Wie es intern funktioniert (Vedran) vs. Was es funktional macht (Carsten)</li>
</ul>
</li>
</ul>



<div style="height:50px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading">Meine Einschätzung zu TRUNCATE TABLE</h3>



<p>Wie du siehst, passt es technisch, aber wie betrachtest du das Ganze? Vielleicht ist es gut, dass ich nicht mit so viel Wissen vorab beladen bin wie beide, denn aus meiner Sicht ist es ziemlich klar: <strong>TRUNCATE TABLE ist DDL!!</strong></p>



<div class="wp-block-group is-nowrap is-layout-flex wp-container-core-group-is-layout-6c531013 wp-block-group-is-layout-flex">
<figure class="wp-block-image size-full is-resized"><img loading="lazy" decoding="async" width="292" height="342" src="https://dbavonnebenan.de/wp-content/uploads/2025/05/emotes_1.png" alt="" class="wp-image-344" style="width:98px;height:auto" srcset="https://dbavonnebenan.de/wp-content/uploads/2025/05/emotes_1.png 292w, https://dbavonnebenan.de/wp-content/uploads/2025/05/emotes_1-256x300.png 256w" sizes="auto, (max-width: 292px) 100vw, 292px" /></figure>



<p>Entgegen Carstens Aussage, dass keine Systemtabellen geändert werden, ist Vedrans Aussage absolut korrekt. Sieh warum&#8230; </p>
</div>



<p>&#8230; und natürlich weil Microsoft sagt, dass TRUNCATE TABLE DDL ist <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f60a.png" alt="😊" class="wp-smiley" style="height: 1em; max-height: 1em;" /> ( <a href="https://learn.microsoft.com/en-us/sql/t-sql/statements/statements?view=sql-server-ver17#data-definition-language" target="_blank" rel="noreferrer noopener">microsoft.com</a> )</p>



<p>Und Partitionierung ist DML… ich weiß nicht, auch hier sind es zumindest Systemspalten, die die Partitionierung definieren.</p>



<p></p>



<div style="height:32px" aria-hidden="true" class="wp-block-spacer"></div>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<div style="height:32px" aria-hidden="true" class="wp-block-spacer"></div>



<h2 class="wp-block-heading">Wie TRUNCATE TABLE funktioniert &#8211; Schritt für Schritt</h2>



<p>Ich würde gerne sagen, dass ich es einfach beschreibe, aber das ist einfach meine normale Beschreibung. Das Thema TRUNCATE TABLE ist wirklich komplex <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/270c.png" alt="✌" class="wp-smiley" style="height: 1em; max-height: 1em;" /></p>



<div style="height:32px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading">1 &#8211; TRUNCATE TABLE Befehl initiiert</h3>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: sql; title: ; notranslate">
TRUNCATE TABLE dbo.MyTable;
</pre></div>


<ul class="wp-block-list">
<li><strong>Erste Aktion</strong>: Akquiriert <strong>SCH-M (Schema Modification)</strong> Lock auf gesamte Tabelle</li>



<li><strong>Effekt</strong>: Tabelle ist komplett gesperrt &#8211; keine Reads, keine Writes, nichts</li>
</ul>



<div style="height:32px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading">2 &#8211; Extents identifizieren bei TRUNCATE TABLE</h3>



<ul class="wp-block-list">
<li>DEEP DIVE Extents &#8211;&gt; <a href="https://learn.microsoft.com/en-us/sql/relational-databases/pages-and-extents-architecture-guide?view=sql-server-ver17" target="_blank" rel="noreferrer noopener">microsoft.com</a></li>



<li>SQL Server liest IAM-Pages</li>



<li>Listet alle Extents auf, die zur Tabelle gehören</li>



<li><strong>Hält weiterhin</strong>: SCH-M Lock auf Tabelle</li>
</ul>



<div style="height:32px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading">3 &#8211; Einzelne Extents bei TRUNCATE TABLE sperren</h3>



<ul class="wp-block-list">
<li>Platziert <strong>X (Exclusive)</strong> Locks auf jeden Extent</li>



<li>Das sind reguläre Data-Locks, KEINE Schema-Locks</li>



<li><strong>Hält jetzt</strong>:
<ul class="wp-block-list">
<li>SCH-M Lock auf Tabelle (verhindert Zugriff)</li>



<li>X Locks auf Extents (markiert sie für Deallocation)</li>
</ul>
</li>
</ul>



<div style="height:32px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading">4 &#8211; TRUNCATE TABLE Operation loggen</h3>



<ul class="wp-block-list">
<li>Schreibt ins Transaction-Log: &#8222;Deallocate extents 8:8, 8:16, etc.&#8220;</li>



<li><strong>Hält weiterhin</strong>: Beide Lock-Typen</li>
</ul>



<div style="height:32px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading">5a &#8211; Bei COMMIT der TRUNCATE TABLE Operation</h3>



<ul class="wp-block-list">
<li>Aktualisiert IAM &#8211; markiert Extents als frei</li>



<li><strong>Gibt frei</strong> alle X Locks auf Extents</li>



<li><strong>Gibt frei</strong> SCH-M Lock auf Tabelle</li>



<li>Tabelle ist wieder zugänglich</li>
</ul>



<div style="height:32px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading">5b &#8211; Bei ROLLBACK der TRUNCATE TABLE Operation</h3>



<ul class="wp-block-list">
<li>Verwirft Deallocation-Marker</li>



<li><strong>Gibt frei</strong> alle X Locks auf Extents</li>



<li><strong>Gibt frei</strong> SCH-M Lock auf Tabelle</li>



<li>Alles bleibt unverändert</li>
</ul>



<div style="height:32px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading">Warum zwei verschiedene Lock-Typen bei TRUNCATE TABLE?</h3>



<p><strong>SCH-M Lock:</strong></p>



<ul class="wp-block-list">
<li>&#8222;Niemand rührt diese Tabelle ÜBERHAUPT an&#8220;</li>



<li>Typisch für DDL-Operationen</li>



<li>Verhindert sogar Metadaten-Abfragen</li>
</ul>



<p><strong>X Locks auf Extents:</strong></p>



<ul class="wp-block-list">
<li>&#8222;Diese spezifischen Datenblöcke werden modifiziert&#8220;</li>



<li>Typisch für Daten-Operationen</li>



<li>Stellt sicher, dass Extents nicht von anderen Sessions wiederverwendet werden bis commit/rollback</li>
</ul>



<div style="height:32px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading">Meine bescheidene Meinung zu TRUNCATE TABLE</h3>



<p>Mit diesem Wissen ist der Fall eigentlich klar für mich, aber wie siehst du es? Tatsächlich werden hier keine Benutzerdaten manipuliert; stattdessen werden System-Level-Zuweisungen angepasst. Folglich erlaube ich mir, trotz meines neuen Halbwissens, den folgenden Vergleich zu ziehen:</p>



<div class="wp-block-group is-nowrap is-layout-flex wp-container-core-group-is-layout-6c531013 wp-block-group-is-layout-flex">
<figure class="wp-block-image size-full is-resized"><img loading="lazy" decoding="async" width="307" height="328" src="https://dbavonnebenan.de/wp-content/uploads/2025/05/emotes_2.png" alt="" class="wp-image-345" style="width:131px;height:auto" srcset="https://dbavonnebenan.de/wp-content/uploads/2025/05/emotes_2.png 307w, https://dbavonnebenan.de/wp-content/uploads/2025/05/emotes_2-281x300.png 281w" sizes="auto, (max-width: 307px) 100vw, 307px" /></figure>



<p>&#8222;Ich sage auch nicht, dass ich Text in einem Word-Dokument lösche, wenn ich die Festplatte schnell formatiere und nur die Block-Allocation lösche.&#8220;</p>
</div>



<p><br>Ja, ich denke das ist ein schöner Vergleich&#8230; Fall abgeschlossen, jetzt ein paar technisch interessante Aspekte und Lösungen zum TRUNCATE TABLE Befehl</p>



<div style="height:32px" aria-hidden="true" class="wp-block-spacer"></div>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<div style="height:32px" aria-hidden="true" class="wp-block-spacer"></div>



<h2 class="wp-block-heading">Technischer Teil, Hacks und TRUNCATE TABLE How-To</h2>



<div style="height:32px" aria-hidden="true" class="wp-block-spacer"></div>



<figure class="wp-block-image aligncenter size-full is-resized"><img loading="lazy" decoding="async" width="643" height="388" src="https://dbavonnebenan.de/wp-content/uploads/2025/05/meme_trunc.jpg" alt="" class="wp-image-348" style="width:325px;height:auto" srcset="https://dbavonnebenan.de/wp-content/uploads/2025/05/meme_trunc.jpg 643w, https://dbavonnebenan.de/wp-content/uploads/2025/05/meme_trunc-300x181.jpg 300w" sizes="auto, (max-width: 643px) 100vw, 643px" /></figure>



<p class="has-text-align-center has-medium-font-size">&#8230; praktisch, nicht technisch <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f609.png" alt="😉" class="wp-smiley" style="height: 1em; max-height: 1em;" /></p>



<div style="height:32px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading">TRUNCATE TABLE mit Survivor Table (Temporary Table)</h3>



<p>Daten, die noch benötigt werden, werden einfach in eine temporäre Tabelle verschoben und nach TRUNCATE TABLE wieder abgerufen</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: sql; title: ; notranslate">
-- Step 1: Create temp table with rows to keep
	SELECT * INTO #SurvivorRows 
	FROM 
		MyTable 
	WHERE
		DateColumn &gt;= &#039;2024-01-01&#039;

-- Step 2: Truncate the entire table
	TRUNCATE TABLE MyTable

-- Step 3: Re-insert survivor rows
	INSERT INTO MyTable
	SELECT * FROM #SurvivorRows

-- Step 4: Clean up
	DROP TABLE #SurvivorRows
</pre></div>


<div style="height:32px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading">TRUNCATE TABLE mit PARTITION Option (2016++)</h3>



<p>Seit SQL Server 2016 (jede Edition) ist es möglich, Partitionen oder Bereiche von Partitionen bei TRUNCATE TABLE zu spezifizieren</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: sql; title: ; notranslate">
-- Truncate specific partitions
	TRUNCATE TABLE MyPartitionedTable 
	     WITH (PARTITIONS (2, 4 TO 6))

-- Check partition status
	SELECT 
		 partition_number
		,rows 
	FROM
		sys.partitions 
	WHERE 
		OBJECT_NAME(OBJECT_ID) = &#039;MyPartitionedTable&#039;
</pre></div>


<div style="height:32px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading">TRUNCATE TABLE ist immer eine Transaktion&#8230;</h3>



<p>&#8230;. und muss beim RESTORE berücksichtigt werden. Carsten hat das ganze sehr schön in seiner Publikation illustriert. Ich beschreibe es nur, das muss an dieser Stelle reichen <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f61d.png" alt="😝" class="wp-smiley" style="height: 1em; max-height: 1em;" /></p>



<p>Obwohl wir nur <strong>TRUNCATE TABLE</strong> schreiben, verwendet SQL Server trotzdem <strong>BEGIN</strong> und <strong>COMMIT TRANSACTION</strong>, was natürlich im TLog aufgezeichnet wird. Entsprechend bleiben bei einer POINT IN TIME RECOVERY die Daten in der Tabelle verfügbar, bis das TLog mit der entsprechenden TRANSACTION wiederhergestellt wird, oder die Zeit der TRUNCATE TABLE Transaktion überschritten wurde.</p>



<p>Du kannst also auch die Daten wiederherstellen <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f44c.png" alt="👌" class="wp-smiley" style="height: 1em; max-height: 1em;" /></p>



<div style="height:32px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading">Transaction Log File Daten kontinuierlich lesen mit fn_dblog und fn_dump_dblog</h3>



<p>Vedran schrieb in einem seiner Posts, dass es eine undokumentierte Funktion gibt, wo du Log-Records zur Laufzeit lesen kannst. Es gibt auch einen sehr coolen Artikel dazu auf sqlshack.com</p>



<div style="height:32px" aria-hidden="true" class="wp-block-spacer"></div>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<p class="has-text-align-center"><em><mark style="background-color:rgba(0, 0, 0, 0)" class="has-inline-color has-septenary-color">NICHT empfohlen für Produktion, sondern nur für Test / Bildung.</mark></em></p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<div style="height:32px" aria-hidden="true" class="wp-block-spacer"></div>


https://www.sqlshack.com/how-to-continuously-read-transaction-log-file-data-directly-in-a-sql-server-database-with-fn_dblog-and-fn_dump_dblog/


<div style="height:32px" aria-hidden="true" class="wp-block-spacer"></div>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<div style="height:32px" aria-hidden="true" class="wp-block-spacer"></div>



<h2 class="wp-block-heading">Fazit zu TRUNCATE TABLE</h2>



<div class="wp-block-group is-nowrap is-layout-flex wp-container-core-group-is-layout-6c531013 wp-block-group-is-layout-flex">
<figure class="wp-block-image size-full is-resized"><img loading="lazy" decoding="async" width="284" height="334" src="https://dbavonnebenan.de/wp-content/uploads/2025/05/emotes_3.png" alt="" class="wp-image-355" style="width:124px;height:auto" srcset="https://dbavonnebenan.de/wp-content/uploads/2025/05/emotes_3.png 284w, https://dbavonnebenan.de/wp-content/uploads/2025/05/emotes_3-255x300.png 255w" sizes="auto, (max-width: 284px) 100vw, 284px" /></figure>



<p>Kommen wir zu meinem eingangs erwähnten Punkt, dass all diese SQL Server Tipps von randoms hinterfragt werden sollten. Denn die Aussage, dass <strong>TRUNCATE TABLE nicht rollback-sicher</strong> ist, ist schlichtweg falsch. Der Weg zu dieser Erkenntnis war für mich lang, eingeleitet durch die Unterhaltung zwischen Vedran und Carsten. Ich habe wirklich viel links und rechts dazugelernt, was ich auch hier nochmal für die Nachwelt festhalten wollte.</p>
</div>



<p>Am Ende ist es egal, ob TRUNCATE TABLE <strong>DDL</strong> oder <strong>DML</strong> ist. Im allgemeinen SQL Server Standard wird es beispielsweise als DML beschrieben, die meisten RDBMSs im Dialekt mit DDL. Wir einigen uns einfach auf <strong>DDL</strong> und gut ist. Viel spannender ist, was unter der Haube beim TRUNCATE TABLE Befehl passiert.</p>



<div style="height:32px" aria-hidden="true" class="wp-block-spacer"></div>



<p><strong>Bester Kommentar von Vedran <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f923.png" alt="🤣" class="wp-smiley" style="height: 1em; max-height: 1em;" /></strong></p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="has-overpass-mono-font-family">&#8222;Or you are trying to argue with SQL Certified Master about SQL? Good luck Mr :)&#8220;</p>
</blockquote>



<div style="height:32px" aria-hidden="true" class="wp-block-spacer"></div>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<div style="height:32px" aria-hidden="true" class="wp-block-spacer"></div>



<h2 class="wp-block-heading">Links aus dem Text</h2>


https://learn.microsoft.com/en-us/sql/t-sql/statements/statements?view=sql-server-ver17#data-definition-language

https://learn.microsoft.com/en-us/sql/t-sql/statements/truncate-table-transact-sql?view=sql-server-ver17

https://blog.sqlauthority.com/2010/03/04/sql-server-rollback-truncate-command-in-transaction/

https://www.sqlshack.com/the-internals-of-sql-truncate-and-sql-delete-statements/

https://www.sqlshack.com/how-to-continuously-read-transaction-log-file-data-directly-in-a-sql-server-database-with-fn_dblog-and-fn_dump_dblog/

https://en.wikipedia.org/wiki/Truncate_(SQL)

https://learn.microsoft.com/en-us/sql/relational-databases/pages-and-extents-architecture-guide?view=sql-server-ver17


<div style="height:32px" aria-hidden="true" class="wp-block-spacer"></div>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<div style="height:32px" aria-hidden="true" class="wp-block-spacer"></div>



<h2 class="wp-block-heading">Full Conversation</h2>



<details class="wp-block-details is-layout-flow wp-block-details-is-layout-flow"><summary>Expand</summary>
<p><strong>Vedran Kesegic</strong></p>



<p>In sql server, truncate can be rolled back (if encapsulated within transaction). It is a logged operation.</p>



<p><strong>Carsten Saastamoinen-Jakobsen</strong></p>



<p>With DELETE we can specify the WHERE clause and some or all rows can be removed. All deleted rows are logged with BEFORE IMAGE/the data values. With TRUNCATE we can delete all rows or only the rows in one or more partitions, if we have a partition table. The only information logged is the pageid. The physical removing/releasing to unused pages happens in a background task after COMMIT. No data is stored in the log, so less and faster! And it is not possible to restore to point in time because no data is logged. The pageid in the log can already have being &#8218;released&#8216; to unused pages and therefore already used to data from another object &#8211; table/index. Before COMMIT the pages is still allocated to the table and still with data. If no explicit transaction is specified the transaction is already committed!</p>



<p><strong>Vedran Kesegic</strong></p>



<p>&#8222;No data is stored in the log&#8220; and &#8222;not possible to restore to point in time&#8220; is both incorrect for TRUNCATE. The difference is, truncate logs only deallocation of pages, which is multiple orders of magnitude less (basically insignifficant) compared to DELETE which logs data that is deleted. That is why truncate is &#8222;instant&#8220;. If you delete eg. 10GB of data, you wait for 10GB to be written to transaction log, which takes time. Truncate logs maybe 1KB for deallocation info, which is instant. If you do not want to delete all rows, you can still use TRUNCATE by storing survivor rows aside before truncating, and inserting them back after truncate. Even better, you can use SWITCH into some dummy table (even with not-partitioned tables) to remove rows quickly, and then insert survivor rows back from that dummy into original table. That is the fastest way to delete when a smaller percent of rows needs to survive.</p>



<p><strong>Vedran Kesegic</strong></p>



<p>Obvious difference is that truncate removes all rows from table (&#8222;truncate table&#8220;) or from partition(s), when using &#8222;trancate table xy with partitions (&#8230;)&#8220;. You cannot choose which rows to delete, they all are gone. With delete you can have a WHERE clause to choose which rows to delete. Delete is more appropriate if you need to delete a small percentage of table rows (or partition rows).</p>



<p><strong>Vedran Kesegic</strong></p>



<p>Truncate is a metadata operation, requires a different lock (SCH_M, schema modification lock). If also has some restrictins regarding foreign keys etc. Check the documentation for the list of limitations: https://learn.microsoft.com/en-us/sql/t-sql/statements/truncate-table-transact-sql</p>



<p><strong>Carsten Saastamoinen-Jakobsen</strong></p>



<p>It is not an argument to call TRUNCATE a DDL statement because a certain lock is used! When statements are categorized, it must be based on what happens and not how a given database system solves the task. Data is deleted, the structure is preserved and only some data may be deleted. When one or more partitions are deleted, the condition is also well-known, but just limited. In previous versions of database systems &#8211; and perhaps still &#8211; a TRUNCATE was performed by a DROP TABLE + CREATE TABLE. Hence DDL rights. It just shows that it is problematic to categorize based on how it is performed and not based on what actually happens. If we look at the documentation for SQL Server, it is not mentioned that TRUNCATE is a DDL. We may have to recognize that a previous error categorization should not continue to call TRUNCATE a DDL, but recognize that it is a DML. We will also be free from erroneous claims about only deleting all rows, that space is not released, that the transaction cannot be rolled back, &#8230;. A DELETE operation without a user-defined transaction cannot be rolled back either! But DELETE is DML and not DDL. TRUNCATE is &#8218;created&#8216; for having af fast way of deleting rows, and extended for only deleting some of the rows &#8211; DML!!!</p>



<p><strong>Vedran Kesegic</strong></p>



<p>No, the SCH-M lock (the &#8222;king of all locks&#8220;) is not the sole reason why TRUNCATE is DDL. DML writes data (rows in user tables), while DDL writes metadata (rows in system tables, data that describes other/user objects). Truncate happens to write metadata (system tables) and thus is classified as DDL. See the doc: https://learn.microsoft.com/en-us/previous-versions/sql/sql-server-2012/ff848799(v=sql.110)</p>



<p><strong>Carsten Saastamoinen-Jakobsen</strong></p>



<p>Can you tell which data is written to the system tables that is not written when removing rows by DELETE? The only additional information is, that with TRUNCATE of all rows without specifying partitions is, that the &#8218;identity&#8216; is reset to the initial values. Removing some or all rows with specifying partitions, this value is not reset to the initial value. Empty page are handled in the same way as empty page after DELETE! All indexes are preserved, the table definition is NOT changed, &#8230;.</p>



<p><strong>Vedran Kesegic</strong></p>



<p>Better than that &#8211; you can see for yourself! The exact difference and details are present in transaction log records after you do TRUNCATE and compare that to log records that DELETE generates. Use fn_dblog() function, but only on the test system because it is undocumented and might have an impact.</p>



<p>Or you are trying to say that Microsoft is wrong in documentation? If so, report it to MS so doc can be fixed. But in this case there is nothing to fix.</p>



<p>Or you are trying to argue with SQL Certified Master about SQL? Good luck Mr <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f642.png" alt="🙂" class="wp-smiley" style="height: 1em; max-height: 1em;" /></p>



<p><strong>Carsten Saastamoinen-Jakobsen</strong></p>



<p>I say that MS is right in the documentations because they are not telling that TRUNCATE is as DDL, because they know that TRUNCATE is a DML!. Of course there is less in the log &#8211; it is the idea wit TRUNCATE!!!!!!!! Just te functionality and te reason for implementing TRUNCATE as an alternative to DELETE. But the log is NOT the system tables!!!!! So please tell which system tables are updated as they are updated when CRAETE, ALTER and DROP but NOT updated when INSERT, DELETE or UPDATE.</p>



<p><strong>Vedran Kesegic</strong></p>



<p>TRUNCATE modifies internal system tables sys.sysallocunits and sys.sysrowsets. DELETE modifies indexes and stats &#8211; see attached pictures. You could do that yourself using fn_dblog as instructed, but I did that for you so you don&#8217;t have to. If you want to know more about deep SQL Server internals you can attend one of my trainings, or trainings of other SQL Masters like Brent Ozar, Paul Randal (sqlskills), etc.</p>



<p><strong>Carsten Saastamoinen-Jakobsen</strong></p>



<p>Of course they are modified as INSERT DELETE and UPDATE modify this informations. But INSERT, DELETE and UPDATE are still only DML and not DDL. And I am not impressed of your job by showing me this information. If you was showing the same for the other DML statements it would be fine. So maybe you should modify your course material and also shows the same for the other DML statements!!!!</p>
</details>



<p></p><p>The post <a href="https://dbavonnebenan.de/sql-server-truncate-vs-delete-dml-or-ddl-de/">TRUNCATE TABLE Operationen im Detail – ROLLBACK ist doch möglich</a> first appeared on <a href="https://dbavonnebenan.de">DBA von Nebenan</a>.</p>]]></content:encoded>
					
					<wfw:commentRss>https://dbavonnebenan.de/sql-server-truncate-vs-delete-dml-or-ddl-de/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>SysAdmin Role ignores the Default User Schema &#8211; A confusing story</title>
		<link>https://dbavonnebenan.de/sql-server-sysadmin-role-can-override-the-default-schema-en/</link>
					<comments>https://dbavonnebenan.de/sql-server-sysadmin-role-can-override-the-default-schema-en/#respond</comments>
		
		<dc:creator><![CDATA[Gabriel]]></dc:creator>
		<pubDate>Thu, 08 May 2025 10:20:44 +0000</pubDate>
				<category><![CDATA[Sql Server]]></category>
		<category><![CDATA[TSql]]></category>
		<category><![CDATA[default]]></category>
		<category><![CDATA[eng]]></category>
		<category><![CDATA[schema]]></category>
		<guid isPermaLink="false">https://dbavonnebenan.de/?p=279</guid>

					<description><![CDATA[<p>Today I had another call on my desk where an error message from an application stated that an object in the Sql Server DB couldn&#8217;t be accessed. The vendor claimed &#8222;connection problem&#8220;, I said &#8222;DB or at least server problem&#8220;, customer says &#8222;everything used to work before&#8220;. The Extended Events were quickly set up, &#8222;Remote [&#8230;]</p>
<p>The post <a href="https://dbavonnebenan.de/sql-server-sysadmin-role-can-override-the-default-schema-en/">SysAdmin Role ignores the Default User Schema – A confusing story</a> first appeared on <a href="https://dbavonnebenan.de">DBA von Nebenan</a>.</p>]]></description>
										<content:encoded><![CDATA[<p>Today I had another call on my desk where an error message from an application stated that an object in the Sql Server DB couldn&#8217;t be accessed. The vendor claimed &#8222;connection problem&#8220;, I said &#8222;DB or at least server problem&#8220;, customer says &#8222;everything used to work before&#8220;.</p>



<p>The Extended Events were quickly set up, &#8222;Remote Procedure Call Starting&#8220; shows me a statement without a fully qualified table name. The target object is in the database under the db_owner schema. Welllll, that&#8217;s already questionable, but OK. Why not keep it as dbo?</p>



<p>Anyway, the behavior at this point was really incomprehensible to me since everything was configured correctly: Default schema of the user, db_owner on the DB. And the SysAdmin. I had noticed that, but well, customer&#8217;s decision. I&#8217;ll note things like this in a session, but won&#8217;t change anything to avoid endangering grown dependencies.</p>



<p>After searching for a long time, I almost gave up, referred to the vendor and ended the call. Unfortunately, I can&#8217;t let go of stuff like this, so I fired up the lab, rebuilt the environment and lo and behold, found the error. </p>



<p>The problem is the SysAdmin role. It directly maps the login to the dbo user of the database &#8211; regardless of whether the actual user exists or not, thus inheriting dbo&#8217;s default schema. Mindblowing, didn&#8217;t know that. 🙂</p>



<div style="height:13px" aria-hidden="true" class="wp-block-spacer"></div>



<h2 class="wp-block-heading">SysAdmin Role InActive</h2>



<figure class="wp-block-image size-full is-resized has-custom-border"><img loading="lazy" decoding="async" width="655" height="449" src="https://dbavonnebenan.de/wp-content/uploads/2025/05/2025-05-08_11-49_1.png" alt="" class="has-border-color has-base-border-color wp-image-285" style="border-width:1px;width:641px;height:auto" srcset="https://dbavonnebenan.de/wp-content/uploads/2025/05/2025-05-08_11-49_1.png 655w, https://dbavonnebenan.de/wp-content/uploads/2025/05/2025-05-08_11-49_1-300x206.png 300w" sizes="auto, (max-width: 655px) 100vw, 655px" /></figure>



<div style="height:13px" aria-hidden="true" class="wp-block-spacer"></div>



<h2 class="wp-block-heading">SysAdmin Role Active</h2>



<figure class="wp-block-image size-full is-resized has-custom-border"><img loading="lazy" decoding="async" width="667" height="351" src="https://dbavonnebenan.de/wp-content/uploads/2025/05/2025-05-08_11-50.png" alt="" class="has-border-color has-base-border-color wp-image-287" style="border-width:1px;width:631px;height:auto" srcset="https://dbavonnebenan.de/wp-content/uploads/2025/05/2025-05-08_11-50.png 667w, https://dbavonnebenan.de/wp-content/uploads/2025/05/2025-05-08_11-50-300x158.png 300w" sizes="auto, (max-width: 667px) 100vw, 667px" /></figure>



<figure class="wp-block-image size-large is-resized has-custom-border"><img loading="lazy" decoding="async" width="504" height="212" src="https://dbavonnebenan.de/wp-content/uploads/2025/05/2025-05-08_11-13.png" alt="" class="has-border-color has-base-border-color wp-image-281" style="border-width:1px;width:495px;height:auto" srcset="https://dbavonnebenan.de/wp-content/uploads/2025/05/2025-05-08_11-13.png 504w, https://dbavonnebenan.de/wp-content/uploads/2025/05/2025-05-08_11-13-300x126.png 300w" sizes="auto, (max-width: 504px) 100vw, 504px" /></figure>



<div style="height:13px" aria-hidden="true" class="wp-block-spacer"></div>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<div style="height:13px" aria-hidden="true" class="wp-block-spacer"></div>



<details class="wp-block-details is-layout-flow wp-block-details-is-layout-flow"><summary>T-Sql CODE I used for the screenshots</summary><div class="wp-block-syntaxhighlighter-code "><pre class="brush: sql; title: ; notranslate">
DECLARE @IsSysAdmin INT
SELECT @IsSysAdmin = IS_SRVROLEMEMBER(&#039;sysadmin&#039;)


SELECT SYSTEM_USER AS CurrentLoginName;

SELECT 
    &#039;Current User&#039;  AS Info,
    USER_NAME()     AS Username,
    SCHEMA_NAME()   AS CurrentSchema,
    CASE WHEN @IsSysAdmin = 1 THEN &#039;Yes&#039; ELSE &#039;No&#039; END 
                    AS IsSysAdmin


SELECT 
    &#039;Default Schema&#039;        AS Info,
    dp.name                 AS Username,
    dp.default_schema_name AS DefaultSchema
FROM 
    sys.database_principals dp
WHERE 
    dp.name = USER_NAME()


SELECT 
    &#039;DB Role Membership&#039;    AS Info,
    USER_NAME()             AS Username,
    r.name                  AS RoleName
FROM 
    sys.database_principals u
JOIN 
    sys.database_role_members m ON u.principal_id = m.member_principal_id
JOIN 
    sys.database_principals r ON m.role_principal_id = r.principal_id
WHERE 
    u.name = USER_NAME()

--------------------

-- select * from &#x5B;customSchema].&#x5B;userTable] &lt;&lt;&lt;&lt;&lt; ALWAYS WORKING !!!!!

 select * from &#x5B;userTable]



</pre></div></details>



<div style="height:13px" aria-hidden="true" class="wp-block-spacer"></div>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<div class="wp-block-group is-nowrap is-layout-flex wp-container-core-group-is-layout-6c531013 wp-block-group-is-layout-flex">
<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="283" height="292" src="https://dbavonnebenan.de/wp-content/uploads/2025/05/2025-05-08_11-43.png" alt="" class="wp-image-284"/></figure>



<ul class="wp-block-list">
<li>Always use fully qualified object names, someday you&#8217;ll be glad you did. You can read this in many blogs.</li>



<li>Always work according to POLP (<a href="https://en.wikipedia.org/wiki/Principle_of_least_privilege">Principle of Least Privilege</a>), an app user shouldn&#8217;t have SysAdmin rights! 🙂</li>
</ul>
</div>



<p class="has-text-align-center"><strong>Gabriel, der DBAvonNebenan</strong></p>



<p></p><p>The post <a href="https://dbavonnebenan.de/sql-server-sysadmin-role-can-override-the-default-schema-en/">SysAdmin Role ignores the Default User Schema – A confusing story</a> first appeared on <a href="https://dbavonnebenan.de">DBA von Nebenan</a>.</p>]]></content:encoded>
					
					<wfw:commentRss>https://dbavonnebenan.de/sql-server-sysadmin-role-can-override-the-default-schema-en/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Dokumentation von Blocks und Deadlocks &#8211; Wenn der 3rd-Party Vendor Beweise fordert</title>
		<link>https://dbavonnebenan.de/reporting-sql-server-blocks-de/</link>
					<comments>https://dbavonnebenan.de/reporting-sql-server-blocks-de/#respond</comments>
		
		<dc:creator><![CDATA[Gabriel]]></dc:creator>
		<pubDate>Mon, 28 Apr 2025 18:11:00 +0000</pubDate>
				<category><![CDATA[PowerShell]]></category>
		<category><![CDATA[Sql Server]]></category>
		<category><![CDATA[BLOCK]]></category>
		<category><![CDATA[DE]]></category>
		<category><![CDATA[DEADLOCK]]></category>
		<category><![CDATA[REPORTING]]></category>
		<guid isPermaLink="false">https://dbavonnebenan.de/?p=411</guid>

					<description><![CDATA[<p>Zu wissen, dass SQL Server Blocking- oder Deadlock-Probleme hat, ist noch ziemlich einfach. Man merkt das normalerweise in der Anwendung und mit den richtigen Queries kann man auch die entsprechenden Blocking-Ketten anzeigen. Am einfachsten geht das mit http://whoisactive.com. Ich hab auch noch eine eigene Entwicklung dazu, die werde ich in einem späteren Blogpost vorstellen, die [&#8230;]</p>
<p>The post <a href="https://dbavonnebenan.de/reporting-sql-server-blocks-de/">Dokumentation von Blocks und Deadlocks – Wenn der 3rd-Party Vendor Beweise fordert</a> first appeared on <a href="https://dbavonnebenan.de">DBA von Nebenan</a>.</p>]]></description>
										<content:encoded><![CDATA[<p>Zu wissen, dass SQL Server Blocking- oder Deadlock-Probleme hat, ist noch ziemlich einfach. Man merkt das normalerweise in der Anwendung und mit den richtigen Queries kann man auch die entsprechenden Blocking-Ketten anzeigen. Am einfachsten geht das mit <a href="http://whoisactive.com">http://whoisactive.com</a>.</p>



<div style="height:34px" aria-hidden="true" class="wp-block-spacer"></div>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: sql; title: ; notranslate">
SELECT 
    blocking_session_id   AS blocking_session,
    session_id            AS blocked_session,
    wait_time / 1000.0    AS waiting_sec,
    wait_type             AS waittype,
    resource_description  AS ressource
FROM 
    sys.dm_os_waiting_tasks
WHERE 
    blocking_session_id IS NOT NULL
ORDER BY 
    wait_time DESC
</pre></div>


<div style="height:34px" aria-hidden="true" class="wp-block-spacer"></div>



<p>Ich hab auch noch eine eigene Entwicklung dazu, die werde ich in einem späteren Blogpost vorstellen, die meinen individuellen Anforderungen besser entspricht.</p>



<div style="height:33px" aria-hidden="true" class="wp-block-spacer"></div>



<h2 class="wp-block-heading">Das Problem</h2>



<p>Jetzt kann ich AdHoc Blockings anzeigen, aber bei Deadlocks wird&#8217;s interessanter. Dafür muss die System Health Session gecheckt werden, die ist auch noch flüchtig.</p>



<div style="height:33px" aria-hidden="true" class="wp-block-spacer"></div>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: sql; title: ; notranslate">
-- Query System Health Session for Deadlocks

SELECT
    event_data.value(&#039;(event/@timestamp)&#x5B;1]&#039;, &#039;datetime2&#039;) AS DeadlockDateTime,
    event_data.query(&#039;.&#039;)                                  AS DeadlockGraph
FROM
(
    SELECT 
        CAST(target_data AS XML) AS target_data
    FROM 
        sys.dm_xe_session_targets AS st
    JOIN 
        sys.dm_xe_sessions AS s ON s.address = st.event_session_address
    WHERE
             s.name = &#039;system_health&#039;
        AND st.target_name = &#039;ring_buffer&#039;
) AS data
CROSS APPLY
    target_data.nodes(&#039;//RingBufferTarget/event&#x5B;@name=&quot;xml_deadlock_report&quot;]&#039;) AS XEventData(event_data)
ORDER BY 
    DeadlockDateTime DESC;
</pre></div>


<div style="height:33px" aria-hidden="true" class="wp-block-spacer"></div>



<p>Dieses Problem hatte ich bei einem Kunden, der extrem mit Deadlocks in einer 3rd Party Anwendung zu kämpfen hatte, aber der Support war bisschen&#8230; sagen wir mal zurückhaltend. 😉</p>



<p>Wir mussten denen alles transparent melden, damit die anfangen eine Lösung zu finden. Query-Resultsets kann man natürlich als transparent betrachten, wenn man sie versteht oder verstehen will. Leider sind die XML von Health Sessions oder Extended Events nicht gerade dafür berühmt, lesbar zu sein. Der Kunde wollte aber einfach nur, dass das Problem gelöst wird.</p>



<p>Also haben wir gemeinsam eine Reporting-Lösung entwickelt.</p>



<div style="height:44px" aria-hidden="true" class="wp-block-spacer"></div>



<h2 class="wp-block-heading">Die Idee</h2>



<p>Mit Extended Events hab ich XML-Daten und mit PowerShell kann ich XML parsen wie ich will, auch zu einem Report meiner Wahl.</p>



<div style="height:44px" aria-hidden="true" class="wp-block-spacer"></div>



<h2 class="wp-block-heading">Das Ziel</h2>



<p>Wir wollten eine Möglichkeit, abseits von SQL Server Resultsets, Anwendungsprobleme mit Anomalien in den SQL Server Metriken zu überlagern, um schnell Lösungen herbeizuführen.</p>



<p>In der Woche nach dem Livegang der Lösung gab&#8217;s viele, viele &#8222;Aha&#8220;-Momente. Von falsch konfigurierten ERP-Parametern bis hin zu schlechtem Transaction-Handling.</p>



<div style="height:38px" aria-hidden="true" class="wp-block-spacer"></div>



<h2 class="wp-block-heading">Der Weg</h2>



<h3 class="wp-block-heading">Persistente Daten</h3>



<p>Das Wichtigste ist, Blocking Session und Deadlock Events persistent zu speichern. Das geht perfekt mit Extended Events. Dafür gibt&#8217;s zwei Bereiche:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: sql; title: ; notranslate">
-- SNIPPET!!

ADD EVENT sqlserver.blocked_process_report(
    ACTION(sqlserver.client_app_name,
           sqlserver.client_hostname,
           sqlserver.database_name)
),
ADD EVENT sqlserver.xml_deadlock_report(
    ACTION(sqlserver.client_app_name,
           sqlserver.client_hostname,
           sqlserver.database_name)
)
</pre></div>


<div style="height:33px" aria-hidden="true" class="wp-block-spacer"></div>



<p>Das ganze wird in einem eigenen Extended Event konfiguriert und die Aufzeichnungen laufen bereits. <strong>Fast!</strong> Wir müssen noch SQL Server konfigurieren, damit Blocked Process als Extended Events behandelt werden.</p>



<div style="height:33px" aria-hidden="true" class="wp-block-spacer"></div>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: sql; title: ; notranslate">
EXEC sp_configure &#039;show advanced options&#039;, 1;
GO
RECONFIGURE;
GO
EXEC sp_configure &#039;blocked process threshold&#039;, &#039;5&#039;; -- 5 Sekunden
RECONFIGURE
GO
</pre></div>


<div style="height:33px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading"><strong>Daten auslesen</strong></h3>



<h3 class="wp-block-heading">100% PowerShell</h3>



<p>Nachdem die Events geloggt werden, müssen sie ausgewertet werden. Was mir sofort eingefallen ist, waren die dbatools mit <a href="https://docs.dbatools.io/Read-DbaXEFile.html">Read-DbaXEFile</a>. Das Problem ist, dass die aktuell geschriebene Datei damit nicht gelesen werden kann. Du kannst dir vorstellen, dass Blocking- und Deadlock-Events nicht in solchen Massen auftreten, dass dutzende File-Rotationen innerhalb von 24h durchgeführt werden. Falls doch, hast du definitiv ein anderes Problem als es nur zu reporten. Also haben wir den Umweg über SQL Server genommen, da auch hier die aktuelle Datei gelesen werden kann.</p>



<h3 class="wp-block-heading">PowerShell + TSql</h3>



<p>XML mit Queries zu lesen, fällt mir immer noch sehr schwer, aber DANKE an Brent Ozar, uns allen bekannt, für die <a href="https://www.brentozar.com/archive/2014/03/extended-events-doesnt-hard/">grundlegende Query</a>, auf der ich aufbauen konnte. Zumindest hatte ich jetzt ein Resultset zur Orientierung und ein separates XML für jedes Event im Log.</p>



<div style="height:33px" aria-hidden="true" class="wp-block-spacer"></div>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: sql; title: ; notranslate">
-- Snippet
select
    *
FROM sys.fn_xe_file_target_read_file
   (&#039;C:\temp\XEventSessions\blocked_process*.xel&#039;,
    &#039;C:\temp\XEventSessions\blocked_process*.xem&#039;,
     null, null)
CROSS APPLY 
    (SELECT CAST(event_data AS XML) AS event_data) as xevents
</pre></div>


<div style="height:33px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading">Finalisierung mit PowerShell</h3>



<p>Da ich endlich mein XML-Event hatte, konnte ich es mit PowerShell parsen, transformieren und letztendlich in einen schönen Text-Report verpacken.</p>



<div style="height:33px" aria-hidden="true" class="wp-block-spacer"></div>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: plain; title: ; notranslate">
Sql Server Deadlock Analysis | Event Time: 2025-04-08T11:16:07.447
================================================================================

SQL STATEMENTS INVOLVED
--------------------------------------------------------------------------------

    Process ID:   process261b00cfc28
    SPID:         643
    Database:     DATABASENAME
    Query:        **DETAILED QUERY**

----------------------------------------

    Process ID:   process258294fbc28
    SPID:         650
    Database:     DATABASENAME
    Query:        **DETAILED QUERY**

----------------------------------------

VICTIM LIST
--------------------------------------------------------------------------------

    Process ID:   process261b00cfc28
    SPID:         643
    Database:     DATABASENAME


PROCESS LIST
--------------------------------------------------------------------------------

    Process ID:               process261b00cfc28
    SPID:                     643
    Isolation Level:          read uncommitted (1)
    Logical Reads:            700
    Wait Resource:            KEY: 6:72058005407662080 (fb04e1bc7788)
    Wait Time (ms):           2247
    Transaction:              user_transaction
    Last Transaction Started: 2025-04-08T11:16:07.447
    Client:                   &#039;dbserver_dll &amp;lt;ID@APPSERVER:34784&gt;&#039;
    Hostname:                 APPSERVER
    User:                     USERNAME


EXECUTED SQL STATEMENT
----------------------------------------

    **DETAILED QUERY**


Process Details
================================================================================


    Process ID:               process258294fbc28
    SPID:                     650
    Isolation Level:          read uncommitted (1)
    Logical Reads:            1904
    Wait Resource:            KEY: 6:72057996178358272 (2a9726d2d061)
    Wait Time (ms):           2278
    Transaction:              user_transaction
    Last Transaction Started: 2025-04-08T11:16:07.330
    Client:                   &#039;dbserver_dll &amp;lt;ID@APPSERVER:11664&gt;&#039;
    Hostname:                 APPSERVER
    User:                     USERNAME


EXECUTED SQL STATEMENT
----------------------------------------

    **DETAILED QUERY**


Process Details
================================================================================


RESOURCE LIST
--------------------------------------------------------------------------------

    Resource:
        Type:               keylock
        Database:           6
        Object:             DATABASENAME.dbo.tcprmp300100
        Index:              Itcprmp300100_1a
        Lock Mode:          X
        HOBT ID:            72058005407662080
        Associated Object ID: 72058005407662080

    Owners:
        Process ID:         process258294fbc28, Mode: X

    Waiting Processes:
        Process ID:         process261b00cfc28, Mode: U, Request Type: wait

================================================================================

    Resource:
        Type:               keylock
        Database:           6
        Object:             DATABASENAME.dbo.tcprrp011100
        Index:              Itcprrp011100_1a
        Lock Mode:          X
        HOBT ID:            72057996178358272
        Associated Object ID: 72057996178358272

    Owners:
        Process ID:         process261b00cfc28, Mode: X

    Waiting Processes:
        Process ID:         process258294fbc28, Mode: U, Request Type: wait

================================================================================
</pre></div>


<div style="height:33px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading">Umsetzung</h3>



<p>Es wurde gewünscht, die Ergebnisse in den Daily Report zu integrieren, den wir bereits mit PowerShell umgesetzt hatten. Jetzt ist es nicht geil, potenziell jeden Morgen 200 Textdateien an eine E-Mail zu hängen, deshalb gibt das PowerShell-Script eine Tabelle zurück, die zeigt, wie Blocks und Locks in Zeitraum X aufgetreten sind, und bei Bedarf kann man proaktiv die Ordnerstruktur checken.</p>



<div style="height:33px" aria-hidden="true" class="wp-block-spacer"></div>



<figure class="wp-block-image size-full has-custom-border"><img loading="lazy" decoding="async" width="662" height="223" src="https://dbavonnebenan.de/wp-content/uploads/2025/04/2025-04-28_15-33.png" alt="" class="has-border-color has-base-border-color wp-image-261" style="border-width:1px" srcset="https://dbavonnebenan.de/wp-content/uploads/2025/04/2025-04-28_15-33.png 662w, https://dbavonnebenan.de/wp-content/uploads/2025/04/2025-04-28_15-33-300x101.png 300w" sizes="auto, (max-width: 662px) 100vw, 662px" /></figure>



<div style="height:33px" aria-hidden="true" class="wp-block-spacer"></div>



<p>Für den dramatischen Effekt und schnellen Überblick wird auch noch eine Excel-Datei generiert. In diesem Beispiel gab&#8217;s einen schönen Kollateralfund, dass die DWH-Jobs massive Probleme hatten 🙂</p>



<div style="height:33px" aria-hidden="true" class="wp-block-spacer"></div>



<figure class="wp-block-image size-large has-custom-border is-style-default"><img loading="lazy" decoding="async" width="1024" height="471" src="https://dbavonnebenan.de/wp-content/uploads/2025/04/2025-04-27_17-18-1024x471.png" alt="" class="has-border-color wp-image-254" style="border-color:#000000;border-width:1px;border-radius:5px" srcset="https://dbavonnebenan.de/wp-content/uploads/2025/04/2025-04-27_17-18-1024x471.png 1024w, https://dbavonnebenan.de/wp-content/uploads/2025/04/2025-04-27_17-18-300x138.png 300w, https://dbavonnebenan.de/wp-content/uploads/2025/04/2025-04-27_17-18-768x353.png 768w, https://dbavonnebenan.de/wp-content/uploads/2025/04/2025-04-27_17-18.png 1131w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<div style="height:33px" aria-hidden="true" class="wp-block-spacer"></div>



<p>Und schon hatten wir ein paar Reports, die wir dem Vendor in die Hand drücken konnten, und dann ging&#8217;s sehr schnell.</p>



<div style="height:24px" aria-hidden="true" class="wp-block-spacer"></div>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: plain; title: ; notranslate">
Die Ordnerstruktur

_report/
├── text_reports/
│   └── YYYY/
│       └── MM/
│           └── DD/
│               ├── 20231120_123456_dbname_blocked.txt
│               ├── 20231120_123456_dbname_blocked.xml
│               └── 20231120_123456_dbname_deadlock.txt
└── excel_reports/
    └── BlockReport_20231120_123456.xlsx
</pre></div>


<div style="height:22px" aria-hidden="true" class="wp-block-spacer"></div>



<p><strong>Leider erlebe ich immer wieder, dass Anwendungsvendoren denken, ihre Kunden sind zu blöd und können die Anwendung nicht bedienen. Hört auf damit und glaubt euren Kunden manchmal auch ohne 101% Daten, die man nur sammeln kann, wenn man bereits Sachkenntnis hat.</strong></p>



<div style="height:33px" aria-hidden="true" class="wp-block-spacer"></div>



<h2 class="wp-block-heading">Repository ansehen</h2>



<div style="height:33px" aria-hidden="true" class="wp-block-spacer"></div>


https://github.com/gabrielkoehl/get-snapBlockEvents


<div style="height:33px" aria-hidden="true" class="wp-block-spacer"></div>



<p>Das Script hat verschiedene Parameter und Optionen, Details stehen in der README.MD. Falls du Fragen hast, lass sie in den Kommentaren oder kontaktier mich.</p>



<div style="height:37px" aria-hidden="true" class="wp-block-spacer"></div>



<h2 class="wp-block-heading">Anderes</h2>



<p>Erik Darling hat auch eine Lösung entwickelt, um Extended Events lesbar zu machen, falls andere Ansätze gebraucht werden. (<a href="https://github.com/erikdarlingdata/DarlingData/tree/main/sp_HumanEvents">sp_HumanEvents</a>)</p>



<div style="height:74px" aria-hidden="true" class="wp-block-spacer"></div>



<figure class="wp-block-image aligncenter size-full is-resized"><img loading="lazy" decoding="async" width="295" height="294" src="https://dbavonnebenan.de/wp-content/uploads/2025/04/2025-04-21_16-19.png" alt="" class="wp-image-113" style="width:223px;height:auto" srcset="https://dbavonnebenan.de/wp-content/uploads/2025/04/2025-04-21_16-19.png 295w, https://dbavonnebenan.de/wp-content/uploads/2025/04/2025-04-21_16-19-150x150.png 150w" sizes="auto, (max-width: 295px) 100vw, 295px" /></figure>



<p class="has-text-align-center"><strong>Gabriel, der DBAvonNebenan</strong></p>



<p></p><p>The post <a href="https://dbavonnebenan.de/reporting-sql-server-blocks-de/">Dokumentation von Blocks und Deadlocks – Wenn der 3rd-Party Vendor Beweise fordert</a> first appeared on <a href="https://dbavonnebenan.de">DBA von Nebenan</a>.</p>]]></content:encoded>
					
					<wfw:commentRss>https://dbavonnebenan.de/reporting-sql-server-blocks-de/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Reporting Blocks and Deadlocks: When the 3rd-Party Vendor Demands Evidence</title>
		<link>https://dbavonnebenan.de/reporting-sql-server-blocks-en/</link>
					<comments>https://dbavonnebenan.de/reporting-sql-server-blocks-en/#respond</comments>
		
		<dc:creator><![CDATA[Gabriel]]></dc:creator>
		<pubDate>Mon, 28 Apr 2025 14:19:36 +0000</pubDate>
				<category><![CDATA[PowerShell]]></category>
		<category><![CDATA[Sql Server]]></category>
		<category><![CDATA[BLOCK]]></category>
		<category><![CDATA[DEADLOCK]]></category>
		<category><![CDATA[eng]]></category>
		<category><![CDATA[REPORTING]]></category>
		<guid isPermaLink="false">https://dbavonnebenan.de/?p=253</guid>

					<description><![CDATA[<p>Knowing that SQL Server has blocking or deadlock problems is still quite easy. You usually notice this in the application, and with the right queries, you can also display the corresponding blocking chains. The simplest way to do this is with http://whoisactive.com. I also have a custom development that I will present in a later [&#8230;]</p>
<p>The post <a href="https://dbavonnebenan.de/reporting-sql-server-blocks-en/">Reporting Blocks and Deadlocks: When the 3rd-Party Vendor Demands Evidence</a> first appeared on <a href="https://dbavonnebenan.de">DBA von Nebenan</a>.</p>]]></description>
										<content:encoded><![CDATA[<p>Knowing that SQL Server has blocking or deadlock problems is still quite easy. You usually notice this in the application, and with the right queries, you can also display the corresponding blocking chains. The simplest way to do this is with <a href="http://whoisactive.com">http://whoisactive.com</a>.</p>



<div style="height:34px" aria-hidden="true" class="wp-block-spacer"></div>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: sql; title: ; notranslate">
SELECT 
    blocking_session_id   AS blocking_session,
    session_id            AS blocked_session,
    wait_time / 1000.0    AS waiting_sec,
    wait_type             AS waittype,
    resource_description  AS ressource
FROM 
    sys.dm_os_waiting_tasks
WHERE 
    blocking_session_id IS NOT NULL
ORDER BY 
    wait_time DESC
</pre></div>


<div style="height:34px" aria-hidden="true" class="wp-block-spacer"></div>



<p>I also have a custom development that I will present in a later blog post, which better meets my individual requirements.</p>



<div style="height:33px" aria-hidden="true" class="wp-block-spacer"></div>



<h2 class="wp-block-heading">The Problem</h2>



<p>Now I can display AdHoc blocks, but with deadlocks it gets more interesting. For this, the System Health Session must be checked, which is also volatile.</p>



<div style="height:33px" aria-hidden="true" class="wp-block-spacer"></div>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: sql; title: ; notranslate">
-- Query System Health Session for Deadlocks

SELECT
    event_data.value(&#039;(event/@timestamp)&#x5B;1]&#039;, &#039;datetime2&#039;) AS DeadlockDateTime,
    event_data.query(&#039;.&#039;)                                  AS DeadlockGraph
FROM
(
    SELECT 
        CAST(target_data AS XML) AS target_data
    FROM 
        sys.dm_xe_session_targets AS st
    JOIN 
        sys.dm_xe_sessions AS s ON s.address = st.event_session_address
    WHERE
             s.name = &#039;system_health&#039;
        AND st.target_name = &#039;ring_buffer&#039;
) AS data
CROSS APPLY
    target_data.nodes(&#039;//RingBufferTarget/event&#x5B;@name=&quot;xml_deadlock_report&quot;]&#039;) AS XEventData(event_data)
ORDER BY 
    DeadlockDateTime DESC;
</pre></div>


<div style="height:33px" aria-hidden="true" class="wp-block-spacer"></div>



<p>I encountered this problem with a client who was struggling enormously with deadlocks in a 3rd party application, but the support was a bit&#8230; let&#8217;s say reluctant. 😉</p>



<p>We had to report everything transparently to them so they would start finding a solution. Of course, query result sets can be considered transparent if you understand them or want to understand them. Unfortunately, the XML of Health Sessions or Extended Events are not exactly famous for being readable. Yet the client simply wanted the problem to be fixed.</p>



<p>So together we developed a reporting solution.</p>



<div style="height:44px" aria-hidden="true" class="wp-block-spacer"></div>



<h2 class="wp-block-heading">The Idea</h2>



<p>With Extended Events I have XML data, and with PowerShell I can parse XML however I want, including into a report of my choice.</p>



<div style="height:44px" aria-hidden="true" class="wp-block-spacer"></div>



<h2 class="wp-block-heading">The Goal</h2>



<p>We wanted a way, apart from SQL Server result sets, to overlay application problems with anomalies in the SQL Server metrics to quickly bring about solutions.</p>



<p>In the week after the solution went live, there were many, many &#8222;aha&#8220; moments. From incorrectly configured ERP parameters to poor transaction handling.</p>



<div style="height:38px" aria-hidden="true" class="wp-block-spacer"></div>



<h2 class="wp-block-heading">The Way</h2>



<h3 class="wp-block-heading">Persistent Data</h3>



<p>The most important thing is to persistently store blocking session and deadlock events. This works perfectly with Extended Events. There are two areas for this:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: sql; title: ; notranslate">
-- SNIPPET!!

ADD EVENT sqlserver.blocked_process_report(
    ACTION(sqlserver.client_app_name,
           sqlserver.client_hostname,
           sqlserver.database_name)
),
ADD EVENT sqlserver.xml_deadlock_report(
    ACTION(sqlserver.client_app_name,
           sqlserver.client_hostname,
           sqlserver.database_name)
)
</pre></div>


<div style="height:33px" aria-hidden="true" class="wp-block-spacer"></div>



<p>The whole thing is configured in its own Extended Event and the recordings are already running. <strong>Nearly!</strong> We still need to configure SQL Server to treat Blocked Process as Extended Events.</p>



<div style="height:33px" aria-hidden="true" class="wp-block-spacer"></div>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: sql; title: ; notranslate">
EXEC sp_configure &#039;show advanced options&#039;, 1;
GO
RECONFIGURE;
GO
EXEC sp_configure &#039;blocked process threshold&#039;, &#039;5&#039;; -- 5 Secconds
RECONFIGURE
GO
</pre></div>


<div style="height:33px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading"><strong>Read Data</strong></h3>



<h3 class="wp-block-heading">100% PowerShell</h3>



<p>After the events are logged, they need to be evaluated. What immediately came to my mind were the dbatools with <a href="https://docs.dbatools.io/Read-DbaXEFile.html">Read-DbaXEFile</a>. The problem is that the currently written file cannot be read with it. You can imagine that blocking and deadlock events don&#8217;t occur in such masses that dozens of file rotations are performed within 24 hours. If they do, you definitely have a different problem than just reporting it . So we took the detour via SQL Server, since the current file can also be read here.</p>



<h3 class="wp-block-heading">PowerShell + TSql</h3>



<p>I still find it very difficult to read XML with queries, but THANKS to Brent Ozar, known to all of us, for the<a href="https://www.brentozar.com/archive/2014/03/extended-events-doesnt-hard/"> basic query</a> I could build upon. At least I now had a result set for orientation and a separate XML for each event in the log.</p>



<div style="height:33px" aria-hidden="true" class="wp-block-spacer"></div>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: sql; title: ; notranslate">
-- Snippet
select
    *
FROM sys.fn_xe_file_target_read_file
   (&#039;C:\temp\XEventSessions\blocked_process*.xel&#039;,
    &#039;C:\temp\XEventSessions\blocked_process*.xem&#039;,
     null, null)
CROSS APPLY 
    (SELECT CAST(event_data AS XML) AS event_data) as xevents
</pre></div>


<div style="height:33px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading">Finalization with PowerShell</h3>



<p>Since I finally had my XML event, I could parse it with PowerShell, transform it, and finally package it into a nice text report.</p>



<div style="height:33px" aria-hidden="true" class="wp-block-spacer"></div>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: plain; title: ; notranslate">
Sql Server Deadlock Analysis | Event Time: 2025-04-08T11:16:07.447
================================================================================

SQL STATEMENTS INVOLVED
--------------------------------------------------------------------------------

    Process ID:   process261b00cfc28
    SPID:         643
    Database:     DATABASENAME
    Query:        **DETAILED QUERY**

----------------------------------------

    Process ID:   process258294fbc28
    SPID:         650
    Database:     DATABASENAME
    Query:        **DETAILED QUERY**

----------------------------------------

VICTIM LIST
--------------------------------------------------------------------------------

    Process ID:   process261b00cfc28
    SPID:         643
    Database:     DATABASENAME


PROCESS LIST
--------------------------------------------------------------------------------

    Process ID:               process261b00cfc28
    SPID:                     643
    Isolation Level:          read uncommitted (1)
    Logical Reads:            700
    Wait Resource:            KEY: 6:72058005407662080 (fb04e1bc7788)
    Wait Time (ms):           2247
    Transaction:              user_transaction
    Last Transaction Started: 2025-04-08T11:16:07.447
    Client:                   &#039;dbserver_dll &amp;lt;ID@APPSERVER:34784&gt;&#039;
    Hostname:                 APPSERVER
    User:                     USERNAME


EXECUTED SQL STATEMENT
----------------------------------------

    **DETAILED QUERY**


Process Details
================================================================================


    Process ID:               process258294fbc28
    SPID:                     650
    Isolation Level:          read uncommitted (1)
    Logical Reads:            1904
    Wait Resource:            KEY: 6:72057996178358272 (2a9726d2d061)
    Wait Time (ms):           2278
    Transaction:              user_transaction
    Last Transaction Started: 2025-04-08T11:16:07.330
    Client:                   &#039;dbserver_dll &amp;lt;ID@APPSERVER:11664&gt;&#039;
    Hostname:                 APPSERVER
    User:                     USERNAME


EXECUTED SQL STATEMENT
----------------------------------------

    **DETAILED QUERY**


Process Details
================================================================================


RESOURCE LIST
--------------------------------------------------------------------------------

    Resource:
        Type:               keylock
        Database:           6
        Object:             DATABASENAME.dbo.tcprmp300100
        Index:              Itcprmp300100_1a
        Lock Mode:          X
        HOBT ID:            72058005407662080
        Associated Object ID: 72058005407662080

    Owners:
        Process ID:         process258294fbc28, Mode: X

    Waiting Processes:
        Process ID:         process261b00cfc28, Mode: U, Request Type: wait

================================================================================

    Resource:
        Type:               keylock
        Database:           6
        Object:             DATABASENAME.dbo.tcprrp011100
        Index:              Itcprrp011100_1a
        Lock Mode:          X
        HOBT ID:            72057996178358272
        Associated Object ID: 72057996178358272

    Owners:
        Process ID:         process261b00cfc28, Mode: X

    Waiting Processes:
        Process ID:         process258294fbc28, Mode: U, Request Type: wait

================================================================================
</pre></div>


<div style="height:33px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading">Implementation</h3>



<p>It was requested to add the results to the daily report, which we had already implemented with PowerShell. Now, it&#8217;s not great to potentially attach 200 text files to an email every morning, so the PowerShell script returns a table showing how blocks and locks occurred in time period X, and if needed, one can proactively check the folder structure.</p>



<div style="height:33px" aria-hidden="true" class="wp-block-spacer"></div>



<figure class="wp-block-image size-full has-custom-border"><img loading="lazy" decoding="async" width="662" height="223" src="https://dbavonnebenan.de/wp-content/uploads/2025/04/2025-04-28_15-33.png" alt="" class="has-border-color has-base-border-color wp-image-261" style="border-width:1px" srcset="https://dbavonnebenan.de/wp-content/uploads/2025/04/2025-04-28_15-33.png 662w, https://dbavonnebenan.de/wp-content/uploads/2025/04/2025-04-28_15-33-300x101.png 300w" sizes="auto, (max-width: 662px) 100vw, 662px" /></figure>



<div style="height:33px" aria-hidden="true" class="wp-block-spacer"></div>



<p>For dramatic effect and a quick overview, an Excel file is also generated. In this example, there was a nice collateral finding that the DWH jobs had serious problems 🙂</p>



<div style="height:33px" aria-hidden="true" class="wp-block-spacer"></div>



<figure class="wp-block-image size-large has-custom-border is-style-default"><img loading="lazy" decoding="async" width="1024" height="471" src="https://dbavonnebenan.de/wp-content/uploads/2025/04/2025-04-27_17-18-1024x471.png" alt="" class="has-border-color wp-image-254" style="border-color:#000000;border-width:1px;border-radius:5px" srcset="https://dbavonnebenan.de/wp-content/uploads/2025/04/2025-04-27_17-18-1024x471.png 1024w, https://dbavonnebenan.de/wp-content/uploads/2025/04/2025-04-27_17-18-300x138.png 300w, https://dbavonnebenan.de/wp-content/uploads/2025/04/2025-04-27_17-18-768x353.png 768w, https://dbavonnebenan.de/wp-content/uploads/2025/04/2025-04-27_17-18.png 1131w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<div style="height:33px" aria-hidden="true" class="wp-block-spacer"></div>



<p>And just like that, we had a few reports that we could hand over to the vendor, and then things moved very quickly.</p>



<div style="height:24px" aria-hidden="true" class="wp-block-spacer"></div>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: plain; title: ; notranslate">
Die Ordner Struktur

_report/
├── text_reports/
│   └── YYYY/
│       └── MM/
│           └── DD/
│               ├── 20231120_123456_dbname_blocked.txt
│               ├── 20231120_123456_dbname_blocked.xml
│               └── 20231120_123456_dbname_deadlock.txt
└── excel_reports/
    └── BlockReport_20231120_123456.xlsx
</pre></div>


<div style="height:22px" aria-hidden="true" class="wp-block-spacer"></div>



<p><strong><strong>Unfortunately, I experience again and again that application vendors think their customers are stupid and cannot operate the application. Stop doing that and believe your customers sometimes without 101% data, which you can only collect if you already have knowledge of the subject matter.</strong></strong></p>



<div style="height:33px" aria-hidden="true" class="wp-block-spacer"></div>



<h2 class="wp-block-heading">See Repository</h2>



<div style="height:33px" aria-hidden="true" class="wp-block-spacer"></div>


https://github.com/gabrielkoehl/get-snapBlockEvents


<div style="height:33px" aria-hidden="true" class="wp-block-spacer"></div>



<p>The script has various parameters and options, details are in the README.MD. If you have questions, leave them in the comments or contact me.</p>



<div style="height:37px" aria-hidden="true" class="wp-block-spacer"></div>



<h2 class="wp-block-heading">Other</h2>



<p>Erik Darling has also developed a solution to make Extended Events readable, in case other approaches are needed. (<a href="https://github.com/erikdarlingdata/DarlingData/tree/main/sp_HumanEvents">sp_HumanEvents</a>)</p>



<div style="height:74px" aria-hidden="true" class="wp-block-spacer"></div>



<figure class="wp-block-image aligncenter size-full is-resized"><img loading="lazy" decoding="async" width="295" height="294" src="https://dbavonnebenan.de/wp-content/uploads/2025/04/2025-04-21_16-19.png" alt="" class="wp-image-113" style="width:223px;height:auto" srcset="https://dbavonnebenan.de/wp-content/uploads/2025/04/2025-04-21_16-19.png 295w, https://dbavonnebenan.de/wp-content/uploads/2025/04/2025-04-21_16-19-150x150.png 150w" sizes="auto, (max-width: 295px) 100vw, 295px" /></figure>



<p class="has-text-align-center"><strong>Gabriel, der DBAvonNebenan</strong></p>



<p></p><p>The post <a href="https://dbavonnebenan.de/reporting-sql-server-blocks-en/">Reporting Blocks and Deadlocks: When the 3rd-Party Vendor Demands Evidence</a> first appeared on <a href="https://dbavonnebenan.de">DBA von Nebenan</a>.</p>]]></content:encoded>
					
					<wfw:commentRss>https://dbavonnebenan.de/reporting-sql-server-blocks-en/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
	</channel>
</rss>
