<?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>Paul S. Randal</title>
	<atom:link href="https://www.sqlskills.com/blogs/paul/feed/" rel="self" type="application/rss+xml" />
	<link>https://www.sqlskills.com/blogs/paul/</link>
	<description>In Recovery...</description>
	<lastBuildDate>Wed, 21 Oct 2026 03:34:49 +0000</lastBuildDate>
	<language>en-US</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=7.0.7</generator>
	<item>
		<title>AI Stories and Thoughts</title>
		<link>https://www.sqlskills.com/blogs/paul/ai-stories-and-thoughts/</link>
					<comments>https://www.sqlskills.com/blogs/paul/ai-stories-and-thoughts/#comments</comments>
		
		<dc:creator><![CDATA[Paul Randal]]></dc:creator>
		<pubDate>Tue, 20 Oct 2026 19:19:11 +0000</pubDate>
				<category><![CDATA[AI]]></category>
		<category><![CDATA[Career]]></category>
		<guid isPermaLink="false">https://www.sqlskills.com/blogs/paul/?p=5394</guid>

					<description><![CDATA[<p>iAI is obviously the hot new thing and prognostications for the future range from the unicorns-and-rainbows life of idleness for us all to the post-apocalyptic killer robots hunting down the cowering survivors of mankind a la Terminator 2. I confidently predict that immediate reality will be somewhere in between &#x1f60a; As we’ve become more permanent [&#8230;]</p>
<p>The post <a href="https://www.sqlskills.com/blogs/paul/ai-stories-and-thoughts/">AI Stories and Thoughts</a> appeared first on <a href="https://www.sqlskills.com/blogs/paul">Paul S. Randal</a>.</p>
]]></description>
										<content:encoded><![CDATA[		<div data-elementor-type="wp-post" data-elementor-id="5394" class="elementor elementor-5394" data-elementor-post-type="post">
						<section class="elementor-section elementor-top-section elementor-element elementor-element-27f0d0c elementor-section-boxed elementor-section-height-default elementor-section-height-default" data-id="27f0d0c" data-element_type="section" data-e-type="section">
						<div class="elementor-container elementor-column-gap-default">
					<div class="elementor-column elementor-col-100 elementor-top-column elementor-element elementor-element-7c60997" data-id="7c60997" data-element_type="column" data-e-type="column">
			<div class="elementor-widget-wrap elementor-element-populated">
						<div class="elementor-element elementor-element-39df5e2 elementor-widget elementor-widget-text-editor" data-id="39df5e2" data-element_type="widget" data-e-type="widget" data-widget_type="text-editor.default">
				<div class="elementor-widget-container">
									<p style="text-align: justify;">iAI is obviously the hot new thing and prognostications for the future range from the unicorns-and-rainbows life of idleness for us all to the post-apocalyptic killer robots hunting down the cowering survivors of mankind a la Terminator 2. I confidently predict that immediate reality will be somewhere in between <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;" /></p><p style="text-align: justify;">As we’ve become more permanent here in Camp Savage, I’ve been working on our network infrastructure. It’s pretty sophisticated and pretty complex. It’s all Unifi gear, with multiple VLANs, Wi-Fi networks, VPNs, NAS, long radio links, runs of fiber, and myriad APs, switches, sensors, and IoT clients. There’s a lot to configure and keep track of and I’ve learned, relearned, and remembered a ton in the last few months.</p><p style="text-align: justify;">Although Unifi gear is really top-notch when it comes to interoperability of all the hardware in their range, there are lots of gotchas with configuration, physical connections, and other networking esoteria. Whenever I’ve become stuck, I’ve turned, of course, to Google to search for help. Its AI Overview has been mostly invaluable for cutting through the tedium of watching YouTube videos and scouring blogs for answers. But occasionally it’s taken me on wild goose chases because I didn’t know any better.</p><p style="text-align: justify;">The easiest example to explain is when I had random client disconnects happening in one building only. It took me down the path of isolating the most twitchy client onto its own network, and changing all kinds of parameters for hours to try to stabilize the connection. After several days of trying lots of things and seeing if overnight performance worked, someone on a forum suggested a totally different path. They asked what’s the connection to this building? Fiber. Any fiber errors in the Unifi logs? Yes! Upshot: I had multi-mode fiber SFP adapters with single-mode fiber cable. Swapping out the adapters fixed the problem instantly.</p><p style="text-align: justify;">AI wasted my time because it didn’t have any way to think that far outside the box, because it hadn’t encountered that explanation or diagnostic step. Maybe a more powerful AI would have, but it took a human to actually point me down the right path.</p><p style="text-align: justify;">The most frustrating examples were the many times I asked for steps to config X or Y and it confidently gave me steps and screenshots based on UIs or hardware from several versions ago, even when prompted with the exact version being used.</p><p style="text-align: justify;">And over to SQL Server…</p><p style="text-align: justify;">Jonathan has encountered several cases of long-term clients introducing AI developer tools that have instructed devs to make changes that have crippled their systems in production because the tools just did not get the complexity of the environment and made assumptions. Wrong assumptions, costing the client time and consulting dollars to identify and put right.</p><p style="text-align: justify;">These devs blindly followed the advice of their expensive AI tools without understanding what they were doing and the possible ramifications and got bitten hard. If you’re trying to understand a performance problem and just blindly trust the AI tool to diagnose and propose a fix, it might get it right. It might get it wrong. Will you understand what it did and why?</p><p style="text-align: justify;">And now more specifically to my specialty and my upcoming precon at PASS in a few weeks: internals, how stuff works, and wait statistics.</p><p style="text-align: justify;">Mangling the well-known proverb: you can’t keep just giving fish to someone otherwise they get further and further from being able to fish themselves, and eventually when the fish-provider isn’t there, or if the fish are no longer trusted to be edible, how does the person eat?</p><p style="text-align: justify;">Don’t stop learning. Don’t rely on tools that try to do your job. Absolutely use AI, but only as a tool to help guide you. Be sure to do further research or ask for references / links / sources, and verify them!  Don’t give up your ability to understand SQL Server; it’s what will separate you from the rest and keep your servers productive.</p><p style="text-align: justify;">Thoughts?</p>								</div>
				</div>
					</div>
		</div>
					</div>
		</section>
				</div>
		<p>The post <a href="https://www.sqlskills.com/blogs/paul/ai-stories-and-thoughts/">AI Stories and Thoughts</a> appeared first on <a href="https://www.sqlskills.com/blogs/paul">Paul S. Randal</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.sqlskills.com/blogs/paul/ai-stories-and-thoughts/feed/</wfw:commentRss>
			<slash:comments>1</slash:comments>
		
		
			</item>
		<item>
		<title>Reflections on 15 Years</title>
		<link>https://www.sqlskills.com/blogs/paul/reflections-on-15-years/</link>
					<comments>https://www.sqlskills.com/blogs/paul/reflections-on-15-years/#comments</comments>
		
		<dc:creator><![CDATA[Paul Randal]]></dc:creator>
		<pubDate>Tue, 11 Aug 2026 04:10:16 +0000</pubDate>
				<category><![CDATA[Personal]]></category>
		<guid isPermaLink="false">https://www.sqlskills.com/blogs/paul/?p=5391</guid>

					<description><![CDATA[<p>(This was the editorial for my 300th SQLskills Insider newsletter last week, that I&#8217;ve been producing since March 2011! See bottom of browser window to subscribe.) I was torn between a “reflections on 15 years” and a “mental health” ponderings for the 300th edition, so here’s a bit of both – blasted out in a [&#8230;]</p>
<p>The post <a href="https://www.sqlskills.com/blogs/paul/reflections-on-15-years/">Reflections on 15 Years</a> appeared first on <a href="https://www.sqlskills.com/blogs/paul">Paul S. Randal</a>.</p>
]]></description>
										<content:encoded><![CDATA[		<div data-elementor-type="wp-post" data-elementor-id="5391" class="elementor elementor-5391" data-elementor-post-type="post">
						<section class="elementor-section elementor-top-section elementor-element elementor-element-b9bd0f8 elementor-section-boxed elementor-section-height-default elementor-section-height-default" data-id="b9bd0f8" data-element_type="section" data-e-type="section">
						<div class="elementor-container elementor-column-gap-default">
					<div class="elementor-column elementor-col-100 elementor-top-column elementor-element elementor-element-5b9d740" data-id="5b9d740" data-element_type="column" data-e-type="column">
			<div class="elementor-widget-wrap elementor-element-populated">
						<div class="elementor-element elementor-element-f4e5f1a elementor-widget elementor-widget-text-editor" data-id="f4e5f1a" data-element_type="widget" data-e-type="widget" data-widget_type="text-editor.default">
				<div class="elementor-widget-container">
									<p style="text-align: justify;"><em>(This was the editorial for my 300th SQLskills Insider newsletter last week, that I&#8217;ve been producing since March 2011! See bottom of browser window to subscribe.)</em></p><p style="text-align: justify;">I was torn between a “reflections on 15 years” and a “mental health” ponderings for the 300<sup>th</sup> edition, so here’s a bit of both – blasted out in a half-hour brain dump.</p><p style="text-align: justify;">The world has become a very noisy, uncaring, impolite place. I have misophonia, a very low tolerance for nonsense (in a less polite setting I’d say b***s**t or f***ery), and a deep, deep dislike of phones, small-talk, or interruptions. So all these things, that have become ubiquitous over the last 15 years, are most irritating:</p><ul><li>Cell phones that ‘ding’ constantly</li><li>People having speakerphone conversations in stores, planes, restaurants</li><li>Constant spam texts and calls – the FTC do-not-call list is a joke</li><li>Sensationalist news</li><li>People in such a rush that they tail-gate, weave through traffic, and generally drive dangerously</li><li>Anything to do with our dysfunctional politics (and likely in your country too, if not in the US)</li><li>Entitlement and inconsideration when traveling</li><li>Neighbors letting dogs bark, bark, bark, bark at all times of day and evening</li><li>The seeming lack of pride and professionalism in many service industries</li><li>People letting their kids run wild in restaurants</li><li>The disappearance of basic politeness like saying please and thank you</li><li>And more</li></ul><p style="text-align: justify;">Am I just an angry old (oldish, ok? only 54!) man? No, I’ve been writing about some of these things since 2011 (see <a href="https://www.sqlskills.com/blogs/paul/the-golden-rule-maybe-just-optional-now/">https://www.sqlskills.com/blogs/paul/the-golden-rule-maybe-just-optional-now/</a>). But I am much more intolerant and now that I’ve slowed down since the pandemic I notice these things a lot more.</p><p style="text-align: justify;">And they grate and grind and oppress. Which is why I enjoy being at Camp Savage so much – no people, noise, traffic, travel – just lots of nature. Am I wandering off into the woods to bury my head in the sand and live in a shack? Well, kind of lol! Part of our Camp Savage move is to get out of the city, the rat-race, the noise and pollution, and live a healthier, happier life with lots of open space, animals, greenhouses, and so on.</p><p style="text-align: justify;">That’s the mental health part. Well, and stopping drinking 18 months ago – that was life-changing, obviously. It’s taken 18 months for my brain to rewire itself.</p><p style="text-align: justify;">What about SQL Server? Well, if you think about the bits that I’m interested in, not a whole lot has changed. Sure, there are new, impactful features like columnstore, AGs, query store , ADR, but when it comes down to it, what I’m interested in, consult on, and teach hasn’t really changed. People still need to:</p><ul><li>Figure out where performance is a problem and then fix it</li><li>Have a disaster recovery plan that works</li><li>Implement high availability</li><li>Understand some of how the engine works</li><li>Use indexes correctly</li><li>Communicate well (by far the most popular of all our Pluralsight courses!)</li></ul><p style="text-align: justify;">I’m often asked if I’m concerned about cloud or AI meaning DBAs will be out of a job or SQLskills will become irrelevant. We have many clients who have no interest in Azure (either from security, performance, scalability, or just basic trust issues) . We also have a bunch who do use the cloud in some way, but it’s not going to become a zero-on-prem game. AI is in no way ready to replace DBAs – plenty of stories out there of AI doing the wrong thing, the explicitly ‘do not do that’ thing, or going rogue. And AIs are only as good as what they’re trained on and who they’re trained by. I’ve seen some horrible answers to test questions I’ve asked AI engines.</p><p style="text-align: justify;">So my answer is a big, fat ‘no’. Not concerned. We’re still here, and hopefully will still be for many years to come.</p><p style="text-align: justify;">Quietly <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;" /></p><p style="text-align: justify;">PS One reader, John Ness, wrote this excellent description of DBAs (reproduced and attributed with permission):</p><p style="text-align: justify;">We don&#8217;t seek the limelight (well not a lot of us) in our everyday jobs. We are just there constantly taking care of the hundreds or thousands of databases (in my case 1200) and ensuring that everything keeps running, that the users can expect constant peak performance, that the backups and restores work, and that the databases are in a consistent state. We don&#8217;t shine like a bright light; we glow in the dark.</p>								</div>
				</div>
					</div>
		</div>
					</div>
		</section>
				</div>
		<p>The post <a href="https://www.sqlskills.com/blogs/paul/reflections-on-15-years/">Reflections on 15 Years</a> appeared first on <a href="https://www.sqlskills.com/blogs/paul">Paul S. Randal</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.sqlskills.com/blogs/paul/reflections-on-15-years/feed/</wfw:commentRss>
			<slash:comments>3</slash:comments>
		
		
			</item>
		<item>
		<title>Presenting a workshop at PASS Summit West in Seattle!</title>
		<link>https://www.sqlskills.com/blogs/paul/presenting-a-workshop-at-pass-summit-west-in-seattle/</link>
					<comments>https://www.sqlskills.com/blogs/paul/presenting-a-workshop-at-pass-summit-west-in-seattle/#respond</comments>
		
		<dc:creator><![CDATA[Paul Randal]]></dc:creator>
		<pubDate>Tue, 07 Jul 2026 18:34:43 +0000</pubDate>
				<category><![CDATA[Community]]></category>
		<category><![CDATA[Conferences]]></category>
		<guid isPermaLink="false">https://www.sqlskills.com/blogs/paul/?p=5383</guid>

					<description><![CDATA[<p>I’m excited to be presenting at the Pass Summit in November! My pre-con workshop is my absolute favorite to teach Performance Tuning with Waits and Latches and I’ll also be presenting a session on Advanced Data Recovery Techniques. Bonus is that I’ll have Kimberly with me! All the details are available on the PASS website [&#8230;]</p>
<p>The post <a href="https://www.sqlskills.com/blogs/paul/presenting-a-workshop-at-pass-summit-west-in-seattle/">Presenting a workshop at PASS Summit West in Seattle!</a> appeared first on <a href="https://www.sqlskills.com/blogs/paul">Paul S. Randal</a>.</p>
]]></description>
										<content:encoded><![CDATA[<p>I’m excited to be presenting at the Pass Summit in November! My pre-con workshop is my absolute favorite to teach <em>Performance Tuning with Waits and Latches</em> and I’ll also be presenting a session on <em>Advanced Data Recovery Techniques</em>. Bonus is that I’ll have Kimberly with me!</p>
<p>All the details are available on the PASS website about what <a href="https://passdatacommunitysummit.com/west/pre-conference-speakers/#speaker-paul-randal" target="_blank" rel="noopener">I&#8217;m presenting</a>.</p>
<p>See you there!</p>
<p>The post <a href="https://www.sqlskills.com/blogs/paul/presenting-a-workshop-at-pass-summit-west-in-seattle/">Presenting a workshop at PASS Summit West in Seattle!</a> appeared first on <a href="https://www.sqlskills.com/blogs/paul">Paul S. Randal</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.sqlskills.com/blogs/paul/presenting-a-workshop-at-pass-summit-west-in-seattle/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Summer Sale: $699 Blackbelt Bundle!</title>
		<link>https://www.sqlskills.com/blogs/paul/summer-sale-699-blackbelt-bundle/</link>
					<comments>https://www.sqlskills.com/blogs/paul/summer-sale-699-blackbelt-bundle/#respond</comments>
		
		<dc:creator><![CDATA[Paul Randal]]></dc:creator>
		<pubDate>Tue, 23 Jun 2026 18:37:41 +0000</pubDate>
				<category><![CDATA[Career]]></category>
		<category><![CDATA[Training]]></category>
		<guid isPermaLink="false">https://www.sqlskills.com/blogs/paul/?p=5380</guid>

					<description><![CDATA[<p>Summer Sale time! Hundreds of dollars lower-than-ever prices on our signature 158-hour Blackbelt training bundle: US$699 for one-year access (or to upgrade) &#8211; $200 lower than ever before! &#8211; US$1,299 for Lifetime access (no expiration) &#8211; $300 lower than ever before! Both prices include the 2022 updates and Q&#38;A sessions. If you have a one-year [&#8230;]</p>
<p>The post <a href="https://www.sqlskills.com/blogs/paul/summer-sale-699-blackbelt-bundle/">Summer Sale: $699 Blackbelt Bundle!</a> appeared first on <a href="https://www.sqlskills.com/blogs/paul">Paul S. Randal</a>.</p>
]]></description>
										<content:encoded><![CDATA[<p><a href="https://www.sqlskills.com/blogs/paul/wp-content/uploads/2021/01/blackbelt-sqlserver2019-7stars.jpg"><img decoding="async" class="alignnone wp-image-5180 size-thumbnail" src="https://www.sqlskills.com/blogs/paul/wp-content/uploads/2021/01/blackbelt-sqlserver2019-7stars-150x150.jpg" alt="" width="150" height="150" /></a><a href="https://www.sqlskills.com/blogs/paul/wp-content/uploads/2021/01/blackbelt-sqlserver2019-7stars.jpg"><img decoding="async" class="alignnone wp-image-5180 size-thumbnail" src="https://www.sqlskills.com/blogs/paul/wp-content/uploads/2021/01/blackbelt-sqlserver2019-7stars-150x150.jpg" alt="" width="150" height="150" /></a><a href="https://www.sqlskills.com/blogs/paul/wp-content/uploads/2021/01/blackbelt-sqlserver2019-7stars.jpg"><img decoding="async" class="alignnone wp-image-5180 size-thumbnail" src="https://www.sqlskills.com/blogs/paul/wp-content/uploads/2021/01/blackbelt-sqlserver2019-7stars-150x150.jpg" alt="" width="150" height="150" /></a><a href="https://www.sqlskills.com/blogs/paul/wp-content/uploads/2021/01/blackbelt-sqlserver2019-7stars.jpg"><img decoding="async" class="alignnone wp-image-5180 size-thumbnail" src="https://www.sqlskills.com/blogs/paul/wp-content/uploads/2021/01/blackbelt-sqlserver2019-7stars-150x150.jpg" alt="" width="150" height="150" /></a><a href="https://www.sqlskills.com/blogs/paul/wp-content/uploads/2021/01/blackbelt-sqlserver2019-7stars.jpg"><img decoding="async" class="alignnone wp-image-5180 size-thumbnail" src="https://www.sqlskills.com/blogs/paul/wp-content/uploads/2021/01/blackbelt-sqlserver2019-7stars-150x150.jpg" alt="" width="150" height="150" /></a></p>
<p style="text-align: justify;">Summer Sale time!</p>
<p style="text-align: justify;">Hundreds of dollars lower-than-ever prices on our signature 158-hour Blackbelt training bundle:</p>
<ul>
<li>US$699 for <a href="https://www.sqlskills.com/product/sqlskills-blackbelt-bundle/" target="_blank" rel="noopener">one-year access</a> (or to upgrade) &#8211; $200 lower than ever before!</li>
<li>&#8211; US$1,299 for <a href="https://www.sqlskills.com/product/bbbct-lifetime/" target="_blank" rel="noopener">Lifetime access</a> (no expiration) &#8211; $300 lower than ever before!</li>
</ul>
<p style="text-align: justify;">Both prices include the 2022 updates and Q&amp;A sessions.</p>
<p style="text-align: justify;">If you have a one-year subscription and would like to upgrade it to Lifetime, purchase a second one-year and I’ll remove the expiration dates and make sure you get the 2022 Q&amp;As/Updates recordings with Lifetime access for free. It doesn’t matter if your original subscription has expired!</p>
<p style="text-align: justify;">Lifetime subscribers will also get the upcoming re-record of IECAG (clustering and AGs) for SQL Server 2025 for free, and Kimberly’s upcoming IESP (stored proc performance) for free.</p>
<p style="text-align: justify;">See our <a href="https://www.sqlskills.com/shop/" target="_blank" rel="noopener">shop</a> for details and <a href="mailto:paul@sqlskills.com?subject=December%20sale%20question%20from%20blog" target="_blank" rel="noopener">let me know</a> of any questions.</p>
<p style="text-align: justify;">Enjoy!</p>
<p>The post <a href="https://www.sqlskills.com/blogs/paul/summer-sale-699-blackbelt-bundle/">Summer Sale: $699 Blackbelt Bundle!</a> appeared first on <a href="https://www.sqlskills.com/blogs/paul">Paul S. Randal</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.sqlskills.com/blogs/paul/summer-sale-699-blackbelt-bundle/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>The SQL Server Transaction Log, Part 4: Log Records</title>
		<link>https://www.sqlskills.com/blogs/paul/the-sql-server-transaction-log-part-4-log-records/</link>
					<comments>https://www.sqlskills.com/blogs/paul/the-sql-server-transaction-log-part-4-log-records/#comments</comments>
		
		<dc:creator><![CDATA[Paul Randal]]></dc:creator>
		<pubDate>Tue, 16 Jun 2026 19:44:30 +0000</pubDate>
				<category><![CDATA[Transaction Log]]></category>
		<guid isPermaLink="false">https://www.sqlskills.com/blogs/paul/?p=5378</guid>

					<description><![CDATA[<p>(This post first appeared on SQLperformance.com four years ago as part of a blog series, before that website was mothballed later in 2022 and the series was curtailed. Reposted here with permission, with a few tweaks.) Previously I’ve talked about why logging is required and the architecture and circular nature of the log, so now [&#8230;]</p>
<p>The post <a href="https://www.sqlskills.com/blogs/paul/the-sql-server-transaction-log-part-4-log-records/">The SQL Server Transaction Log, Part 4: Log Records</a> appeared first on <a href="https://www.sqlskills.com/blogs/paul">Paul S. Randal</a>.</p>
]]></description>
										<content:encoded><![CDATA[<p style="text-align: justify;"><em>(This post first appeared on SQLperformance.com four years ago as part of a blog series, before that website was mothballed later in 2022 and the series was curtailed. Reposted here with permission, with a few tweaks.)</em></p>
<p style="text-align: justify;">Previously I’ve talked about <a href="https://www.sqlskills.com/blogs/paul/the-sql-server-transaction-log-part-1-logging-basics/" target="_blank" rel="noopener">why logging is required</a> and the <a href="https://www.sqlskills.com/blogs/paul/the-sql-server-transaction-log-part-2-log-architecture/" target="_blank" rel="noopener">architecture</a> and <a href="https://www.sqlskills.com/blogs/paul/the-sql-server-transaction-log-part-3-the-circular-nature-of-the-log/" target="_blank" rel="noopener">circular nature of the log</a>, so now it’s time to look at the real heart of the logging system—the log records themselves. As always, I recommend you read the previous posts in the series before reading this one.</p>
<h2 style="text-align: justify;">What Are Log Records?</h2>
<p style="text-align: justify;">The simplest definition of a log record: it describes a single <em>change</em> to a database. A single <em>operation</em> in the database may cause multiple<em> changes</em>, but each change will usually have its own log record to describe it. An example of this is updating a column in a single row—it will do the following:</p>
<ul style="text-align: justify;">
<li>Perform the update, which generates one log record.</li>
<li>If it’s the first time the extent of the page (containing the updated row) is part of what has been changed since the most recent full backup, a bit must be set in the relevant differential bitmap, which generates another log record.</li>
</ul>
<p style="text-align: justify;">Each log record has a fixed overhead, so sometimes, the Storage Engine breaks this rule by combining multiple changes into a single log record to reduce the number of log records written to the log. Some examples of this are:</p>
<ul style="text-align: justify;">
<li>When an update happens to multiple columns in a single table row, a single log record can contain all the column changes for one row instead of one log record per column change. Note if any nonclustered indexes need to be updated as well, those changes will be in separate log records.</li>
<li>When an index is being built or rebuilt, the Storage Engine will fill an entire page with rows and then use a single log record to log the whole page image instead of one log record per row inserted into the new page.</li>
</ul>
<p style="text-align: justify;">Each log record has a Log Sequence Number (LSN), which I defined back in part two as &lt;VLF sequence number&gt;:&lt;log block ID&gt;:&lt;log record ID&gt; (4 bytes: 4 bytes: 2 bytes). Each LSN is unique, is ever increasing, and identifies the physical location of a log record within the log. Log records never move in the log, so their LSNs never change once created. The page header for each data file page in the database contains the LSN of the most recent log record describing a change to the page and is used during crash recovery, which I’ll cover in a future post.</p>
<h2 style="text-align: justify;">Log Record Contents</h2>
<p style="text-align: justify;">You can think of a log record as similar to a table record—it has a defined structure comprised of a number of fields. All log records share a common header with metadata about the log record and may then have varying additional fields depending on the log record type. Some of the header fields include:</p>
<ul style="text-align: justify;">
<li>The log record LSN</li>
<li>The log record type</li>
<li>The change context, if any (e.g., heap or clustered index)</li>
<li>The transaction ID the change is part of, if any. Some log records are non-transactional and can’t be rolled back—for instance, changes to PFS pages and differential bitmaps</li>
<li>The length of the log record in bytes</li>
<li>The LSN of the previous log record generated by the transaction, if any</li>
<li>The amount of free space reserved in the log in case the log record needs to be rolled back</li>
</ul>
<p style="text-align: justify;">The log manager must always ensure there’s enough free space in the log to generate any log records required to roll back a transaction without having to grow the size of the log, making log space reservation critical. If this were not the case, and a transaction rollback required the log to grow, which then failed (e.g., out of space on the log drive), the database must be set offline in the SUSPECT state. You can read a bit more about rollbacks in my article <a href="https://www.sqlskills.com/blogs/paul/sql101-introduction-to-sql-server-transactions/" target="_blank" rel="noopener"><em>SQL101: Introduction to SQL Server Transactions</em></a>, and I’ll cover them in depth in the next article.</p>
<p style="text-align: justify;">Log records describing changes to table/index pages also include:</p>
<ul style="text-align: justify;">
<li>The allocation unit ID that owns the page</li>
<li>The page ID and the slot ID of the row being changed</li>
<li>The key values of the row being changed, if any</li>
<li>A list of what locks were held to protect the change</li>
<li>The offset of the change in the row</li>
<li>The before and after images of the change, or an array of before and after images if multiple parts of the row are changed</li>
</ul>
<p style="text-align: justify;">The before and after images are the set of bytes changed and what they were changed to, respectively, and these allow the change to be rolled back or replayed. Some log records only include the after image since they can’t be rolled back, making the before image unnecessary—for example, a log record generated as part of rolling back another log record or a non-transactional log record.</p>
<p style="text-align: justify;">Many more fields may be present depending on the type of log record, and we’ll encounter some of them as we progress through the rest of this series.</p>
<h2 style="text-align: justify;">Log Record Types</h2>
<p style="text-align: justify;">There are between 70 and 100 log record types, depending on the version of SQL Server (there’s no documented list). All log record names start with LOP, which stands for Log Operation, and some common types include:</p>
<ul style="text-align: justify;">
<li>LOP_FORMAT_PAGE – creating a new page image</li>
<li>LOP_MODIFY_ROW – modifying a row from 1 to 16 contiguous bytes, potentially involving multiple adjacent columns</li>
<li>LOP_MODIFY_COLUMNS – modifying multiple disjoint columns in a row</li>
<li>LOP_SET_BITS – setting some bits in an allocation bitmap to 1 or 0</li>
<li>LOP_INSERT_ROWS – inserting one or more rows on a page</li>
<li>LOP_DELETE_ROWS – deleting one or more rows on a page</li>
<li>LOP_EXPUNGE_ROWS – used by the ghost cleanup task when removing deleted rows from a page</li>
<li>LOP_BEGIN_XACT – always the first log record generated by a transaction</li>
<li>LOP_COMMIT_XACT – the log record signifying a transaction has successfully committed</li>
<li>LOP_ABORT_XACT – the log record signifying a transaction has finished being rolled back</li>
</ul>
<h2 style="text-align: justify;">Examining Log Records</h2>
<p style="text-align: justify;">The way to examine log records is via the undocumented <em>fn_dblog </em>function. It requires two LSN parameters specifying the starting and ending points in the log, but I seldom use those and instead specify NULL for both, which means scan all available log records in active VLFs. The great thing about this function is it lets you limit which fields it outputs (otherwise, you get all 130), and you can specify predicates.</p>
<p style="text-align: justify;">Note:</p>
<ol style="text-align: justify;">
<li>If you want to examine all possible log records, including those in VLFs marked inactive by log truncation, you can enable trace flag 2537.</li>
<li>If you’re playing around and want to see what log records an operation does, use a test database in simple recovery mode, and issue a checkpoint to truncate the log. This means you can run your operation, and then fn_dblog will only show you the log records since the checkpoint.</li>
</ol>
<p style="text-align: justify;">Let’s look at a simple example. I’ve created a database, a table with a single integer column, inserted one row, and performed a backup and a checkpoint. Now I’ll do the following:</p>
<pre style="text-align: justify;">UPDATE TestTable SET Column1 = 2;
GO

SELECT
    [Current LSN],
    [Operation],
    [Context],
    [Transaction ID],
    [Log Record Length]
FROM fn_dblog (NULL, NULL)
WHERE [Operation] NOT LIKE '%CKPT';
GO</pre>
<pre style="text-align: justify;">Current LSN             Operation        Context       Transaction ID  Log Record Length
----------------------  ---------------  ------------  --------------  -----------------
00000030:000000af:0001  LOP_BEGIN_XACT   LCX_NULL      0000:000004be   124
00000030:000000af:0002  LOP_SET_BITS     LCX_DIFF_MAP  0000:00000000   56
00000030:000000af:0003  LOP_MODIFY_ROW   LCX_HEAP      0000:000004be   104
00000030:000000af:0004  LOP_COMMIT_XACT  LCX_NULL      0000:000004be   84</pre>
<p style="text-align: justify;">The LSN is in hex, and you can see all four log records for the transaction are in the same log block (the 000000AF). Here’s what we’re seeing:</p>
<ol style="text-align: justify;">
<li>The transaction begins with the LOP_BEGIN_XACT with transaction ID 000004be and no context.</li>
<li>As the update will change an extent after a backup, the differential bitmap bit for it must be set, using the LOP_SET_BITS with the context of LCX_DIFF_MAP. Notice the log record doesn’t have a transaction ID as diff map changes are non-transactional (i.e. they are never rolled back)</li>
<li>The actual modification is logged with the LOP_MODIFY_ROW with context LCX_HEAP (because the table is a heap) and is part of the transaction.</li>
<li>The transaction ends with the LOP_COMMIT_XACT.</li>
</ol>
<p style="text-align: justify;">It can be fascinating to spelunk around in the log to see what SQL Server is doing for some operations. You might think this doesn’t have any use in a production environment, but here’s a <a href="https://www.sqlskills.com/blogs/paul/using-fn_dblog-fn_dump_dblog-and-restoring-with-stopbeforemark-to-an-lsn/">post</a> showing the function being utilized (and the <em>fn_dump_dblog</em> function for examining log records in backups) to find out who performed a DROP TABLE and the restore point to get it back.</p>
<h2 style="text-align: justify;">Until Next Time…</h2>
<p style="text-align: justify;">Since I’ve now laid the groundwork for understanding log records, in the next post of this series, I’ll show some deeper examples, including how transaction rollback works. And feel free to post a comment with any questions you might have.</p>
<p>The post <a href="https://www.sqlskills.com/blogs/paul/the-sql-server-transaction-log-part-4-log-records/">The SQL Server Transaction Log, Part 4: Log Records</a> appeared first on <a href="https://www.sqlskills.com/blogs/paul">Paul S. Randal</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.sqlskills.com/blogs/paul/the-sql-server-transaction-log-part-4-log-records/feed/</wfw:commentRss>
			<slash:comments>1</slash:comments>
		
		
			</item>
		<item>
		<title>SQL101: Application data consistency checking</title>
		<link>https://www.sqlskills.com/blogs/paul/sql101-application-data-consistency-checking/</link>
					<comments>https://www.sqlskills.com/blogs/paul/sql101-application-data-consistency-checking/#respond</comments>
		
		<dc:creator><![CDATA[Paul Randal]]></dc:creator>
		<pubDate>Thu, 23 Apr 2026 16:36:54 +0000</pubDate>
				<category><![CDATA[Corruption]]></category>
		<category><![CDATA[Disaster Recovery]]></category>
		<category><![CDATA[SQL101]]></category>
		<guid isPermaLink="false">https://www.sqlskills.com/blogs/paul/?p=5375</guid>

					<description><![CDATA[<p>It&#8217;s quite common for a company that experiences a corruption-causing disaster, but has no valid backups to restore from and no ability to fail over to a redundant secondary, to just run repair and then immediately start running in production again. &#60;Imagine there&#8217;s a clever GIF here combining OH NO! UH OH! OMG! SMH! NOOO!&#62; [&#8230;]</p>
<p>The post <a href="https://www.sqlskills.com/blogs/paul/sql101-application-data-consistency-checking/">SQL101: Application data consistency checking</a> appeared first on <a href="https://www.sqlskills.com/blogs/paul">Paul S. Randal</a>.</p>
]]></description>
										<content:encoded><![CDATA[<p style="text-align: justify;">It&#8217;s quite common for a company that experiences a corruption-causing disaster, but has no valid backups to restore from and no ability to fail over to a redundant secondary, to just run repair and then immediately start running in production again.</p>
<p style="text-align: justify;">&lt;Imagine there&#8217;s a clever GIF here combining OH NO! UH OH! OMG! SMH! NOOO!&gt;</p>
<p style="text-align: justify;">Over the years I’ve taught countless classes and conference sessions that talk about corruption and how it happens, how to run consistency checks, using <em>DBCC CHECKDB</em>, and running repair. When I’m talking about repair, I explain that if you have to run <em>REPAIR_ALLOW_DATA_LOSS</em> and it actually deletes data records, you need to reinitialize any affected replication topologies, and validate any affected constraints. This is all documented in Books Online, but that doesn’t mean that people read it and know that – lol!</p>
<p style="text-align: justify;">What *isn’t* in Books Online, and I make sure the attendees realize, is that repair is all about making the database structurally consistent – it doesn’t know anything about the data relationships between tables (either protected by constraints or just inherent in the schema design). This means that after a data-losing repair, the data relationships in the database may well be broken.</p>
<p style="text-align: justify;">I then always ask the audience: “How many of you have an application data-consistency checker that you can run to validate all the business rules and relationships that the application depends on? In fact, how many of you even have a way to test that the application is working correctly after running a repair or deploying new code?”</p>
<p style="text-align: justify;">Every time I ask, I might (rarely) get one or two hands go up, out of a class of 30 or a session of 50 or more. I’ve never had more than two, and usually I don’t get any.</p>
<p style="text-align: justify;">It’s just not something people think about. They assume that if repair runs correctly then they can carry on and everything will work. No.</p>
<p style="text-align: justify;"><strong>Call to action:</strong></p>
<p style="text-align: justify;">You really should have some way to validate that your application is running on correct data. Otherwise, in the best case, it will fail, but in the worst case, it will continue running erroneously – maybe with wrong results that affect your business.</p>
<p style="text-align: justify;">This basically means codifying the required relationships in constraints and/or some code that verifies the required relationships (if they can’t be expressed as relational constraints) are correct.</p>
<p style="text-align: justify;">If you have a third-party application, it might be hard or impossible to persuade the vendor to provide such a tool, especially if they specifically don’t support running repair on their database.</p>
<p style="text-align: justify;">Alternative: have a bullet=;proof baclup-and-restore strategy and/or failover solution.</p>
<p style="text-align: justify;"><strong>Bottom line:</strong> Your business depends on the applications running in the data tier, and you need to make sure, as much as you can, that they’re running on correct data.</p>
<p>The post <a href="https://www.sqlskills.com/blogs/paul/sql101-application-data-consistency-checking/">SQL101: Application data consistency checking</a> appeared first on <a href="https://www.sqlskills.com/blogs/paul">Paul S. Randal</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.sqlskills.com/blogs/paul/sql101-application-data-consistency-checking/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>SQL101: Phishing attacks</title>
		<link>https://www.sqlskills.com/blogs/paul/sql101-phishing-attacks/</link>
					<comments>https://www.sqlskills.com/blogs/paul/sql101-phishing-attacks/#respond</comments>
		
		<dc:creator><![CDATA[Paul Randal]]></dc:creator>
		<pubDate>Thu, 02 Apr 2026 19:34:42 +0000</pubDate>
				<category><![CDATA[Security]]></category>
		<category><![CDATA[SQL101]]></category>
		<guid isPermaLink="false">https://www.sqlskills.com/blogs/paul/?p=5371</guid>

					<description><![CDATA[<p>I thought it was bad previously, but in 2026 I&#8217;ve noticed a big rise in emails trying to scam me into clicking a link &#8211; known as phishing. Phishing describes an email that entices the recipient to open it and maybe click a link, which then installs some malware on the computer. This could be [&#8230;]</p>
<p>The post <a href="https://www.sqlskills.com/blogs/paul/sql101-phishing-attacks/">SQL101: Phishing attacks</a> appeared first on <a href="https://www.sqlskills.com/blogs/paul">Paul S. Randal</a>.</p>
]]></description>
										<content:encoded><![CDATA[<p style="text-align: justify;">I thought it was bad previously, but in 2026 I&#8217;ve noticed a big rise in emails trying to scam me into clicking a link &#8211; known as phishing. Phishing describes an email that entices the recipient to open it and maybe click a link, which then installs some malware on the computer. This could be something that logs keystrokes and sends them to another system on the Internet.</p>
<p style="text-align: justify;">You’ve likely have received emails like that, purporting to come from Microsoft or PayPal or some other company you recognize, and urging you to click a link to fix something to do with your account. Just since Sunday, I&#8217;ve received many phishing emails, including:</p>
<ul style="text-align: justify;">
<li>To our A/P department, purporting to come from me, giving the ok to pay an invoice from a fake CEO training course in my name</li>
<li>Domain-name expiry notifications</li>
<li>Anti-virus order receipts and click/call if that&#8217;s a mistake</li>
<li>Fake DocuSign emails for contracts/receipts</li>
</ul>
<p style="text-align: justify;">I&#8217;ve read many books on hacking, from both sides of the ethical fence, and it always strikes me that security checklists and security reviews of SQL Server environments are all well and good, but there are a few missing things that I think are worth considering.</p>
<p style="text-align: justify;">For instance, does your company provide training or guidance on recognizing and avoiding phishing emails? Such phishing emails could be cleverly targeted, especially if hackers are going after a specific company and make an email look like it’s coming from a source the DBA trusts. If a DBA clicked a phishing link and unknowingly installed malware on a personal laptop, say, and then connected to a work system, the hackers could capture the DBA’s login credentials.</p>
<p style="text-align: justify;">Such phishing emails could be cleverly targeted, especially if hackers are going after a specific company and make an email look like it’s coming from a source the DBA trusts. Hacking books have plenty of descriptions of this being done to companies like banks and defense contractors.</p>
<p style="text-align: justify;">A way to test people in your company would be to create a fake email with a link that takes them to a web page showing that they’ve clicked something they shouldn’t have, and to be wary in future – or just to keep track of what proportion of recipients in the company were fooled into clicking the link.</p>
<p style="text-align: justify;">Another thing to be wary of is social engineering. This is where a hacker calls someone on the phone, pretending to be someone who needs some information that can help them break into a computer system, and fools the person into giving that information out. I’ve read about this being used many times in the past, and is a relatively common technique used by phone scammers. &#8220;Hello, we&#8217;re from Microsoft support and your computer has been hacked&#8230;&#8221;</p>
<p style="text-align: justify;">Finally, one of the things you might consider for your company is engaging the services of a third-party company that does what&#8217;s called penetration testing. These people will deliberately try to hack into your environment, with your permission, to discover security weaknesses that you can then patch before a malicious hacker tries to break in. Sometimes this is known as <em>ethical hacking</em>, and you can actually learn how to do it yourself, to think about security from the attacker’s perspective and assess your own environment for security flaws.</p>
<p style="text-align: justify;"><strong>Call to Action</strong></p>
<p style="text-align: justify;">If you’re responsible for databases that contain any information that you don’t want someone to have unauthorized access to, you need to make sure that your security doesn’t have any problems. That includes making sure that the users are educated about ways that they can be duped into giving out info or installing malware, and testing your system’s defenses to see if they can be broken. You can be sure that someone out there will try to get in sooner or later.</p>
<p>PS A few interesting books on hacking that spring to mind:</p>
<ul>
<li><a href="http://www.amazon.com/gp/product/0544251792/ref=as_li_tl?ie=UTF8&amp;camp=1789&amp;creative=390957&amp;creativeASIN=0544251792&amp;linkCode=as2&amp;tag=sqlscom-20&amp;linkId=CP6KVDTYP6AFRJD6" target="_blank" rel="noopener">@War: The Rise of the Military-Internet Complex</a></li>
<li><a href="https://www.amazon.com/Ghost-in-Wires-audiobook/dp/B005HBO3BY?&amp;linkCode=ll2&amp;tag=sqlscom-20&amp;linkId=e01499d068c5617bdcb7ea52212b8969&amp;language=en_US&amp;ref_=as_li_ss_tl" target="_blank" rel="noopener">Ghost in the Wires: My Adventures as the World&#8217;s Most Wanted Hacker</a> (I actually crossed paths with Mitnick when I was at DEC &#8211; R.I.P.)</li>
<li><a href="https://www.amazon.com/Art-Deception-Controlling-Element-Security/dp/076454280X?&amp;linkCode=ll2&amp;tag=sqlscom-20&amp;linkId=aee028d192ca241cc0d192e02fda5ff3&amp;language=en_US&amp;ref_=as_li_ss_tl" target="_blank" rel="noopener">The Art of Deception: Controlling the Human Element of Security</a></li>
<li>(where the word comes from) <a href="https://www.amazon.com/Hackers-Computer-Revolution-Steven-Levy/dp/1449388396?&amp;linkCode=ll2&amp;tag=sqlscom-20&amp;linkId=9527e4e04853d00dc501b57882d56a61&amp;language=en_US&amp;ref_=as_li_ss_tl" target="_blank" rel="noopener">Hackers: Heroes of the Computer Revolution</a></li>
</ul>
<p>The post <a href="https://www.sqlskills.com/blogs/paul/sql101-phishing-attacks/">SQL101: Phishing attacks</a> appeared first on <a href="https://www.sqlskills.com/blogs/paul">Paul S. Randal</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.sqlskills.com/blogs/paul/sql101-phishing-attacks/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>SQL101: Top Ten SQL Server Performance Tuning Best Practices</title>
		<link>https://www.sqlskills.com/blogs/paul/sql101-top-ten-sql-server-performance-tuning-best-practices/</link>
					<comments>https://www.sqlskills.com/blogs/paul/sql101-top-ten-sql-server-performance-tuning-best-practices/#comments</comments>
		
		<dc:creator><![CDATA[Paul Randal]]></dc:creator>
		<pubDate>Wed, 25 Mar 2026 20:11:58 +0000</pubDate>
				<category><![CDATA[Performance Tuning]]></category>
		<category><![CDATA[SQL101]]></category>
		<guid isPermaLink="false">https://www.sqlskills.com/blogs/paul/?p=5370</guid>

					<description><![CDATA[<p>There are a huge number of best practices around SQL Server performance tuning – I could easily write a whole book on the topic, especially when you consider the number of different database settings, server settings, coding practices, wait types, and so on that can affect performance. For this post I decided to step back [&#8230;]</p>
<p>The post <a href="https://www.sqlskills.com/blogs/paul/sql101-top-ten-sql-server-performance-tuning-best-practices/">SQL101: Top Ten SQL Server Performance Tuning Best Practices</a> appeared first on <a href="https://www.sqlskills.com/blogs/paul">Paul S. Randal</a>.</p>
]]></description>
										<content:encoded><![CDATA[<p style="text-align: justify;">There are a huge number of best practices around SQL Server performance tuning – I could easily write a whole book on the topic, especially when you consider the number of different database settings, server settings, coding practices, wait types, and so on that can affect performance. For this post I decided to step back a bit from a list of specifics and list some general recommendations for how to <em>approach</em> performance tuning so that you maximize effort and minimize distractions.</p>
<h3 style="text-align: justify;">1) Don’t Assume the Symptom Is the Root Cause</h3>
<p style="text-align: justify;">Many DBAs and developers tend towards what I call ‘knee-jerk performance troubleshooting’, where a minimal amount of analysis and investigation is performed and the assumption is made that the most prevalent symptom of poor performance must be the root cause. When this happens, and effort is made to try to address the supposed root cause, it can lead to a lot of wasted time, and frustration that the mitigation efforts don’t help the situation.</p>
<p style="text-align: justify;">My favorite example of this, and a problem I’m sure you’ve all had, is when  average disk latency is high. The classic knee-jerk reaction is that it must be the I/O subsystem that has a problem, so the company spends money on a better I/O subsystem and the problem goes away for a little while and then comes back again, because the problem is not the hardware itself, but something happening within SQL Server.</p>
<p style="text-align: justify;">For a case like this, it’s generally better to take a mental step back and ask why is SQL Server overloading the I/O subsystem or more precisely, why is SQL Server doing so many physical reads. There are many reasons this could be happening, such as (but not limited to):</p>
<ul style="text-align: justify;">
<li>An inefficient query plan doing a large, parallel table scan instead of using a nonclustered index because of something like a missing index, or implicit conversion, or out-of-date statistics</li>
<li>Memory pressure on the buffer pool (meaning there isn’t enough space to hold the usual ‘working set’ of database pages) from the OS</li>
</ul>
<p style="text-align: justify;">It always pays to do some investigation instead of jumping to a quick conclusion on the root cause.</p>
<h3 style="text-align: justify;">2) Determine the Scope of the Problem</h3>
<p style="text-align: justify;">It’s extremely important to figure out what the scope of the problem is, as that determines how you’ll go about investigating the problem, what metrics to gather, and what scripts and tools to use. For instance, being asked to investigate stored procedure XYZ which takes twice as long to run as it usually does is very different from being asked to tune all long-running stored procedures.</p>
<p style="text-align: justify;">Stored procedure metrics can be obtained by running the query in Management Studio, and noting duration, CPU, and IO statistics.  That information can also be obtained from the plan cache, and you can also leverage the plan cache when you need to find the longest-running stored procedures.  The following query, adapted from the popular set of DMV scripts <a href="https://glennsqlperformance.com/resources/">here</a>, lists the slowest 25 procedures, based on average duration:</p>
<pre class="brush: sql; title: ; toolbar: true; wrap-lines: true; notranslate">
SELECT
    TOP (25) &#x5B;p].&#x5B;name] AS &#x5B;SP Name],
    &#x5B;eps].&#x5B;min_elapsed_time],
    &#x5B;eps].&#x5B;total_elapsed_time] / &#x5B;eps].&#x5B;execution_count] AS &#x5B;avg_elapsed_time],
    &#x5B;eps].&#x5B;max_elapsed_time],
    &#x5B;eps].&#x5B;last_elapsed_time],
    &#x5B;eps].&#x5B;total_elapsed_time],
    &#x5B;eps].&#x5B;execution_count],
    ISNULL (&#x5B;eps].&#x5B;execution_count] /
        DATEDIFF (MINUTE, &#x5B;eps].&#x5B;cached_time], GETDATE ()), 0) AS &#x5B;Executions/Minute],
    FORMAT (&#x5B;eps].&#x5B;last_execution_time],
        &#039;yyyy-MM-dd HH:mm:ss&#039;, &#039;en-US&#039;) AS &#x5B;Last Execution Time],
    FORMAT (&#x5B;eps].&#x5B;cached_time],
        &#039;yyyy-MM-dd HH:mm:ss&#039;, &#039;en-US&#039;) AS &#x5B;Plan Cached Time]
    -- ,&#x5B;qp].&#x5B;query_plan] AS &#x5B;Query Plan] -- Uncomment if you want the query plan
FROM sys.procedures AS &#x5B;p] WITH (NOLOCK)
INNER JOIN sys.dm_exec_procedure_stats AS &#x5B;eps] WITH (NOLOCK)
    ON &#x5B;p].&#x5B;object_id] = &#x5B;eps].&#x5B;object_id]
CROSS APPLY sys.dm_exec_query_plan (&#x5B;eps]. &#x5B;plan_handle]) AS &#x5B;qp]
WHERE
    &#x5B;eps].&#x5B;database_id] = DB_ID ()
    AND DATEDIFF (MINUTE, &#x5B;eps].&#x5B;cached_time], GETDATE()) &gt; 0
ORDER BY &#x5B;avg_elapsed_time] DESC
OPTION (RECOMPILE);
</pre>
<p style="text-align: justify;">There are also tools like the <a href="https://www.sentryone.com/products/features/top-sql">Top SQL functionality</a> in SolarWinds SQL Sentry that can help identify highest impact and highest resource using queries.</p>
<h3 style="text-align: justify;">3) Define the Goal for Success</h3>
<p style="text-align: justify;">Once you have the scope of the problem, the next step is to determine the goal of the performance tuning effort, so you know when you&#8217;ve achieved success and can move on to another task. Don’t allow the goal to be something undefined and open-ended like ‘stored procedure XYZ needs to be faster’, it needs to be well-defined such as ‘stored procedure XYZ needs to run at the speed it did before, i.e. at 50% of the current elapsed time’.</p>
<p style="text-align: justify;">Sometimes the investigation will be a bit more involved if the scope is wider, requiring capturing metrics and information over time before any analysis and mitigation can start. For instance, one of the first consulting clients I worked with had a somewhat open-ended goal for me which was, paraphrasing, ‘tempdb runs out of space once a week, and we need it not to do that’ without any idea why. The investigation involved me setting up two SQL Agent jobs; one every 10 seconds to look for large uses of tempdb and log information to a table, and another once an hour to email me any results from the previous hour. The general code I wrote to find space-hogs in tempdb is below:</p>
<pre class="brush: sql; title: ; toolbar: true; wrap-lines: true; notranslate">
-- InternalMB/Pages: worktables (cursor, spool) , workfiles (hash joins), sort
-- UserMB/Pages: everything else
--
SELECT
    GETDATE () AS &#x5B;Date],
    &#x5B;tsu].&#x5B;session_id] AS &#x5B;SessionID],
    &#x5B;tsu].&#x5B;exec_context_id] AS &#x5B;ExecContextID], -- anything over 0 means parallelism
    (&#x5B;tsu].&#x5B;user_objects_alloc_page_count] -
        &#x5B;tsu].&#x5B;user_objects_dealloc_page_count]) AS &#x5B;UserPages],
    ROUND (CONVERT (FLOAT, (&#x5B;tsu].&#x5B;user_objects_alloc_page_count] -
        &#x5B;tsu].&#x5B;user_objects_dealloc_page_count]) * 8) / 1024.0, 2) AS &#x5B;UserMB],
    (&#x5B;tsu].&#x5B;internal_objects_alloc_page_count] -
        &#x5B;tsu].&#x5B;internal_objects_dealloc_page_count]) AS &#x5B;InternalPages],
    ROUND (CONVERT (FLOAT, (&#x5B;tsu].&#x5B;internal_objects_alloc_page_count] -
        &#x5B;tsu].&#x5B;internal_objects_dealloc_page_count]) * 8) / 1024.0, 2) AS &#x5B;InternalMB],
    &#x5B;er].&#x5B;plan_handle] AS &#x5B;Plan],
    &#x5B;est].&#x5B;text] AS &#x5B;Text]
FROM sys.dm_db_task_space_usage &#x5B;tsu]
JOIN sys.dm_exec_requests &#x5B;er]
    ON &#x5B;er].&#x5B;session_id] = &#x5B;tsu].&#x5B;session_id]
CROSS APPLY sys.dm_exec_sql_text (&#x5B;er].&#x5B;sql_handle]) &#x5B;est]
/*
WHERE
    -- Optionally, filter by a size limit
    -- E.g., the 16384 is 128MB in 8KB pages
    ((&#x5B;user_objects_alloc_page_count] - &#x5B;user_objects_dealloc_page_count]) +
        (&#x5B;internal_objects_alloc_page_count] - &#x5B;internal_objects_dealloc_page_count])) &gt;= 16384
*/
ORDER BY
    ((&#x5B;user_objects_alloc_page_count] - &#x5B;user_objects_dealloc_page_count]) +
        (&#x5B;internal_objects_alloc_page_count] - &#x5B;internal_objects_dealloc_page_count])) DESC;
</pre>
<h3>4) Understand the Limitations</h3>
<p style="text-align: justify;">Before you start proposing or making changes, it’s important to know if there are any things you simply cannot do. Here are some examples:</p>
<ul style="text-align: justify;">
<li>If the application is written by a vendor, you’re not going to be able to make code changes to improve performance</li>
<li>If the application is written by a vendor, you might not even be able to add or change indexes without voiding the vendor’s support agreement</li>
<li>You might not be able to change a setting like MAXDOP or parameter sniffing for the whole server, which may mean using an <a href="https://docs.microsoft.com/en-us/sql/t-sql/statements/alter-database-scoped-configuration-transact-sql">ALTER DATABASE SCOPED CONFIGURATION</a> option for just one database</li>
</ul>
<p style="text-align: justify;">Even if you can change code, there may be a lengthy testing process which prevents a change from being immediately implemented, so you may need to pursue alternative solutions (potentially short-term) to quickly fix the problem.</p>
<h3 style="text-align: justify;">5) Change One Thing at a Time</h3>
<p style="text-align: justify;">One of the most confusing things to do when performance tuning is to make multiple changes at the same time, as then you won’t know which change had an effect, or whether multiple changes cancelled each other out. Always change one thing at a time and keep a note of what you changed and what effect it had, if any. Also, if a change doesn’t have any effect then revert the change so that it doesn’t become a complication if the workload evolves at a later date.</p>
<h3>6) Do Not Test in Production</h3>
<p style="text-align: justify;">One of the worst things to do when performance tuning is to make changes directly in production, as that can lead to dire consequences for the workload and business if a change create a huge negative effect. This means you need a separate test/QA environment that can be used to evaluate changes under production workload conditions, or as close to it as possible. And that leads nicely into the next point…</p>
<h3 style="text-align: justify;">7) Understand How Test Compares to Production</h3>
<p style="text-align: justify;">If your test system doesn’t compare to production then you may not see the same change in performance in production as you do in test. Classic examples of this include:</p>
<ul style="text-align: justify;">
<li>A production system with a certain number of CPUs (e.g. four 8-core processors) and a lower powered test system to save money (e.g. four quad-core processors)</li>
<li>Along the same lines, test system having a lot less memory than production, or a different NUMA configuration, or a lower-rated storage subsystem</li>
<li>Test system only having a subset of the production data to test with</li>
<li>Test system not being able to simulate the production workload</li>
</ul>
<p style="text-align: justify;">All of these things can result in the test system producing different query plans, or the workload in test having very different characteristics than in production. This means you’ll be performance tuning for a different workload and environment and the efficacy of the changes may not translate to the production environment.</p>
<h3>8) Understand the Implications of the Change</h3>
<p style="text-align: justify;">After you’ve determined what the necessary change is, you need to consider what wider effect, if any, making that change will have. For example, if you need to change MAXDOP or the cost threshold for parallelism, that will flush the plan cache, and you might run the risk of parameter-sensitive queries recompiling with sub-optimal plans.</p>
<p style="text-align: justify;">Other changes might be more environmental, like offloading parts of a query workload to a readable secondary in an availability group. That can lead to index fragmentation issues on the primary database, which can be a performance problem of their own (as I described in <a href="https://sqlperformance.com/2015/03/sql-indexes/unexpected-fragmentation">this SQLPerformance.com post</a>).</p>
<p style="text-align: justify;">You don’t want to solve one performance problem and end up with an unexpected different problem to then have to solve.</p>
<h3>9) Create a Rollback Plan</h3>
<p style="text-align: justify;">It’s very important that you have a complete log of what’s been changed and have the ability to revert the changes if something goes wrong. This means preserving original copies of all code and schema and ideally having a script you can run to quickly roll back the changes.</p>
<p style="text-align: justify;">If this would be hard to do, and would really entail restoring the database from backups, one thing to consider is creating a database snapshot of the database and keeping it around for a few days. A database snapshot automatically keeps a pre-change copy of all changed data file pages since the time the database snapshot was created and allows you to effectively put the database back to that time with a one-line T-SQL command (internally SQL Server does this by pushing the pre-change pages back into the real database – called ‘reverting the database to the database snapshot’).</p>
<h3 style="text-align: justify;">10) Remove Diagnostic Elements from Production</h3>
<p style="text-align: justify;">Once you’ve finished the investigation and reached the performance tuning goal, make sure you remove all of the diagnostics that you implemented to help with the investigation, as they could cause performance problems themselves if left in place, especially Extended Event sessions as they can become ‘silent killers’ that use up a lot of CPU resources with no other clue that they are the problem.</p>
<p style="text-align: justify;">You can see which Extended Event sessions are running using the following code:</p>
<pre class="brush: sql; title: ; toolbar: true; wrap-lines: true; notranslate">
SELECT
    &#x5B;ses].&#x5B;name] AS &#x5B;Session Name],
    CASE
        WHEN &#x5B;xs].&#x5B;address] IS NOT NULL THEN &#039;Running&#039;
        ELSE &#039;Stopped&#039;
    END AS &#x5B;State],
    &#x5B;xs].&#x5B;create_time] AS &#x5B;Start Time]
FROM sys.server_event_sessions AS &#x5B;ses]
LEFT OUTER JOIN sys.dm_xe_sessions AS &#x5B;xs]
    ON &#x5B;ses].&#x5B;name] = &#x5B;xs].&#x5B;name]
ORDER BY &#x5B;State], &#x5B;Start Time];
</pre>
<p style="text-align: justify;">And if you&#8217;re on SQL Server 2025 and using Extended Events, there&#8217;s a new MAX_DURATION option you can use to ensure a diagnostic session stops running after a certain amount of time.</p>
<h2>Summary</h2>
<p>You should always take a step-by-step approach to performance tuning rather than jumping right in and changing things haphazardly in production, and I hope this post has provided you with a simple framework you can put into practice. There’s a lot of code out there to help you with various investigations, plus free tools like <a href="https://www.solarwinds.com/free-tools/plan-explorer" target="_blank" rel="noopener">Plan Explorer</a> – I can’t recommend this enough! Happy tuning!</p>
<p>The post <a href="https://www.sqlskills.com/blogs/paul/sql101-top-ten-sql-server-performance-tuning-best-practices/">SQL101: Top Ten SQL Server Performance Tuning Best Practices</a> appeared first on <a href="https://www.sqlskills.com/blogs/paul">Paul S. Randal</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.sqlskills.com/blogs/paul/sql101-top-ten-sql-server-performance-tuning-best-practices/feed/</wfw:commentRss>
			<slash:comments>3</slash:comments>
		
		
			</item>
		<item>
		<title>The Curious Case of&#8230; no buffer pool memory and no OS memory available</title>
		<link>https://www.sqlskills.com/blogs/paul/the-curious-case-of-no-buffer-pool-memory-and-no-os-memory-available/</link>
					<comments>https://www.sqlskills.com/blogs/paul/the-curious-case-of-no-buffer-pool-memory-and-no-os-memory-available/#comments</comments>
		
		<dc:creator><![CDATA[Paul Randal]]></dc:creator>
		<pubDate>Fri, 13 Mar 2026 20:01:07 +0000</pubDate>
				<category><![CDATA[The Curious Case of...]]></category>
		<guid isPermaLink="false">https://www.sqlskills.com/blogs/paul/?p=5366</guid>

					<description><![CDATA[<p>Jonathan had a client issue recently where SQL Server&#8217;s buffer pool had been forced down to a ridiculously small size, only a few hundred MB, but the OS also showed basically no free memory. Page Life Expectancy was zero! What was going on? From investigating SQL Server&#8217;s memory usage, the memory manager showed that target [&#8230;]</p>
<p>The post <a href="https://www.sqlskills.com/blogs/paul/the-curious-case-of-no-buffer-pool-memory-and-no-os-memory-available/">The Curious Case of&#8230; no buffer pool memory and no OS memory available</a> appeared first on <a href="https://www.sqlskills.com/blogs/paul">Paul S. Randal</a>.</p>
]]></description>
										<content:encoded><![CDATA[<p style="text-align: justify;">Jonathan had a client issue recently where SQL Server&#8217;s buffer pool had been forced down to a ridiculously small size, only a few hundred MB, but the OS also showed basically no free memory. Page Life Expectancy was zero! What was going on?</p>
<p style="text-align: justify;">From investigating SQL Server&#8217;s memory usage, the memory manager showed that target and total memory were the same, at only 1.2GB, and lock pages in memory was correctly set. The next thing to check was for ballooning in VMware &#8211; a common cause of memory issues &#8211; but this wasn&#8217;t the problem either.</p>
<p style="text-align: justify;">Next, querying <em>sys.dm_os_sys_memory</em> showed almost 100GB in kernel non-paged pool memory. Jonathan immediately guessed that it was a kernel driver memory leak &#8211; based on some deliberate-error-inducing coding he&#8217;d done of kernel drivers &#8211; so the next step was to see what filter drivers were on the system.</p>
<p style="text-align: justify;">You can do this using the command fltmc on any Windows machine. For instance, in a command prompt using &#8216;Run as administrator&#8217; on a Windows 10 laptop, it shows:</p>
<pre>C:\WINDOWS\system32&gt;fltmc

Filter Name                     Num Instances    Altitude    Frame
------------------------------  -------------  ------------  -----
bindflt                                 1       409800         0
UCPD                                   11       385250.5       0
WdFilter                               11       328010         0
storqosflt                              0       244000         0
wcifs                                   1       189900         0
dbx                                     5       186500         0
CldFlt                                  5       180451         0
FileCrypt                               0       141100         0
luafv                                   1       135000         0
npsvctrig                               1        46000         0
RsFx0800                                1        41008.00      0
Wof                                     8        40700         0
FileInfo                               11        40500         0</pre>
<p style="text-align: justify;">And then you can investigate each driver using the <a href="https://learn.microsoft.com/en-us/windows-hardware/drivers/ifs/allocated-altitudes" target="_blank" rel="noopener">Allocated Filter Altitudes</a> list from Microsoft (as they assign a position in the filter driver stack to each filter driver), starting with those with the highest altitudes. From there, you can prove what&#8217;s taking up memory using the PoolMon tool from the Windows Driver Development Kit (see <a href="https://learn.microsoft.com/en-us/windows-hardware/drivers/debugger/using-poolmon-to-find-a-kernel-mode-memory-leak?source=recommendations" target="_blank" rel="noopener">Use PoolMon to Find a Kernel-Mode Memory Leak</a>).</p>
<p style="text-align: justify;">Turns out in the client&#8217;s case it was a driver from an old tool that hadn&#8217;t been un-installed &#8211; problem solved!</p>
<p style="text-align: justify;">Bottom line: don&#8217;t have the mindset that what manifests as a problem in SQL Server is always a SQL Server problem, as sometimes the issue is environmental and SQL Server is the unwitting victim of the root cause!</p>
<p>The post <a href="https://www.sqlskills.com/blogs/paul/the-curious-case-of-no-buffer-pool-memory-and-no-os-memory-available/">The Curious Case of&#8230; no buffer pool memory and no OS memory available</a> appeared first on <a href="https://www.sqlskills.com/blogs/paul">Paul S. Randal</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.sqlskills.com/blogs/paul/the-curious-case-of-no-buffer-pool-memory-and-no-os-memory-available/feed/</wfw:commentRss>
			<slash:comments>2</slash:comments>
		
		
			</item>
		<item>
		<title>The SQL Server Transaction Log, Part 3: The Circular Nature of the Log</title>
		<link>https://www.sqlskills.com/blogs/paul/the-sql-server-transaction-log-part-3-the-circular-nature-of-the-log/</link>
					<comments>https://www.sqlskills.com/blogs/paul/the-sql-server-transaction-log-part-3-the-circular-nature-of-the-log/#comments</comments>
		
		<dc:creator><![CDATA[Paul Randal]]></dc:creator>
		<pubDate>Mon, 09 Mar 2026 20:02:18 +0000</pubDate>
				<category><![CDATA[Transaction Log]]></category>
		<guid isPermaLink="false">https://www.sqlskills.com/blogs/paul/?p=5360</guid>

					<description><![CDATA[<p>(This post first appeared on SQLperformance.com four years ago as part of a blog series, before that website was mothballed later in 2022 and the series was curtailed. Reposted here with permission, with a few tweaks.) In the second part of this series, I described the structural hierarchy of the transaction log. As this post is chiefly [&#8230;]</p>
<p>The post <a href="https://www.sqlskills.com/blogs/paul/the-sql-server-transaction-log-part-3-the-circular-nature-of-the-log/">The SQL Server Transaction Log, Part 3: The Circular Nature of the Log</a> appeared first on <a href="https://www.sqlskills.com/blogs/paul">Paul S. Randal</a>.</p>
]]></description>
										<content:encoded><![CDATA[<p style="text-align: justify;"><em>(This post first appeared on SQLperformance.com four years ago as part of a blog series, before that website was mothballed later in 2022 and the series was curtailed. Reposted here with permission, with a few tweaks.)</em></p>
<p style="text-align: justify;">In the <a href="https://www.sqlskills.com/blogs/paul/the-sql-server-transaction-log-part-2-log-architecture/" target="_blank" rel="noopener">second part</a> of this series, I described the structural hierarchy of the transaction log. As this post is chiefly concerned with the Virtual Log Files (VLFs) I described, I recommend you read the second part before continuing.</p>
<p style="text-align: justify;">When all is well, the transaction log will endlessly loop, reusing the existing VLFs. This behavior is what I call the <em>circular nature of the log</em>. Sometimes, however, something will happen to prevent this, and the transaction log grows and grows, adding more and more VLFs. In this post, I’ll explain how all this works, or sometimes doesn’t.</p>
<h2 style="text-align: justify;">VLFs and Log Truncation</h2>
<p style="text-align: justify;">All VLFs have a header structure containing metadata about the VLF. One of the most important fields in the structure is the status of the VLF, and the values we’re interested in are zero, meaning the VLF is <em>inactive</em>, and two, meaning the VLF is <em>active</em>. It’s important because an inactive VLF can be reused, but an active one cannot. Note that a VLF is wholly active or wholly inactive.</p>
<p style="text-align: justify;">A VLF will remain active while required log records are in it, so it can’t be reused and overwritten (I’ll cover log records themselves next time). Examples of reasons why log records may be required include:</p>
<ul style="text-align: justify;">
<li>There’s a long-running transaction the log records are part of, so they cannot be released until the transaction has committed or has finished rolling back</li>
<li>A log backup hasn’t yet backed up those log records</li>
<li>That portion of the log has not yet been processed by the Log Reader Agent for transactional replication or Change Data Capture</li>
<li>That portion of the log hasn’t yet been sent to an asynchronous database mirror or availability group replica</li>
</ul>
<p style="text-align: justify;">It’s important to note that if there are no reasons for a VLF to remain active, it won’t switch to being inactive again until a process called <em>log truncation</em> occurs – more on this below.</p>
<p style="text-align: justify;">Using a simple hypothetical transaction log with only five VLFs and VLF sequence numbers starting at 1 (remember from last time that in reality, they never do), when the transaction log is created, VFL 1 is immediately marked as active, as there always has to be at least one active VLF in the transaction log—the VLF where log blocks are currently being written to. Our example scenario is shown in Figure 1 below.</p>
<p style="text-align: justify;"><a href="https://www.sqlskills.com/blogs/paul/wp-content/uploads/2026/03/Figure1.png"><img fetchpriority="high" decoding="async" class="alignnone wp-image-5361 size-large" src="https://www.sqlskills.com/blogs/paul/wp-content/uploads/2026/03/Figure1-1024x142.png" alt="" width="640" height="89" /></a></p>
<p style="text-align: justify;"><em class="caption">(Figure 1: Hypothetical, brand-new transaction log with 5 VLFs, sequence numbers 1 through 5 (my image))</em></p>
<p style="text-align: justify;">As more log records are created, and more log blocks are written to the transaction log, VLF 1 fills up, so VLF 2 has to become active for more log blocks to be written to, as shown in Figure 2 below.</p>
<p style="text-align: justify;"><a href="https://www.sqlskills.com/blogs/paul/wp-content/uploads/2026/03/Figure2.png"><img decoding="async" class="alignnone wp-image-5362 size-large" src="https://www.sqlskills.com/blogs/paul/wp-content/uploads/2026/03/Figure2-1024x219.png" alt="" width="640" height="137" /></a></p>
<p style="text-align: justify;"><em class="caption">(Figure 2: Activity moves through the transaction log (my image))</em></p>
<p style="text-align: justify;">SQL Server tracks the start of the oldest uncommitted (active) transaction, and this LSN is persisted on disk every time a checkpoint operation occurs. The LSN of the most recent log record written to the transaction log is also tracked, but it’s only tracked in memory as there’s no way to persist it disk without running into various race conditions. That doesn’t matter as it’s only used during crash recovery, and SQL Server can work out the LSN of the “end” of the transaction log during crash recovery. Checkpoints and crash recovery are topics for future posts in the series.</p>
<p style="text-align: justify;">Eventually, VLF 2 will fill up, and VLF 3 will become active, and so on. The crux of the circular nature of the transaction log is that earlier VLFs in the transaction log become inactive so they can be reused. This is done by a process called <em>log truncation</em>, which is also commonly called <em>log clearing</em>. Unfortunately, both of these terms are terrible misnomers because nothing is actually truncated or cleared.</p>
<p style="text-align: justify;">Log truncation is simply the process of examining all the VLFs in the transaction log and determining which active VLFs can now be marked as inactive again, as none of their contents are still required by SQL Server. When log truncation is performed, there’s no guarantee any active VLFs can be made inactive—it entirely depends on what’s happening with the database.</p>
<p style="text-align: justify;">There are two common misconceptions about log truncation:</p>
<ol style="text-align: justify;">
<li>The transaction log gets smaller (the “truncation” misconception). No, it doesn’t – there’s no size change from log truncation. The only thing capable of making the transaction log smaller is an explicit DBCC SHRINKFILE.</li>
<li>The inactive VLFs are zeroed out in some way (the “clearing” misconception). No – nothing is written to the VLF when it’s made inactive except for a few fields in the VLF header.</li>
</ol>
<p style="text-align: justify;">Figure 3 below shows our transaction log where VLFs 3 and 4 are active, and log truncation was able to mark VLFs 1 and 2 inactive.</p>
<p style="text-align: justify;"><a href="https://www.sqlskills.com/blogs/paul/wp-content/uploads/2026/03/Figure3.png"><img loading="lazy" decoding="async" class="alignnone wp-image-5363 size-large" src="https://www.sqlskills.com/blogs/paul/wp-content/uploads/2026/03/Figure3-1024x219.png" alt="" width="640" height="137" /></a></p>
<p style="text-align: justify;"><em class="caption">(Figure 3: Log truncation marks earlier VLFs as inactive (my image))</em></p>
<p style="text-align: justify;">When log truncation occurs depends on which recovery model is in use for the database:</p>
<ul style="text-align: justify;">
<li>Simple model: log truncation occurs when a checkpoint operation completes</li>
<li>Full model or bulk-logged model: log truncation occurs when a log backup completes (as long as there isn’t a concurrent full or differential backup running, in which case log truncation is deferred until the data backup completes)</li>
</ul>
<p style="text-align: justify;">There are no exceptions to this.</p>
<h2 style="text-align: justify;">Circular Nature of the Log</h2>
<p style="text-align: justify;">To avoid the transaction log having to grow, log truncation must be able to mark VLFs inactive. The first physical VLF in the log must be inactive for the transaction log to have its circular nature.</p>
<p style="text-align: justify;">Consider Figure 4 below, which shows VLFs 4 and 5 are in use and log truncation has marked VLFs 1 through 3 as inactive. More log records are generated, more log blocks are written into VLF 5, and eventually, it fills up.</p>
<p style="text-align: justify;"><a href="https://www.sqlskills.com/blogs/paul/wp-content/uploads/2026/03/Figure4.png"><img loading="lazy" decoding="async" class="alignnone wp-image-5364 size-large" src="https://www.sqlskills.com/blogs/paul/wp-content/uploads/2026/03/Figure4-1024x214.png" alt="" width="640" height="134" /></a></p>
<p style="text-align: justify;"><em class="caption">(Figure 4: Activity fills up the highest physical VLF in the transaction log (my image))</em></p>
<p style="text-align: justify;">At this point, the log manager for the database looks at the status of the first physical VLF in the transaction log, which in our example is VLF 1, with sequence number 1. VLF 1 is inactive, so the transaction log can wrap around and begin filling again from the start. The log manager changes the first VLF to active and increases its sequence number to be one higher than the current highest VLF sequence number. So it becomes VLF 6, and logging continues with log block being written into that VLF. This is the circular nature of the log, as shown below in Figure 5.</p>
<p style="text-align: justify;"><a href="https://www.sqlskills.com/blogs/paul/wp-content/uploads/2026/03/Figure5.png"><img loading="lazy" decoding="async" class="alignnone wp-image-5365 size-large" src="https://www.sqlskills.com/blogs/paul/wp-content/uploads/2026/03/Figure5-1024x251.png" alt="" width="640" height="157" /></a></p>
<p style="text-align: justify;"><em class="caption">(Figure 5: The circular nature of the transaction log and VLF reuse (my image))</em></p>
<h2 style="text-align: justify;">When Things Go Wrong</h2>
<p style="text-align: justify;">When the first physical VLF in the transaction log isn’t inactive, the transaction log cannot wrap around, so it will grow (as long as it’s configured to do so and there is sufficient disk space). This often happens because there’s something preventing log truncation from deactivating VLFs. If you find the transaction log for a database is growing, you can query SQL Server to find out if there’s a log truncation problem using this simple code below:</p>
<pre lang="tsql">SELECT [log_reuse_wait_desc]
FROM [master].[sys].[databases]
WHERE [name] = N'MyDatabase';
</pre>
<p style="text-align: justify;">If log truncation was able to deactivate one or more VLFs, then the result will be <em>NOTHING. Otherwise</em>, you’ll be given a reason why log truncation couldn’t deactivate any VLFs. There is a long list of possible reasons described <a href="https://learn.microsoft.com/en-us/sql/relational-databases/logs/the-transaction-log-sql-server?view=sql-server-ver17#factors-that-can-delay-log-truncation" target="_blank" rel="noopener">here</a> in the section <em>Factors that can delay log truncation.</em></p>
<p style="text-align: justify;">It’s important to understand the semantics of what the result is: it’s the reason log truncation couldn’t do anything <em>the last time it tried to run</em>. For instance, the result might be <em>ACTIVE_BACKUP_OR_RESTORE,</em> but you know that that long-running full backup has finished. This just means that the last time log truncation was attempted, the backup was still running.</p>
<p style="text-align: justify;">In my experience, the most common reason for log truncation being prevented is <em>LOG_BACKUP</em>; i.e., go perform a log backup! But there’s also an interesting, weird behavior with<em> LOG_BACKUP</em>. If you continually see the result <em>LOG_BACKUP</em> but you know log backups are happening successfully, it’s because there is very little activity in the database and the current VLF is the same as it was the last time a log backup was performed. So, <em>LOG_BACKUP</em> means “go perform a log backup” or “all of the log records backed up are from the current VLF, so it couldn’t be deactivated.” When the latter happens, it can be confusing.</p>
<h2 style="text-align: justify;">Circling Back&#8230;</h2>
<p style="text-align: justify;">Maintaining the circular nature of the transaction log is very important to avoid costly log growths and the need to take corrective action. Usually, this means ensuring log backups are happening regularly to facilitate log truncation and sizing the transaction log to be able to hold any large, long-running operations like index rebuilds or ETL operations without log growth occurring.</p>
<p style="text-align: justify;">In the next part of the series, I’ll cover log records, how they work, and some interesting examples.</p>
<p>The post <a href="https://www.sqlskills.com/blogs/paul/the-sql-server-transaction-log-part-3-the-circular-nature-of-the-log/">The SQL Server Transaction Log, Part 3: The Circular Nature of the Log</a> appeared first on <a href="https://www.sqlskills.com/blogs/paul">Paul S. Randal</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.sqlskills.com/blogs/paul/the-sql-server-transaction-log-part-3-the-circular-nature-of-the-log/feed/</wfw:commentRss>
			<slash:comments>2</slash:comments>
		
		
			</item>
	</channel>
</rss>
