<?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>Rollback &#187; English</title>
	<atom:link href="http://blog.rollback.hu/category/english/feed/" rel="self" type="application/rss+xml" />
	<link>http://blog.rollback.hu</link>
	<description>SQL, üzemeltetés kicsiknek és nagyoknak.</description>
	<lastBuildDate>Thu, 17 Nov 2011 16:38:59 +0000</lastBuildDate>
	<generator>http://wordpress.org/?v=2.8.6</generator>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
			<item>
		<title>T-SQL Tuesday #12 &#8211; Why are DBA skills necessary?</title>
		<link>http://blog.rollback.hu/2010/11/t-sql-tuesday-12-why-are-dba-skills-necessary/</link>
		<comments>http://blog.rollback.hu/2010/11/t-sql-tuesday-12-why-are-dba-skills-necessary/#comments</comments>
		<pubDate>Tue, 02 Nov 2010 04:34:10 +0000</pubDate>
		<dc:creator>Erik</dc:creator>
				<category><![CDATA[English]]></category>
		<category><![CDATA[eszmefuttatás]]></category>
		<category><![CDATA[üzemeltetés]]></category>

		<guid isPermaLink="false">http://blog.rollback.hu/?p=295</guid>
		<description><![CDATA[One of the few persons I admire for their skills is Paul Randal, who&#8217;s the host of the 12th T-SQL Tuesday. T-SQL Tuesday is about blogging about the same topic, inevitably from different angles as we people are different. It&#8217;s fun for reading, I hope it&#8217;s fun for writing as well &#8211; this is my [...]]]></description>
			<content:encoded><![CDATA[<p><a href="http://www.sqlskills.com/BLOGS/PAUL/post/Invitation-to-participate-in-T-SQL-Tuesday-12-e28093-Why-are-DBA-skills-necessary.aspx"><img src="http://blog.rollback.hu/wp-content/uploads/2010/10/t-sql-tuesday.jpg" alt="t-sql tuesday" title="t-sql tuesday" width="150" height="150" class="alignleft size-full wp-image-297" /></a>One of the few persons I admire for their skills is Paul Randal, who&#8217;s the host of the 12th T-SQL Tuesday. T-SQL Tuesday is about blogging about the same topic, inevitably from different angles as we people are different. It&#8217;s fun for reading, I hope it&#8217;s fun for writing as well &#8211; this is my first time participating. Given my current state of mind, this is going to be a rambling, rather than an article.</p>
<p>The question outlined in the title can be interpreted in many ways. The typical things in my head are the following:</p>
<ul>
<li>Programming: set-based, declarative approach vs sequential, procedural programming</li>
<li>Operations: RDBMS is quite different from OS and other standard apps (including server products)</li>
</ul>
<p><span id="more-295"></span><br />
Going through the items: a typical problem with application development is that most developers just can&#8217;t digest that SQL is not procedural language. It hates when you tell it how to do something. And it will punish you with amazing performance hits. Believe it or not but I really got T-SQL code like this:</p>
<pre class="brush: sql;">
DECLARE @tranid bigint, @processor int
DECLARE tranitem CURSOR FOR
SELECT transaction_id, processor
FROM transaction_history;
OPEN tranitem;
FETCH NEXT FROM tranitem INTO @tranid, @processor;
WHILE @@FETCH_STATUS = 0
   BEGIN
      IF @processor BETWEEN 31 AND 44
         UPDATE transaction_history SET processed = 1 WHERE transaction_id = @tranid;
      FETCH NEXT FROM tranitem INTO @tranid, @processor;
   END;
CLOSE tranitem;
DEALLOCATE tranitem;
</pre>
<p>Needless to say, transaction_history had 300 million lines and the column processor had an index on it. With the supplied script, we couldn&#8217;t wait for the completion. With the rewritten one, all the few thousand matching rows were updated in less than a minute. Oh, yes, this is what I wrote:</p>
<pre class="brush: sql;">
UPDATE transaction_history SET processed = 1 WHERE processor BETWEEN 31 AND 44
</pre>
<p>So a DBA is someone who can write effective T-SQL code? Sounds like a database developer&#8230; :)</p>
<p>Looking at the second item: why is a relational database engine so special? Well, because I like them :) Seriously, they should use a different approach in some cases than an OS or a webserver. Most obvious part is the scheduling I think. OSes usually use preemptive scheduler, that is, every process gets a time slice to run, then they should wait in the queue. With SQL Server that would be disastrous as the SQL processes would wait in the queue holding locks and it would bring down server performance. Instead, SQL Server goes for a cooperative scheduler, trying to give enough room for a process to finish completely as quick as possible. In general, SQL Server thrills to minimize lock holding time. And as a DBA, you should be good at understanding the locking mechanism, lock compatibilites and understand how online exactly is <em>online indexing</em>.</p>
<p>Also, if you&#8217;re a DBA, you should know that SQL Server is optimistic. It expects that if a transaction is started, it&#8217;ll be committed and in 99.99% of the cases, it&#8217;s right. In the remaining 0.01%, rollback is a bit more expensive than it could be if someone had optimized for it. Needles to say, you won&#8217;t restart your server if you abort a batch running for two hours and the rollback is not done after 30/60/90/120 minutes. Do you? You would wait a bit more to get back your database&#8230;</p>
<p>So far, the DBA is a guy who can write well-performing T-SQL code and understand the SQL Server architecture.</p>
<p>Let me add one more thing: they should be good at operational thinking as well &#8211; this differentiate a DBA from a developer and a Windows admin. Operational mindset is that you&#8217;re sensitive to potential pitfalls and you know how to avoid them. So if you see the T-SQL code, your only question is not if it&#8217;s good as it looks but you also ask if it&#8217;ll run fine in 25 instances in parallel. You know that things shoudl be tested first and test environment should be as mush similar to production as possible. (You know you should have a test environment, do you?) And you know when you can/should make bold moves, going with something untested.</p>
<p>Why are DBA skills necessary? Because otherwise you screw up your database server. Either by bringing it down and/or wasting its capacity. I&#8217;m no angel here. I was an involuntary DBA a few years ago and there was an issue with the backup partition in one of our clusters. To suspend all tranlog backups, I decided to stop the SQL Agent instead of disabling all the backup jops one by one. Smart idea, you may say. So I said SQL Agent service stop in Computer management and started to work. A few unexpected things happened in the next few minutes (e.g. I saw new backups appearing on the drive), so I checked the cluster and found that the agent started magically. Then I found that the whole cluster failed over. Later I realized that the cluster service detected that the agent was dead and did a failover. I should have used the cluster administrator or cluster.exe for this task. Since then, I&#8217;m very well aware of it.</p>
<p>One interesting thing is that people has a very dishonesting picture about SQL Server and what I see is that it partially comes from the fact that if you picked a SQL 7/2000 installer without any idea about all the things I wrote, you could still install and use it (and got surpised on the big transaction log). And these guys ran into serious problems &#8211; not because SQL Server was crap but because they had no idea about how it works. On the other side of the wall, if you had no experience with Oracle, no way on earth you could install one. That was a separate art. This way if you had an up and running Oracle server, there must have been someone with at least average skills around. (For the record: I&#8217;m not challenging the fact that Oracle had a better database engine at that time.)</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.rollback.hu/2010/11/t-sql-tuesday-12-why-are-dba-skills-necessary/feed/</wfw:commentRss>
		<slash:comments>3</slash:comments>
		</item>
		<item>
		<title>Error 18456, Level 14, state &#8211; SQL Server login errors</title>
		<link>http://blog.rollback.hu/2009/12/error-18456-level-14-state-sql-server-login-errors/</link>
		<comments>http://blog.rollback.hu/2009/12/error-18456-level-14-state-sql-server-login-errors/#comments</comments>
		<pubDate>Sun, 27 Dec 2009 23:08:09 +0000</pubDate>
		<dc:creator>Erik</dc:creator>
				<category><![CDATA[English]]></category>
		<category><![CDATA[error]]></category>
		<category><![CDATA[errorlog]]></category>
		<category><![CDATA[security]]></category>

		<guid isPermaLink="false">http://blog.rollback.hu/?p=131</guid>
		<description><![CDATA[(Magyarul itt)
Occasionally it happens that someone is unable to log in to SQL Server because they mistyped the password, have no permission, etc. This is not a problem &#8211; as long as we know what is the blocking issue. But how about someone being cocksure they&#8217;re trying the correct user, password ,server, etc and still [...]]]></description>
			<content:encoded><![CDATA[<p>(<a href="http://blog.rollback.hu/2009/07/sql-server-login-hibak-error-18456-level-14-state/">Magyarul itt</a>)<br />
Occasionally it happens that someone is unable to log in to SQL Server because they mistyped the password, have no permission, etc. This is not a problem &#8211; as long as we know what is the blocking issue. But how about someone being cocksure they&#8217;re trying the correct user, password ,server, etc and still failing? The most straightforward solution is my favorite, reading the SQL errorlog, once again. If the server is set to audit failed logins (and this is the default), you can find the error in the errorlog as well, not on the client side only:</p>
<blockquote><p>
Msg 18456, Level 14, State 1, Server DEMOSQL1, Line 1<br />
Login failed for user &#8216;tygger&#8217;
</p></blockquote>
<p>Actually, something better gets in to the log, but first, let&#8217;s analyze the first line, that is ,the first three numbers of the error. the first one is the error number, the identifier of the error; the second is the severity, that is, how bad it is. The bigger the number the worse it is. If you see something above 19, you might be in real trouble. The third number is the state, which is an interesting species. This can be used to provide diagnostic information, like throwing an error with state 1 from an SP and with state 2 from parameterized query. It can make DBAs (and developers) life easier. Now back to that errorlog!<br />
<span id="more-131"></span></p>
<pre class="brush: plain;">
2009-07-27 14:02:02.21 Logon     Error: 18456, Severity: 14, State: 8.
2009-07-27 14:02:02.21 Logon     Login failed for user 'tygger'. [CLIENT: 10.10.10.1]
</pre>
<p>Feel the difference: that state value is obviously different from what the client saw &#8211; and this is intentional. SQL Server won&#8217;t disclose any info which may help a malicious person during an attack. But if there&#8217;s a real need for help, you can go to the sysadmins and ask them to check the log what was the reason of the failure. They can check for the status values in the errorlog. And here&#8217;s the resolution:<br />
<table id="wp-table-reloaded-id-7-no-1" class="wp-table-reloaded wp-table-reloaded-id-7" cellspacing="1" cellpadding="1" border="1">
<thead>
	<tr class="odd row-1">
		<th class="column-1">ERROR</th><th class="column-2">DESCRIPTION</th>
	</tr>
</thead>
<tbody>
	<tr class="even row-2">
		<td class="column-1">2, 5</td><td class="column-2">Invalid username</td>
	</tr>
	<tr class="odd row-3">
		<td class="column-1">6</td><td class="column-2">SQL authentication was attempted with Windows login name</td>
	</tr>
	<tr class="even row-4">
		<td class="column-1">7</td><td class="column-2">Disabled login</td>
	</tr>
	<tr class="odd row-5">
		<td class="column-1">8</td><td class="column-2">Bad password</td>
	</tr>
	<tr class="even row-6">
		<td class="column-1">9</td><td class="column-2">Invalid password</td>
	</tr>
	<tr class="odd row-7">
		<td class="column-1">11, 12</td><td class="column-2">correct login but no server access</td>
	</tr>
	<tr class="even row-8">
		<td class="column-1">13</td><td class="column-2">SQL Server service paused</td>
	</tr>
	<tr class="odd row-9">
		<td class="column-1">16</td><td class="column-2">Correct login but no access to the selected (or default) database</td>
	</tr>
	<tr class="even row-10">
		<td class="column-1">18</td><td class="column-2">User must change password</td>
	</tr>
	<tr class="odd row-11">
		<td class="column-1">23</td><td class="column-2">Server is shutting down, only sysadmins can log in</td>
	</tr>
	<tr class="even row-12">
		<td class="column-1">27</td><td class="column-2">Correct login but server cannot determine an initial database</td>
	</tr>
	<tr class="odd row-13">
		<td class="column-1">38</td><td class="column-2">[2008] Opening explicitly specified database failed (16 in SQL 2005)</td>
	</tr>
	<tr class="even row-14">
		<td class="column-1">40</td><td class="column-2">[2008] Opening login default database failed (16 in SQL 2005)</td>
	</tr>
</tbody>
</table>
</p>
<p>Some of these are not obvious (some of them I have no idea about:), so here&#8217;s some help:</p>
<p>State 16 in SQL 2005, 38/40 in 2008 is caused most commonly by auto close databases. This is a great feature &#8211; for MSDE or Express but not for production. So I recommend you checking if you have any database with auto close enabled and if you have, turn this off &#8211; you have just problems with it. Other than this login issue (which will disappear if you retry as the database will be reopened in a few seconds, so a subsequent user can use it immediately) it pollutes your precious errorlog with messages like this: &#8220;<strong><em>Starting up database XYZ</em></strong>&#8221; .</p>
<p>State 11/12 are typical Windows login issues: SQL Server knows who tries to log in, authentication is successful, but there&#8217;s no SQL login for the Windows login, authorization fails.</p>
<p>This post was written based on the <a href="http://blogs.msdn.com/sql_protocols/archive/2006/02/21/536201.aspx">SQL Protocol team blog post</a> and the discussion after it, as it was pretty embarrassing that there&#8217;s not a single consolidated list of errors, but many of them should be collected from the comments &#8211; so I put the list together.</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.rollback.hu/2009/12/error-18456-level-14-state-sql-server-login-errors/feed/</wfw:commentRss>
		<slash:comments>4</slash:comments>
		</item>
		<item>
		<title>SQL Server and forgotten sysadmin passwords</title>
		<link>http://blog.rollback.hu/2009/08/sql-server-and-forgotten-sysadmin-passwords/</link>
		<comments>http://blog.rollback.hu/2009/08/sql-server-and-forgotten-sysadmin-passwords/#comments</comments>
		<pubDate>Mon, 31 Aug 2009 19:24:19 +0000</pubDate>
		<dc:creator>Erik</dc:creator>
				<category><![CDATA[English]]></category>
		<category><![CDATA[security]]></category>
		<category><![CDATA[sql2008]]></category>

		<guid isPermaLink="false">http://blog.rollback.hu/?p=111</guid>
		<description><![CDATA[Up to SQL 2005, the sysadmin role contains the local Adminstrators group by default (and by design), under the name BUILTIN\Administrators. This bothered some folks as it wasn’t secure enough for them, so the SQL 2008 asks you during the install who should be the member of the sysadmin role, no automatic membership granted to [...]]]></description>
			<content:encoded><![CDATA[<p>Up to SQL 2005, the sysadmin role contains the local Adminstrators group by default (and by design), under the name BUILTIN\Administrators. This bothered some folks as it wasn’t secure enough for them, so the SQL 2008 asks you during the install who should be the member of the sysadmin role, no automatic membership granted to local admins. This is a pretty well-known feature.</p>
<p>However, it it lesser known that if you start the SQL Server in a single user, minimal mode (sqlservr -m -c started from the directory of the program directory, you can see it at the installed service), the local admins become sysadmins, independently of their normal privileges. This can be a failsafe solution, for example in an over-hardened environment where someone accidentally lost the password of the renamed and disabled sa account, which was the only sysadmin. You can reset passwords, add new sysadmins or whatever you want.</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.rollback.hu/2009/08/sql-server-and-forgotten-sysadmin-passwords/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>SQL authentication and strict computer policy</title>
		<link>http://blog.rollback.hu/2009/07/sql-authentication-and-strict-computer-policy/</link>
		<comments>http://blog.rollback.hu/2009/07/sql-authentication-and-strict-computer-policy/#comments</comments>
		<pubDate>Sat, 11 Jul 2009 11:31:25 +0000</pubDate>
		<dc:creator>Erik</dc:creator>
				<category><![CDATA[English]]></category>
		<category><![CDATA[security]]></category>

		<guid isPermaLink="false">http://blog.rollback.hu/?p=102</guid>
		<description><![CDATA[(Magyarul itt)
Many people faced the upleasant message below while trying to connect to SQL Server with SQL authentication: &#8220;Login failed for user &#8216;(null)&#8217;. Reason: Not associated with a trusted SQL Server connection&#8221; The most typical reason for this is that the server is configured to allow only Windows logins, not SQL logins. The cure is [...]]]></description>
			<content:encoded><![CDATA[<p>(Magyarul <a href="http://blog.rollback.hu/2008/11/sql-integrate-authentication-es-a-szigoru-policy/">itt</a>)</p>
<p>Many people faced the upleasant message below while trying to connect to SQL Server with SQL authentication: <strong><em>&#8220;Login failed for user &#8216;(null)&#8217;. Reason: Not associated with a trusted SQL Server connection&#8221;</em></strong> The most typical reason for this is that the server is configured to allow only Windows logins, not SQL logins. The cure is pretty simple, open up server properties and change the authentication option from Windows only to SQL and Windows &#8211; and there you go.</p>
<p>Recently, I met the error message in a different situation. A few guys couldn&#8217;t connect to a server with Windows integrated authentication. They got the error message above. Others could connect without any issue. I tried to figure out what was going on: my first candidate was MTU size but as the authentication token size was the same size (actually, it was bigger for those who had no problem), I ruled this out. Then I checked if they could connect to a file share on the machine. This action failed and I found this in the security eventlog:</p>
<blockquote><p>
Event Type:	Failure Audit<br />
Event Source:	Security<br />
Event Category:	Logon/Logoff<br />
Event ID:	534<br />
Date:		8/3/2008<br />
Time:		3:04:30 PM<br />
User:		NT AUTHORITY\SYSTEM<br />
Computer:	SQL10<br />
Description:<br />
Logon Failure:<br />
 	Reason:	The user has not been granted the requested<br />
 		logon type at this machine<br />
 	User Name:	erik<br />
 	Domain:		MYDOMAIN<br />
 	Logon Type:	3<br />
 	Logon Process:	NtLmSsp<br />
 	Authentication Package:	NTLM<br />
 	Workstation Name:	ERIKPC
</p></blockquote>
<p>So what the heck does this mean? I asked my old pal Google, who said <a href="http://www.windowsecurity.com/articles/Logon-Types.html">he read about it once</a>, and people say it&#8217;s nothing else than network logon failure. So I decided to check the local security policy. And I found that in the User Rights Assignment node the <em>Access this computer from the network</em> privilege was granted only the local Administrators group. At that point I banged my head into the wall why I hadn&#8217;t found the pattern sooner and also extended this privilege to the users group as well. After a policy refresh, it worked.</p>
<p>Lookink back, it was pretty simple. As the <del>peons</del> users were not authorized to access the computer via the network, the OS tore and dropped their token so the SQL Server didn&#8217;t get anything from their identity. Once again I learnt something new.</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.rollback.hu/2009/07/sql-authentication-and-strict-computer-policy/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>A very slow SQL Server</title>
		<link>http://blog.rollback.hu/2009/06/a-very-slow-sql-server/</link>
		<comments>http://blog.rollback.hu/2009/06/a-very-slow-sql-server/#comments</comments>
		<pubDate>Mon, 01 Jun 2009 13:20:49 +0000</pubDate>
		<dc:creator>Erik</dc:creator>
				<category><![CDATA[English]]></category>
		<category><![CDATA[error]]></category>
		<category><![CDATA[üzemeltetés]]></category>

		<guid isPermaLink="false">http://blog.rollback.hu/?p=94</guid>
		<description><![CDATA[A few days ago I found that the replication into a database on one of our SQL 2005 Servers slowed down. The same publisher was replicated to multiple subscribers without any issue. I decided to check the database for blocking processes, because this db was used for reporting:

select * from sysprocesses
where
spid in (select blocked from [...]]]></description>
			<content:encoded><![CDATA[<p>A few days ago I found that the replication into a database on one of our SQL 2005 Servers slowed down. The same publisher was replicated to multiple subscribers without any issue. I decided to check the database for blocking processes, because this db was used for reporting:</p>
<pre class="brush: sql;">
select * from sysprocesses
where
spid in (select blocked from sysprocesses where spid &lt;&gt; blocked)
and dbid = db_id('MyDatabase')
</pre>
<p>(The where spid <> blocked clause was needed because from SQL 2000 SP4, the &#8220;self-blocking&#8221; is also listed as a blocking but it wasn&#8217;t interesting in this case.) I expected to spot one of our report writers running a bloated query with massive joins ans sophisticated filters but it wasn&#8217;t the case. I found nothing. I had a suspicion that if I restarted the server, this phenomenon would disappear but as the server was still in a usable state, I decided to dig deeper cause I hate mysteries unresolved.</p>
<p>I started to check the distribution agent history in Replication Monitor but the only thing I found was that the agents were very slow. I  checked them in the Activity Monitor and noticed that they were spending lot of time in RESOURCE_SEMAPHORE wait state. I checked the <a href="http://www.microsoft.com/technet/prodtechnol/sql/bestpractice/performance_tuning_waits_queues.mspx">excellent white paper about SQL Server wait types</a>, and saw that it usually means that due to high concurrent load, the query should wait for memory grant. So I checked the memory usage a bit.<br />
<span id="more-94"></span></p>
<p>In SQL 2000, I used DBCC MEMUSAGE, but it disappeared in SQL 2005. Good news is that there&#8217;s something even better. While DBCC MEMUSAGE told you the to 30 memory eater object, the sys.dm_os_buffer_descriptors DMV will tell you this info for every object you want. I picked the sample scripts below from Books Online at sys.dm_os_buffer_descriptors and queried both the server and the database level memory usage.</p>
<pre class="brush: sql;">
-- server level
SELECT count(*)AS cached_pages_count
    ,CASE database_id
        WHEN 32767 THEN 'ResourceDb'
        ELSE db_name(database_id)
        END AS Database_name
FROM sys.dm_os_buffer_descriptors
GROUP BY db_name(database_id) ,database_id
ORDER BY cached_pages_count DESC

-- and database level
SELECT count(*)AS cached_pages_count
    ,name ,index_id
FROM sys.dm_os_buffer_descriptors AS bd
    INNER JOIN
    (
        SELECT object_name(object_id) AS name
            ,index_id ,allocation_unit_id
        FROM sys.allocation_units AS au
            INNER JOIN sys.partitions AS p
                ON au.container_id = p.hobt_id
                    AND (au.type = 1 OR au.type = 3)
        UNION ALL
        SELECT object_name(object_id) AS name
            ,index_id, allocation_unit_id
        FROM sys.allocation_units AS au
            INNER JOIN sys.partitions AS p
                ON au.container_id = p.hobt_id
                    AND au.type = 2
    ) AS obj
        ON bd.allocation_unit_id = obj.allocation_unit_id
WHERE database_id = db_id()
GROUP BY name, index_id
ORDER BY cached_pages_count DESC
</pre>
<p>I got back that the 95% of the total server memory is used by my troublesome database and inside that, 95% (approx. 140K pages) is used by the table MSreplication_subscriptions. That surprised me as I was under the impression that that is a small table. I decided to query the table with some stats:</p>
<pre class="brush: sql;">
set statistics io on
select * from MSreplication_subscriptions
</pre>
<p>And got this:</p>
<pre class="brush: plain;">
(13 row(s) affected)
Table 'MSreplication_subscriptions'. Scan count 1, logical reads 142543, physical reads 1234, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
</pre>
<p>Shocking&#8230; 13 rows, 142000 reads and no LOB in the table&#8230; it wasn&#8217;t a perfect day for a broken database&#8230; I ran a DBCC on the table:</p>
<pre class="brush: sql;">
DBCC CHECKTABLE ('MSreplication_subscriptions')
</pre>
<p>It returned this info, 5 hours and 43 minutes later:</p>
<pre class="brush: plain;">
DBCC results for 'MSreplication_subscriptions'.
There are 13 rows in 147460 pages for object 'MSreplication_subscriptions'.
DBCC execution completed. If DBCC printed error messages, contact your system administrator.
</pre>
<p>I started to get confused&#8230; I thought something was broken, but DBCC didn&#8217;t scream and I couldn&#8217;t imagine how it could go so wrong that even DBCC failed to spot the error. So I decided to allow myself a break and left the computer. Needless to say, my brain couldn&#8217;t stop so in two minutes it came into my mind that I didn&#8217;t checked the physical table structure. I went back to query the index stats because the clustered index contains the records themselves on the leaf level, so querying the clustered index stats provides info about the table itself. For a moment I was thinking if the table just got heavily fragmented, but it was a silly idea, given that the page numbers significantly outnumbered the record numbers (and still no LOB). So I queried the index details:</p>
<pre class="brush: sql;">
SELECT * FROM
sys.dm_db_index_physical_stats(db_id(), object_id('dbo.MSreplication_subscriptions'),
null, null, 'detailed')
</pre>
<p>As you can see, it wasn&#8217;t even a DMV, but a DMF, that is, a dynamic management function. It returned everything I needed, and even more:<br />
<table id="wp-table-reloaded-id-1-no-1" class="wp-table-reloaded wp-table-reloaded-id-1" cellspacing="1" cellpadding="1" border="1">
<thead>
	<tr class="odd row-1">
		<th class="column-1">database_id</th><th class="column-2">object_id</th><th class="column-3">index_id</th><th class="column-4">partition_number</th><th class="column-5">index_type_desc</th><th class="column-6">alloc_unit_type_desc</th><th class="column-7">index_depth</th><th class="column-8">index_level</th><th class="column-9">avg_fragmentation_in_percent</th><th class="column-10">fragment_count</th><th class="column-11">avg_fragment_size_in_pages</th><th class="column-12">page_count</th><th class="column-13">avg_page_space_used_in_percent</th><th class="column-14">record_count</th><th class="column-15">ghost_record_count</th><th class="column-16">version_ghost_record_count</th><th class="column-17">min_record_size_in_bytes</th><th class="column-18">max_record_size_in_bytes</th><th class="column-19">avg_record_size_in_bytes</th><th class="column-20">forwarded_record_count</th>
	</tr>
</thead>
<tbody>
	<tr class="even row-2">
		<td class="column-1">6</td><td class="column-2">2073058421</td><td class="column-3">1</td><td class="column-4">1</td><td class="column-5">CLUSTERED INDEX</td><td class="column-6">IN_ROW_DATA</td><td class="column-7">5</td><td class="column-8">0</td><td class="column-9">98.1227621828284</td><td class="column-10">147942</td><td class="column-11">1.00244014546241</td><td class="column-12">148303</td><td class="column-13">47.3060785767235</td><td class="column-14">13</td><td class="column-15">1</td><td class="column-16"><b>1608002</b></td><td class="column-17">314</td><td class="column-18">374</td><td class="column-19">347.846</td><td class="column-20">NULL</td>
	</tr>
	<tr class="odd row-3">
		<td class="column-1">6</td><td class="column-2">2073058421</td><td class="column-3">1</td><td class="column-4">1</td><td class="column-5">CLUSTERED INDEX</td><td class="column-6">IN_ROW_DATA</td><td class="column-7">5</td><td class="column-8">1</td><td class="column-9">99.7432605905006</td><td class="column-10">3895</td><td class="column-11">1</td><td class="column-12">3895</td><td class="column-13">48.5910180380529</td><td class="column-14">148303</td><td class="column-15">0</td><td class="column-16">0</td><td class="column-17">11</td><td class="column-18">118</td><td class="column-19">101.346</td><td class="column-20">NULL</td>
	</tr>
	<tr class="even row-4">
		<td class="column-1">6</td><td class="column-2">2073058421</td><td class="column-3">1</td><td class="column-4">1</td><td class="column-5">CLUSTERED INDEX</td><td class="column-6">IN_ROW_DATA</td><td class="column-7">5</td><td class="column-8">2</td><td class="column-9">99</td><td class="column-10">100</td><td class="column-11">1</td><td class="column-12">100</td><td class="column-13">50.2309117865085</td><td class="column-14">3895</td><td class="column-15">0</td><td class="column-16">0</td><td class="column-17">11</td><td class="column-18">118</td><td class="column-19">102.433</td><td class="column-20">NULL</td>
	</tr>
	<tr class="odd row-5">
		<td class="column-1">6</td><td class="column-2">2073058421</td><td class="column-3">1</td><td class="column-4">1</td><td class="column-5">CLUSTERED INDEX</td><td class="column-6">IN_ROW_DATA</td><td class="column-7">5</td><td class="column-8">3</td><td class="column-9">0</td><td class="column-10">2</td><td class="column-11">1</td><td class="column-12">2</td><td class="column-13">64.6466518408698</td><td class="column-14">100</td><td class="column-15">0</td><td class="column-16">0</td><td class="column-17">11</td><td class="column-18">118</td><td class="column-19">102.69</td><td class="column-20">NULL</td>
	</tr>
	<tr class="even row-6">
		<td class="column-1">6</td><td class="column-2">2073058421</td><td class="column-3">1</td><td class="column-4">1</td><td class="column-5">CLUSTERED INDEX</td><td class="column-6">IN_ROW_DATA</td><td class="column-7">5</td><td class="column-8">4</td><td class="column-9">0</td><td class="column-10">1</td><td class="column-11">1</td><td class="column-12">1</td><td class="column-13">1.42080553496417</td><td class="column-14">2</td><td class="column-15">0</td><td class="column-16">0</td><td class="column-17">11</td><td class="column-18">102</td><td class="column-19">102.119</td><td class="column-20">NULL</td>
	</tr>
</tbody>
</table>
</p>
<p>At first sight, it&#8217;s a mess but it has a logic. Every row in the result set corresponds to a level of an index on a table (recall, indexes are in B-tree structure in SQL Server). We have a single, clustered index here, with 5 levels (see the idex_depth and index_level columns). The <strong>page_count</strong> cloumn shows the pages used by the level and the <strong>record_count</strong> returns the number of records. On level 0, that is, on the leaf level, we have 13 records but 148303 pages (table was growing all the time). For the solution, let&#8217;s check  the two additional alternative record count columns.</p>
<p>The <strong>ghost_record_count</strong> tells you how many records are in the pages physically which are already not there logically due to deletion (or update which requires more space). Such records are deleted by the GHOST_CLEANUP system process, so it might happen that we can&#8217;t see a value other than 0 in the column if the cleanup was wicked fast. The other column called <strong>version_ghost_record_count</strong> and counts those records which are logically not in the table but cannot be deleted yet due to an open transaction using row versioning (running on snapshot isolation level) and the original row has been changed. In our case, we have a single ghost record and 1608002 version ghost. It&#8217;s pretty tough for a table with 13 rows, even if 113 distribution agents belong to those 13 rows and they run continuously, updating at least once in a minute. So I checked for the oldest transaction:</p>
<pre class="brush: sql;">
DBCC OPENTRAN
</pre>
<pre class="brush: plain;">
Transaction information for database 'MyDatabase'.

Oldest active transaction:
    SPID (server process ID): 74
    UID (user ID) : -1
    Name          : user_transaction
    LSN           : (21:201:1)
    Start time    : May  13 2009  1:48:22:733PM
    SID           : 0x010500000000000515000000119d203ab2692ec2891a29a8ed030000
DBCC execution completed. If DBCC printed error messages, contact your system administrator.
</pre>
<p>And all of it happened on 28th May. I checked the process and found that it was a distribution agent, ran by a DISTRIB.EXE OS process. Given that I restarted the SQL Agent a few hours before and there was no replication latency a day before, I was pretty sure that it was a zombie agent since 13th May.</p>
<p>I killed the processz and the ghost_record_count went up to 1.5M, then an hour later when I checked back, the whole table was on 8 pages. Happy end.</p>
<p>Although it was a bit long post, I hope someone will benefit from this demonstration of the pretty huge armory of a DBA.</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.rollback.hu/2009/06/a-very-slow-sql-server/feed/</wfw:commentRss>
		<slash:comments>3</slash:comments>
		</item>
	</channel>
</rss>

