{"id":682,"date":"2009-09-29T00:00:00","date_gmt":"2009-09-29T00:00:00","guid":{"rendered":"https:\/\/test.simple-talk.com\/uncategorized\/finding-data-corruption\/"},"modified":"2021-08-24T13:40:35","modified_gmt":"2021-08-24T13:40:35","slug":"finding-data-corruption","status":"publish","type":"post","link":"https:\/\/www.red-gate.com\/simple-talk\/databases\/sql-server\/database-administration-sql-server\/finding-data-corruption\/","title":{"rendered":"Finding Data Corruption"},"content":{"rendered":"<div id=\"pretty\">\n<p>The corruption monster can be a silent and deceptive job killer. It can strike at once or lay in wait for weeks before launching an attack. No, I am not talking about a developer; I am talking about <span class=\"STBold\">database corruption<\/span>.<\/p>\n<p>If you have been a DBA for long enough, you will have encountered the data corruption monster in at least one of its many forms. Often corruption occurs when there is a power failure and the server, rather than shutting down gracefully, simply dies in the middle of processing data. As a result of this, or some other hardware malfunction, data or indexes become corrupt on disk and can no longer be used by SQL Server, until repaired.<\/p>\n<p>Fortunately, there are several steps you can take to protect your data, and equally important your job, in the event of data corruption. First and foremost, it should go without saying that not having a good backup strategy is equivalent to playing Solitary Russian Roulette. However, I&#8217;ll also demonstrate a few other techniques, based around the various DBCC commands, and a script that will make sure corruption issues are discovered and reported as soon as they occur, before they propagate through your data infrastructure. Hopefully, suitably armed, the DBA can limit the damage caused by this much-less-friendly version of the <i>monster at the end of the book<\/i>.<\/p>\n<p><span class=\"STItalic\">P.S.<\/span> If you are unfortunate enough never to have read <span class=\"STItalic\">The Monster at the End of This Book <\/span>(by Jon Stone, illustrated by Michael Smollin. Golden Books), starring the lovable Grover Monster from Sesame Street, you have firstly my sympathy and secondly my apologies, because the previous references will have meant little to you. I can only suggest you buy it immediately, along with <span class=\"STItalic\">The<\/span> <span class=\"STItalic\">Zombie Survival Guide <\/span>(by Max Brooks, Three Rivers Press), and add them both to your required reading list for all new DBAs.<\/p>\n<h1>Causes of corruption<\/h1>\n<p>There are many ways that a database can become &#8220;corrupt&#8221;. Predominantly it happens when a hardware malfunction occurs, typically in the disk subsystem that is responsible for ensuring that the data written to disk is the exact same data that SQL Server expected to be written to disk when it passed along this responsibility to the operating system, and subsequently the disk controller driver and disk itself. For example, I have seen this sort of data corruption caused by a power outage in the middle of a transaction.<\/p>\n<p>However, it is not just disk subsystem failures that cause data corruption. If you upgrade a database from SQL Server 2000 to SQL Server 2005 or 2008, and then interrogate it using the corruption-seeking script provided in this article, you may be surprised to find that you will receive what can be construed as errors in the database files. However, fortunately these are just warnings regarding space usage between versions, and there are recommended steps to address the issue, such as running <code>DBCC<\/code> <code>UPDATEUSAGE<\/code>.<\/p>\n<p>Whatever the cause, the DBA does not want to live in ignorant bliss of possible corruption for any length of time. Unfortunately, the corruption monster is often adept at hiding, and will not rear its head until you interact with the corrupt data. By this time, the corruption may have worked its way into your backup files and, when falling through to your last resort of restoring the database, you may simply restore the same corruption. The importance of a solid, regular backup strategy cannot be overstated (so I will state it quite often). On top of that, you need a script or tool that will regularly check, and report on any corruption issues, before it&#8217;s too late. I&#8217;ll provide just such a script in this article.<\/p>\n<h1>Consequences of corruption<\/h1>\n<p>As noted in the previous section, most of the time corruption occurs due to failure in an external hardware source, like a hard disk controller or power supply. SQL Server 2005, and later, uses a feature called <span class=\"STBold\">Page Checksum<\/span> to detect potential problems that might arise from this. This feature creates a checksum value during writes of pages to, and subsequent reads from, disk. Essentially, if the checksum value read for a page does not match what was originally written, then SQL Server knows that the data was modified outside of the database engine. Prior to SQL Server 2005, but still included as an option, is <span class=\"STBold\">Torn Page Detection<\/span>, which performs similar checks.<\/p>\n<p>If SQL Server detects a corruption issue, it&#8217;s response to the situation will vary depending on the scale of the damage. If the damage is such that the database is unreadable by SQL Server then it would be unable to initialize and load that database. This would require a complete restore of the database in almost all cases.<\/p>\n<p>If the damage is more contained, perhaps with only one or two data pages being affected, then SQL Server should still be able to read and open the database, and at that stage we can use tools such as DBCC to assess and hopefully repair the damage. Bear in mind, too, that as part of your overall backup and restore procedure, you have the ability to perform a page level restore, if perhaps you only need to restore 1 or more data pages. For additional information on restoring pages from database backups, please see: <a href=\"http:\/\/msdn.microsoft.com\/en-us\/library\/ms175168.aspx\">http:\/\/msdn.microsoft.com\/en-us\/library\/ms175168.aspx<\/a><\/p>\n<p>Before moving on, I should note that, while I typically leave these options enabled for all instances, both Torn Page Detection and Page Checksum incur overhead and it is possible to disable them. The idea is that if you trust your disk subsystem and power environment then you may not need to have these options turned on, if performance is the highest concern. Most disk subsystems today have battery backup to ensure write activity completes successfully.<\/p>\n<p>You can use <code>sp_dboption<\/code> for SQL 2000 to enable or disable Torn Page Detection. For SQL Server 2005, and above, you can use the <code>ALTER DATABASE<\/code> command to enable either <code>torn<\/code> <code>page<\/code> <code>detection<\/code> or <code>checksum<\/code> (you are not permitted to have both on at the same time), or you can use <code>none<\/code> to disable them both.<\/p>\n<h1>Fighting corruption<\/h1>\n<p>Aside from having frequent and tested backups, so that you can at least return to a version of the data from the recent past, if the absolute worst happens, the well-prepared DBA will have some tools in his tacklebox that he can use to pinpoint the location of, and hopefully repair, any corrupt data.<\/p>\n<p>However, before I dive in with the equivalent of a machete in a bayou, I should let you know that I am by no means an expert in database corruption. Like you, I am a just a day-to-day DBA hoping with all hope that I do not encounter corrupt databases, but wanting to be as well-prepared as I can be in case it happens.<\/p>\n<p>As such, I&#8217;m going to maintain my focus on the practicalities of the tools and scripts that a DBA can use to fight corruption, mainly revolving around the use of the DBCC family of commands.<\/p>\n<p>I will not dive too deeply into the bowels of the SQL Server storage engine, where one is likely to encounter all manner of esoteric terms that refer to how SQL Server allocates or maps data in the physical file, such as GAM pages (Global Allocation Map), SGAM, pages (Shared GAM), PFS pages (Page Free Space), IAM chains (Index Allocation Map), and more. For this level of detail I can do no better than to point you towards the work of Paul Randal:<\/p>\n<p><a href=\"https:\/\/www.sqlskills.com\/blogs\/paul\/category\/Corruption\/\">https:\/\/www.sqlskills.com\/blogs\/paul\/category\/Corruption\/<\/a><\/p>\n<p>He has done a lot of work on the DBCC tool, is a true expert on the topic of data corruption, and is certainly the man with the most oxygen in the tank for the required dive.<\/p>\n<h2>DBCC CHECKDB<\/h2>\n<p><code>DBCC<\/code> <code>CHECKDB<\/code> is the main command the DBA will use to test and fix consistency errors in SQL Server databases.\u00a0 DBCC has been around for many years, through most versions of SQL Server. Depending on who you ask, it stands for either <span class=\"STBold\">Database Consistency Checks<\/span> or <span class=\"STBold\">Database Console Commands<\/span>, the latter of which is more accurate since DBCC includes commands that fall outside the scope of just checking the consistency of a database.<\/p>\n<p>For our purpose, though, we are concerned only with consistency and integrity of our databases. <code>DBCC<\/code> <code>CHECKDB<\/code> is actually an amalgamation of other DBCC commands, <code>DBCC CHECKCATALOG<\/code>, <code>DBCC<\/code> <code>CHECKALLOC<\/code> and <code>DBCC<\/code> <code>CHECKTABLE<\/code>. Running <code>DBCC<\/code> <code>CHECKDB<\/code> includes these other commands so negates the need to run them separately.<\/p>\n<div class=\"float-left\">\n<p class=\"ILLUSTRATION\"><img decoding=\"async\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/816-findin1.jpg\" alt=\"816-findin1.jpg\" \/><\/p>\n<p class=\"CAPTION\">Figure 1: New database NEO with no objects.<\/p>\n<\/div>\n<p>In order to demonstrate how to use this, and other tools, to seek out and repair data corruption, I&#8217;m first going to need to create a database, and then perform the evil deed of despoiling the data within it. If we start from scratch, it will make it easier to find and subsequently corrupt data and\/or index pages, so let&#8217;s create a brand new, unsullied database, aptly named &#8220;Neo&#8221;. As you can see in Figure 1, there are no objects created in this new database. It is pristine.<\/p>\n<p>Just to prove that <code>NEO<\/code> is not yet corrupt, we can run the <code>DBCC<\/code> <code>CHECKDB<\/code> command, the output of which is shown in Figure 2.<\/p>\n<p class=\"ILLUSTRATION\"><img decoding=\"async\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/816-findin13.jpg\" alt=\"816-findin13.jpg\" \/><\/p>\n<p class=\"CAPTION\">Figure 2: No reported errors with database NEO.<\/p>\n<p>As expected, there are no reported consistency or allocation errors, but that will all change very shortly. I had mentioned that there is a monster at the end of this book and it is not lovable old Grover from Sesame Street.<\/p>\n<p><span class=\"STItalic\">Please<\/span> do not go on to the next page!<\/p>\n<h2>DBCC PAGE<\/h2>\n<p>Aha, you are still reading I see. Well, before we unleash the monster, I want to show you one more very important DBCC command, of which you may not be aware, namely <code>DBCC<\/code> <code>PAGE<\/code>. It&#8217;s &#8220;officially&#8221; undocumented, in that Microsoft does not support it, but in reality I have found piles of information on this command from well known and respected sources, like Paul Randal, so I no longer consider it undocumented.<\/p>\n<p>The syntax is simple:<\/p>\n<pre>dbcc page ( {'dbname' | dbid}, filenum, pagenum [, printopt={0|1|2|3} ])\r\n<\/pre>\n<p>However, the output of the command can be quite daunting to the uninitiated DBA. So before we introduce the monster that corrupts databases, I want to run <code>DBCC<\/code> <code>PAGE<\/code> against the <code>NEO<\/code> database. The command is as follows:<\/p>\n<pre>DBCC PAGE (NEO,1,1,3)\r\n<\/pre>\n<p>The first &#8220;1&#8221; is the file number of the data file, the second &#8220;1&#8221; is the page number, and the final &#8220;3&#8221; is the print option which, depending on value chosen (0-3) returns differing levels of information. A value of &#8220;3&#8221; indicates that we want to see both page header information, as well as details. The not-very-exciting results are shown in Figure 3.<\/p>\n<p class=\"ILLUSTRATION\"><img decoding=\"async\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/816-findin14.jpg\" alt=\"816-findin14.jpg\" \/><\/p>\n<p class=\"CAPTION\">Figure 3: DBCC PAGE default results.<\/p>\n<p>The reason that they are not very exciting is that we forgot to turn on an important trace flag (<code>3604<\/code>). If you are a SQL Server and not familiar with trace flags then please give me a call and we can talk over a beer or two. Really, I do not mind and I would welcome the camaraderie and chance to be pedantic.<\/p>\n<p>For now, though, I&#8217;ll simply note that in order to see output of the <code>DBCC PAGE<\/code> command, we need to run another DBCC command called <code>DBCC TRACEON<\/code>. Specifically:<\/p>\n<pre>DBCC TRACEON (3604)\r\n<\/pre>\n<p>Figure 4 shows the output from rerunning <code>DBCC PAGE<\/code>, with this trace flag turned on.<\/p>\n<p class=\"ILLUSTRATION\"><img decoding=\"async\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/816-findin2.jpg\" alt=\"816-findin2.jpg\" \/><\/p>\n<p class=\"CAPTION\">Figure 4: DBCC PAGE with trace flag 3604 turned on.<\/p>\n<p>At the bottom of the output I can see that pages 1:172 &#8211; 1:383 are not allocated, and all pages are 0% full. Recall, this is a database with no tables or any other objects created and with no data inserted.<\/p>\n<p>So, let&#8217;s now create a simple table and insert some data into it. The script to do this in is shown in Listing 1. It creates a table in the <code>NEO<\/code> database, called <code>ONE<\/code>, and inserts into it 1000 records (well, 999 really). Simple stuff, but the important point in the context of this example is that this data load will cause additional pages to be allocated to the database and be filled with data, and I&#8217;ll be able to home in on these new pages.<\/p>\n<pre>USE [NEO]\r\nGO\r\n\u00a0\r\nIF\u00a0 EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[ONE]') AND type in (N'U'))\r\nDROP TABLE [dbo].[ONE]\r\nGO\r\n\u00a0\r\nCREATE TABLE [dbo].[ONE](\r\n\u00a0\u00a0 [NEOID] [int] NULL,\r\n\u00a0\u00a0 [NEOTEXT] [nchar](50) NULL\r\n) ON [PRIMARY]\r\n\u00a0\r\nGO\r\n\u00a0\r\nBEGIN Tran T_Time\r\n\u00a0\r\nDECLARE @SQL_Alphabet varchar(26)\r\nSET @SQL_Alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'\r\nDECLARE @rnd_seed int\r\nSET @rnd_seed = 26\r\nDECLARE @counter int = 1\r\nWHILE @counter &lt; 1000\r\n\u00a0\u00a0 BEGIN\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \r\n\u00a0\u00a0\u00a0\u00a0 Insert\u00a0 Into ONE \r\n\u00a0\u00a0\u00a0\u00a0 Values (\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 @counter,\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 (select SUBSTRING (@SQl_alphabet,\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 Cast(RAND() * @rnd_seed as int) + 1,\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 CAST(RAND() * @rnd_seed as int) + 1)\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 )\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 )\r\n\u00a0\u00a0\u00a0\u00a0 SET @counter = @counter + 1\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \r\n\u00a0\u00a0 END \r\nCommit Tran T_Time\r\n<\/pre>\n<p class=\"CAPTION\">Listing 1. Creating and populating the ONE table.<\/p>\n<p>Figure 5 shows the sample data that was inserted.<\/p>\n<p class=\"ILLUSTRATION\"><img decoding=\"async\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/816-findin3.jpg\" alt=\"816-findin3.jpg\" \/><\/p>\n<p class=\"CAPTION\">Figure 5: Sample data in the ONE table.<\/p>\n<p>From Figure 4, I already know that, for our empty database, pages 1:172 &#8211; 1:383 were unallocated. Re-running <code>DBCC<\/code> <code>PAGE<\/code> should reveal that more pages have been allocated to accommodate this data, and that those pages have different percentages of fullness. Figure 6 shows the new results.<\/p>\n<p class=\"ILLUSTRATION\"><img decoding=\"async\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/816-findin4.jpg\" alt=\"816-findin4.jpg\" \/><\/p>\n<p class=\"CAPTION\">Figure 6: New Pages added to NEO database after loading data.<\/p>\n<p>I can see that pages 1:184 &#8211; 1:189, for example, are now allocated and are 100 percent full. Having identified one of the new pages (1:184) that contains the data that I just loaded, I can run <code>DBCC PAGE<\/code> again for that specific page and return a basket full of information, as shown in Figure 7.<\/p>\n<p class=\"ILLUSTRATION\"><img decoding=\"async\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/816-findin5.jpg\" alt=\"816-findin5.jpg\" \/><\/p>\n<p class=\"CAPTION\">Figure 7. Individual records from page 1:184.<\/p>\n<p>I can see, for example, that it returns the actual value for both <code>NEOID<\/code> and <code>NEOTEXT<\/code>, 553 and UVWXYZ respectively. It also returns a hex dump (<code>10006c00 29020000...<\/code>) that specifies the specific location in the data file where the record with <code>NEOID<\/code> 533 is stored.<\/p>\n<p>If you are not an expert in reading hexadecimal then fear not; neither am I at this point. I do know, however, that using this information I will be able to find this exact same record and modify it outside of SQL Server, which will really wreak some havoc. For that however, I will need my trusty hexadecimal editor, which I will discuss shortly.<\/p>\n<h2>Corruption on data pages<\/h2>\n<p>We know that our <code>ONE<\/code> table, in the <code>NEO<\/code> database, is a heap, so any corruption we induce is going to be directly on the data pages, rather than on any non-clustered index.<\/p>\n<p>The latter case is actually more favorable as the data in the index is a &#8220;duplicate&#8221; and so it is relatively easy to repair the damage. We&#8217;ll cover this latter case after we&#8217;ve looked at inducing, and hopefully recovering from, corruption of the data in our heap table.<\/p>\n<h3>Putting a Hex on the data<\/h3>\n<p>There are many hexadecimal editors out there in the world, many of them free or at least free to try out. For this article, I downloaded a trial version of one called, ironically, <span class=\"STBold\">Hex Editor Neo<\/span>, by HHD Software.<\/p>\n<p>What a Hexadecimal editor allows the DBA to do is simply open and view the contents of a file, in this case the data file. While it is an interesting exercise, I would only recommend it for testing or training purposes as it is a very dangerous tool in inexperienced hands.<\/p>\n<p>What I want to do here is use this hexadecimal editor to &#8220;zero out&#8221; data in a single database file, in fact in a single data page. This will cause the required corruption, mimicking a hardware problem that has caused inconsistent information to be written to disk, without making the database unreadable by SQL Server.<\/p>\n<p>And though I have not stated it heretofore &#8230;<\/p>\n<p><span class=\"STBold\">Do not go any further without first backing up the database!<\/span><\/p>\n<p>The data that I am fixing (that is a Southern expression) to zero out resides on the data page revealed in Figure 7, namely <span class=\"STBold\">1:184<\/span>. In order to corrupt the data on this page, I first need to shutdown SQL Server, so that the parent data file, <code>C:\\Program Files\\Microsoft SQL Server\\MSSQL.1\\MSSQL\\Data\\NEO.mdf<\/code>, is not in use.<\/p>\n<p>Next, I simply open Hex Editor Neo and find the location of the one record with <code>NEOID= 553<\/code> and <code>NEOTEXT =\"UVWXYZ\"<\/code>, that we identified using the <code>DBCC PAGE<\/code> previously.<\/p>\n<p>Most hexadecimal editors, Hex Editor Neo included, have the ability to search for values within the data file. Here, referring back to the <code>DBCC PAGE<\/code> information for page 1:184, I simply search for the value <code>10006c00 29020000<\/code> to find record 553. As you can see in Figure 8, the record in the Hex editor looks almost identical to the output of the previous <code>DBCC<\/code> <code>PAGE<\/code> command.<\/p>\n<p class=\"ILLUSTRATION\"><img decoding=\"async\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/816-findin6.jpg\" alt=\"816-findin6.jpg\" \/><\/p>\n<p class=\"CAPTION\">Figure 8: Opening the database file in Hex Editor Neo.<\/p>\n<p>Next, I am simply going to make just one small change to the data, zeroing out &#8220;U&#8221; in the record, by changing 55 to 00. That is it. Figure 9 shows the change.<\/p>\n<p class=\"ILLUSTRATION\"><img decoding=\"async\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/816-findin7.jpg\" alt=\"816-findin7.jpg\" \/><\/p>\n<p class=\"CAPTION\">Figure 9: Zeroing out a valid data value.<\/p>\n<p>Next I save the file, and close the Hex editor, which you have to do otherwise the date file will be in use and you will be unable to initialize the database, and start SQL Server. Now, at last, we are about to unleash the monster &#8230;<\/p>\n<h3>Confronting the Corruption Monster<\/h3>\n<p>At first glance all appears fine. The <code>NEO<\/code> database is up and available, and no errors were reported in the Event Log. In Management studio, I can drill into the objects of the database, including the <code>ONE<\/code> table, without issue. However, if I try to query the table with <code>SELECT<\/code> <code>*<\/code> <code>FROM<\/code> <code>ONE<\/code>, something frightening happens, as shown in Listing 2.<\/p>\n<pre>Msg 824, Level 24, State 2, Line 1\r\nSQL Server detected a logical consistency-based I\/O error: incorrect checksum (expected: 0x9a3e399c; actual: 0x9a14b99c).\r\nIt occurred during a read of page (1:184) in database ID 23 at offset 0x00000000170000 in file \r\n'C:\\Program Files\\Microsoft SQL Server\\MSSQL.1\\MSSQL\\DATA\\NEO.mdf'.\u00a0 Additional messages in the SQL Server error log or system event log may provide more detail. This is a severe error condition that threatens database integrity and must be corrected immediately. \r\nComplete a full database consistency check (DBCC CHECKDB). This error can be caused by many factors; for more information, see SQL Server Books Online.\r\n<\/pre>\n<p class=\"CAPTION\">Listing 2: Corruption strikes the <code>ONE<\/code> table.<\/p>\n<p>This is indeed the horror show that DBAs do not want to see. It is obviously a very severe error and major corruption. This error will be thrown each time record 553 is included in the query results, and so any table scan will reveal the problem.<\/p>\n<p>This has to be fixed quickly. Fortunately, we took a backup of the database prior to corrupting the data file so if all else fails I can resort to that backup file to restore the data. It is critical, when dealing with corruption issues, that you have known good backups. Unfortunately, in the real world, it&#8217;s possible this corruption could have gone undetected for many days, which will mean that your backups will also carry the corruption.<\/p>\n<p>If this is the case then, at some point you may be faced with accepting the very worst possible scenario, namely data loss. Before accepting that fate, however, I am going to ace down the monster, and see if I can fix the problem using <code>DBCC<\/code> <code>CHECKDB<\/code>.<\/p>\n<p>There are many options for <code>DBCC<\/code> <code>CHECKDB<\/code> and I&#8217;ll touch on only a few of them here. <code>DBCC<\/code> <code>CHECKDB<\/code> has been enhanced many times in its life and received major re-writes for SQL Server 2005 and above. One of the best enhancements for the lone DBA, working to resolve corruption issues, is the generous proliferation of more helpful error messages.<\/p>\n<p>So, let&#8217;s jump in and see how bad the situation is and what, if anything, can be done about it. To begin, I will perform a limited check of the physical consistency of the database, with the following command:<\/p>\n<pre>DBCC CHECKDB('neo') WITH PHYSICAL_ONLY;\r\nGO\r\n<\/pre>\n<p>Figure 10 shows the results which are, as expected, not great.<\/p>\n<p>The worst outcome is the penultimate line, which tells me that <code>REPAIR_ALLOW_DATA_LOSS<\/code> is the minimal repair level for the errors that were encountered. This means that we can repair the damage by running <code>DBCC<\/code> <code>CHECKDB<\/code> with the <code>REPAIR_ALLOW_DATA_LOSS<\/code> option but, as the name suggests, it will result in data loss.<\/p>\n<div class=\"float-right\">\n<p class=\"ILLUSTRATION\"><img decoding=\"async\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/816-findin8.jpg\" alt=\"816-findin8.jpg\" \/><\/p>\n<p class=\"CAPTION\">Figure 10: The DBCC report on the corruption.<\/p>\n<\/div>\n<p>There are two other repair levels that we would have preferred to see: <code>REPAIR_FAST<\/code> or <code>REPAIR_REBUILD.<\/code> The former is included for backward compatibility and does not perform repairs of 2005 database. If the minimal repair option had been <code>REPAIR_REBUILD<\/code>, it would have indicated that the damage was limited to, for example, a non-clustered index. Such damage can be repaired by rebuilding the index, with no chance of data loss.<\/p>\n<p>In general, it is recommended that you use the repair options of <code>DBCC CHECKDB<\/code> that may cause data loss only as a last resort, a restore from backup being the obvious preferable choice, so that the data will remain intact. This, of course, requires that the backup itself be uncorrupt.<\/p>\n<p>For this exercise, however, I am going to act on the information provided by <code>DBCC<\/code> <code>CHECKDB<\/code> and run the minimal repair option, <code>REPAIR_ALLOW_DATA_LOSS<\/code>. The database will need to be in single user mode to perform the repair, so the syntax will be:<\/p>\n<pre>ALTER DATABASE NEO SET SINGLE_USER WITH ROLLBACK IMMEDIATE\r\nGO\r\nDBCC CHECKDB('neo', REPAIR_ALLOW_DATA_LOSS) \r\nGO\r\n<\/pre>\n<p>The results of running the <code>DBCC<\/code> <code>CHECKDB<\/code> command are as shown in Listing 3.<\/p>\n<pre>DBCC results for 'ONE'.\r\nRepair: The page (1:184) has been deallocated from object ID 2121058592, index ID 0, partition ID 72057594039042048, alloc unit ID 72057594043301888 (type In-row data).\r\nMsg 8928, Level 16, State 1, Line 1\r\nObject ID 2121058592, index ID 0, partition ID 72057594039042048, alloc unit ID 72057594043301888 (type In-row data): Page (1:184) could not be processed.\u00a0 See other errors for details.\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 The error has been repaired.\r\nMsg 8939, Level 16, State 98, Line 1\r\nTable error: Object ID 2121058592, index ID 0, partition ID 72057594039042048, alloc unit ID 72057594043301888 (type In-row data), page (1:184). Test (IS_OFF (BUF_IOERR, pBUF-&gt;bstat)) failed. Values are 29362185 and -4.\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 The error has been repaired.\r\nThere are 930 rows in 14 pages for object \"ONE\".\r\n<\/pre>\n<p class=\"CAPTION\">Listing 3: The error is repaired, but data is lost.<\/p>\n<p>The good news is that the errors have now been repaired. The bad news is that it took the data with it, deallocating the entire data page from the file. Notice, in passing, that the output shows an object ID for the table on which the corruption occurred, and also an index ID, which in this case is 0 as there are no indexes on the table.<\/p>\n<div class=\"float-left\">\n<p class=\"ILLUSTRATION\"><img decoding=\"async\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/816-findin9.jpg\" alt=\"816-findin9.jpg\" \/>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0<\/p>\n<p>\u00a0\u00a0\u00a0\u00a0\u00a0<\/p>\n<p>&nbsp;<\/p>\n<p class=\"CAPTION\">Figure 11: Missing data After <br \/>\nDBCC CHECKDB Repair_Allow_Data_Loss.<\/p>\n<\/div>\n<p>So, at this point, I know that I&#8217;ve lost data, and it was for a data page, but only one page; but how much data exactly? A simple <code>SELECT<\/code> statement reveals that not only have I lost the row I tampered with (<code>NEOID<\/code> 553), but also another 68 rows, up to row 621. Figure 11 rubs it in my face.<\/p>\n<p>These rows should be easily recovered if you have a good backup. You have a good backup, right? <span class=\"STItalic\">Right<\/span>? Assuming you do, then you are faced with the task of restoring from backup to another database, like <code>NEO2<\/code>, and syncing the two tables for the missing rows. Syncing the two tables can be accomplished with a simple <code>INSERT INTO<\/code> statement, like that shown in Listing 4.<\/p>\n<pre>INSERT\u00a0 INTO NEO..ONE ( NEOID, NEOTEXT )\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 SELECT\u00a0 NEOID,\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 NEOTEXT\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 FROM\u00a0\u00a0\u00a0 NEO2..ONE\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 WHERE\u00a0\u00a0 NEOID NOT IN ( SELECT\u00a0\u00a0 NEOID\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 FROM\u00a0\u00a0\u00a0\u00a0 NEO..ONE )\r\n<\/pre>\n<p class=\"CAPTION\">Listing 4: Syncing two tables to recover lost data rows.<\/p>\n<p>In this &#8220;controlled example&#8221;, the fix is fairly simple. Other scenarios, with much higher levels of corruption, may require you to turn to other measures to get the data back, after repairing with data loss. These means will almost always involve a restore of the database from backup, which is why I impress again the importance of a solid, verified and well documented database backup policy.<\/p>\n<h2>Corruption on non-clustered indexes<\/h2>\n<p>I noted earlier that corruption of a non-clustered index is much easier to deal with than corruption of an actual data page, as these indexes are just &#8220;redundancies&#8221; of the actual data and can be easily rebuilt. However, it would be interesting to prove this point. I&#8217;ll use the same Hexadecimal editor technique to corrupt the non-clustered index, and not the data, and see what the outcome would be.<\/p>\n<p>One indicator of whether the corruption is on an index or a table is the <code>IndexID<\/code> provided with the DBCC output. For our <code>ONE<\/code> heap table, I noted (in Listing 3) that the <code>IndexID<\/code> was 0 as there were no indexes defined for the table. An <code>IndexID<\/code> of 1 means a clustered index and a value of 2-250 indicates a non-clustered index.<\/p>\n<p>For the sake of brevity, let&#8217;s assume that I have performed the necessary repair on the <code>NEOID<\/code> column and created a non-clustered index on the <code>ONE<\/code> table, for the <code>NEOID<\/code> column.<\/p>\n<p>First, I need to find out the page value of the index I defined for the <code>ONE<\/code> table. I will then plug this page of the non-clustered index into <code>DBCC<\/code> <code>PAGE<\/code> so that I know, again, exactly what data to modify to simulate index corruption, instead of data page corruption of the heap.<\/p>\n<p>To retrieve the page value of the index, I can use another DBCC command, call it undocumented again, <code>DBCC<\/code> <code>INDID<\/code>. The syntax for this command is:<\/p>\n<pre>DBCC INDID (DBID, TABLEID,-1)\r\n<\/pre>\n<p>So, to execute this for my newly-indexed <code>ONE<\/code> table, the command will be:<\/p>\n<pre>DBCC ind(23, 2121058592, -1)\r\n<\/pre>\n<p>The results reveal several <code>IndexID<\/code>s, mostly zero, along with several <code>IndexID<\/code> values of 2, indicating a non-clustered index. Notice in Figure 11 the <code>IndexID<\/code> of 2 and the associated page of that index, 180.<\/p>\n<p class=\"ILLUSTRATION\"><img decoding=\"async\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/816-findin10.jpg\" alt=\"816-findin10.jpg\" \/><\/p>\n<p class=\"CAPTION\">Figure 12: Finding the page of the new non-clustered index.<\/p>\n<p>I can now run <code>DBCC<\/code> <code>PAGE<\/code> again, plugging in this page information:<\/p>\n<pre>DBCC TRACEON (3604);\r\nGO\r\nDBCC PAGE (NEO,1,180,3)\r\nGO\r\n<\/pre>\n<p>The results look a lot different than when looking at a data page. I see returned the Hexadecimal value (<code>HEAP<\/code> <code>RID<\/code>) that represents each row in the index for the page interrogated, as shown in Figure 12.<\/p>\n<p class=\"ILLUSTRATION\"><img decoding=\"async\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/816-findin11.jpg\" alt=\"816-findin11.jpg\" \/><\/p>\n<p class=\"CAPTION\">Figure 13: Looking at the non-clustered index for the ONE table with DBCC PAGE.<\/p>\n<p>I used the Hex editor again to modify, or zero out, the <code>HEAP<\/code> <code>RID<\/code>, and once again this does indeed corrupt the database in much the same way as changing an actual data page. However, there is one major difference: this time, when I run <code>DBCC CHECKDB('neo') WITH PHYSICAL_ONLY<\/code>, the <code>IndexID<\/code> of the corrupt object is reported as &#8220;2&#8221; i.e. a non-clustered index.<\/p>\n<p>Armed with this knowledge, I have open to me options for repairing the damage, other than restoring from backup, or running <code>DBCC CHECKDB<\/code> with <code>REPAIR_ALLOW_DATA_LOSS<\/code>, with the potential loss of data that this entails.<\/p>\n<p>I can simply drop and recreate the non-clustered index using the code in Listing 5.<\/p>\n<pre>USE [NEO]\r\nGO\r\n\u00a0\r\nIF\u00a0 EXISTS (SELECT * FROM sys.indexes WHERE object_id = OBJECT_ID(N'[dbo].[ONE]')\r\n\u00a0AND name = N'NEO_ID_NC')\r\nDROP INDEX [NEO_ID_NC] ON [dbo].[ONE] WITH ( ONLINE = OFF )\r\nGO\r\n\u00a0\r\nUSE [NEO]\r\nGO\r\n\u00a0\r\nCREATE NONCLUSTERED INDEX [NEO_ID_NC] ON [dbo].[ONE] \r\n(\r\n\u00a0\u00a0 [NEOID] ASC\r\n)WITH (PAD_INDEX\u00a0 = OFF, STATISTICS_NORECOMPUTE\u00a0 = OFF, SORT_IN_TEMPDB = OFF,\r\n\u00a0IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS\u00a0 = ON, \r\n\u00a0ALLOW_PAGE_LOCKS\u00a0 = ON) ON [PRIMARY]\r\nGO\r\n<\/pre>\n<p class=\"CAPTION\">Listing 5: Drop and recreate corrupt non-clustered index.<\/p>\n<p>Now that I have delved somewhat into corrupting, finding and fixing some problems, let&#8217;s turn now to the discovery process.<\/p>\n<h1>Seeking out corruption<\/h1>\n<p>What is the best way for you to find out that you have corruption on your databases, before it propagates through numerous backups and causes bigger issues than it need do?<\/p>\n<p>One option is to set up regular integrity checks using Maintenance Plans, which are useful, and certainly better than not having any integrity checks at all. However, I enjoy the level of control and flexibility I have when building custom scripts to perform the same functions as the maintenance plans. As such, rather than delve into maintenance plans, I will instead share with you a script that I use to iterate through each database, including system databases, and report on any errors returned by <code>DBCC<\/code> <code>CHECKDB<\/code>.<\/p>\n<p>With this code, and an easy way to read the error logs where the <code>DBCC<\/code> <code>CHECKDB<\/code> results will be written (which I cover in <a href=\"http:\/\/www.amazon.com\/SQL-Server-Tacklebox-Rodney-Landrum\/dp\/1906434255\">Chapter 7<\/a> of my book), you will be comforted by the knowledge that you will not let corruption seep into your data infrastructure and go unnoticed. And that you can act thoughtfully to resolve the issue, once discovered.<\/p>\n<p>The custom query, in Listing 6, will iterate through all databases on a SQL Server instance, capture errors and mail the top error to you so that you can look further into the matter.<\/p>\n<pre class=\"theme:ssms2012 lang:tsql\">CREATE TABLE #CheckDBTemp (\r\n\u00a0\u00a0\u00a0\u00a0 Error\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 INT\r\n\u00a0\u00a0 , [Level]\u00a0\u00a0\u00a0\u00a0\u00a0 INT\r\n\u00a0\u00a0 , [State]\u00a0\u00a0\u00a0\u00a0\u00a0 INT\r\n\u00a0\u00a0 , MessageText\u00a0\u00a0 NVARCHAR(1000)\r\n\u00a0\u00a0 , RepairLevel\u00a0\u00a0 NVARCHAR(1000)\r\n\u00a0\u00a0 , [Status]\u00a0\u00a0\u00a0\u00a0\u00a0 INT\r\n\u00a0\u00a0 , [DBID]\u00a0\u00a0\u00a0\u00a0\u00a0 INT\r\n\u00a0\u00a0 , ObjectID\u00a0\u00a0\u00a0\u00a0\u00a0 INT\r\n\u00a0\u00a0 , IndexID\u00a0\u00a0\u00a0\u00a0\u00a0 INT\r\n\u00a0\u00a0 , PartitionID\u00a0\u00a0 BIGINT\r\n\u00a0\u00a0 , AllocUnitID\u00a0\u00a0 BIGINT\r\n\u00a0\u00a0 , [File]\u00a0\u00a0\u00a0\u00a0\u00a0 INT\r\n\u00a0\u00a0 , Page\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 INT\r\n\u00a0\u00a0 , Slot\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 INT\r\n\u00a0\u00a0 , RefFile\u00a0\u00a0\u00a0\u00a0\u00a0 INT\r\n\u00a0\u00a0 , RefPage\u00a0\u00a0\u00a0\u00a0\u00a0 INT\r\n\u00a0\u00a0 , RefSlot\u00a0\u00a0\u00a0\u00a0\u00a0 INT\r\n\u00a0\u00a0 , Allocation\u00a0\u00a0 INT\r\n)\r\n-- Needed variables\r\nDECLARE @TSQL\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 NVARCHAR(1000)\r\nDECLARE @dbName\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 NVARCHAR(100)\r\nDECLARE @dbErrorList\u00a0\u00a0 NVARCHAR(1000)\r\nDECLARE @dbID\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 INT\r\nDECLARE @ErrorCount\u00a0\u00a0\u00a0\u00a0\u00a0 INT\r\nDECLARE @EmailSubject\u00a0\u00a0 NVARCHAR(255)\r\nDECLARE @ProfileName\u00a0\u00a0 VARCHAR(100)\r\nDECLARE @EmailRecipient\u00a0\u00a0 VARCHAR(255)\r\n\u00a0\r\n-- Init variables\r\nSET @dbID = 0\r\nSET @dbErrorList = ''\r\nSET @EmailSubject = 'Integrity Check Failure on ' + CAST(COALESCE(@@SERVERNAME, 'Server Name Not Available') AS NVARCHAR)\r\nSET @ProfileName = 'Notifications'\r\nSET @EmailRecipient = 'rlandrum13@cox.net'\r\n-- CYCLE THROUGH DATABASES\r\nWHILE(@@ROWCOUNT &gt; 0)\r\nBEGIN\r\n\u00a0\u00a0 IF( @dbID &gt; 0 )\r\n\u00a0\u00a0 BEGIN\r\n\u00a0\u00a0\u00a0\u00a0\u00a0 SET @TSQL = 'DBCC CHECKDB(''' +\u00a0 @dbName\u00a0 + ''') WITH TABLERESULTS, PHYSICAL_ONLY, NO_INFOMSGS'\r\n\u00a0\r\n\u00a0\u00a0\u00a0\u00a0\u00a0 INSERT INTO #CheckDBTemp\r\n\u00a0\u00a0\u00a0\u00a0\u00a0 EXEC(@TSQL)\r\n\u00a0\r\n\u00a0\u00a0\u00a0\u00a0\u00a0 SELECT @ErrorCount = COUNT(*) FROM #CheckDBTemp\r\n\u00a0\r\n\u00a0\u00a0\u00a0\u00a0\u00a0 IF( @ErrorCount &gt; 0 )\r\n\u00a0\u00a0\u00a0\u00a0\u00a0 BEGIN\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 SET @dbErrorList = @dbErrorList + CHAR(10) + CHAR(13) + 'Issue found on database : ' + @dbName\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 SET @dbErrorList = @dbErrorList + CHAR(10) + CHAR(13) + (Select Top 1 MessageText from\u00a0 #CheckDBTemp)\r\n\u00a0\u00a0\u00a0\u00a0\u00a0 END\r\n\u00a0\r\n\u00a0\u00a0\u00a0\u00a0\u00a0 TRUNCATE TABLE #CheckDBTemp\r\n\u00a0\u00a0 END\r\n\u00a0\u00a0 \r\n\u00a0\u00a0 IF SUBSTRING(CONVERT(varchar(50), SERVERPROPERTY('ProductVersion')),1,1) = '8'\r\n\u00a0\u00a0 BEGIN\r\n\u00a0\u00a0\u00a0\u00a0\u00a0 SELECT TOP 1 @dbName = name, @dbID = dbid\r\n\u00a0\u00a0\u00a0\u00a0\u00a0 FROM sysdatabases WHERE dbid &gt; @dbID \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 AND name NOT IN ('tempdb')\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 AND DATABASEPROPERTYEX(name, 'Status') = 'Online'\r\n\u00a0\u00a0\u00a0\u00a0\u00a0 ORDER by dbid\u00a0\u00a0\u00a0\u00a0\u00a0 \r\n\u00a0\u00a0 END\r\n\u00a0\u00a0 ELSE\r\n\u00a0\u00a0 BEGIN\r\n\u00a0\u00a0\u00a0\u00a0\u00a0 SELECT TOP 1 @dbName = name, @dbID = database_ID\r\n\u00a0\u00a0\u00a0\u00a0\u00a0 FROM sys.databases WHERE database_ID &gt; @dbID \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 AND name NOT IN ('tempdb') \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 AND DATABASEPROPERTYEX(name, 'Status') = 'Online'\r\n\u00a0\u00a0\u00a0\u00a0\u00a0 ORDER by database_ID\r\n\u00a0\u00a0 END\r\nEND\r\n-- If errors were found\r\nIF( @dbErrorList &lt;&gt; '' )\r\nBEGIN\r\n\u00a0\u00a0 IF SUBSTRING(CONVERT(varchar(50), SERVERPROPERTY('ProductVersion')),1,1) = '8'\r\n\u00a0\u00a0 BEGIN\r\n\u00a0\u00a0\u00a0\u00a0\u00a0 EXEC master..xp_sendmail @recipients = @EmailRecipient, @subject = @EmailSubject, @message = @dbErrorList\r\n\u00a0\u00a0 END\r\n\u00a0\u00a0 ELSE\r\n\u00a0\u00a0 BEGIN\r\n\u00a0\u00a0\u00a0\u00a0\u00a0 EXEC msdb..sp_send_dbmail @profile_name = @ProfileName, @recipients = @EmailRecipient, @subject = @EmailSubject, @body = @dbErrorList, @importance = 'High'\r\n\u00a0\u00a0 END\r\nEND\r\n\u00a0\r\nDROP TABLE #CheckDBTemp\r\n<\/pre>\n<p class=\"CAPTION\">Listing 6: A script for seeking out and reporting database corruption.<\/p>\n<p>You will notice that the code uses a <code>DBCC<\/code> <code>CHECKDB<\/code> option that I&#8217;ve not previously covered, and that is <code>WITH<\/code> <code>TABLERESULTS<\/code>. As the name suggests, it causes the results to be returned in table format. This option is not covered in Books Online, but is highly useful for automating error checking via SQL Agent Jobs or custom code.<\/p>\n<p>This code can easily be modified to return an email reporting that all databases except <code>NEO<\/code> are in good shape. It might soften the blow somewhat to know that of 20 databases only one is corrupt. I know it would help me somewhat. In any event, when corruption occurs you are going to receive the mail, seen in Figure 14, which is truly the monster that wakes you up in the middle of the night in a cold sweat.<\/p>\n<p class=\"ILLUSTRATION\"><img decoding=\"async\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/816-findin12.jpg\" alt=\"816-findin12.jpg\" \/><\/p>\n<p class=\"CAPTION\">Figure 14: The monster in email form.<\/p>\n<p>In this mail, I can see the <code>ObjectID<\/code>, the <code>IndexID<\/code> and the corrupted page, as well as the database name. This should be enough to go on for further investigation with the newfound tools, <code>DBCC PAGE<\/code>, <code>DBCC INDID<\/code> and <code>DBCC CHECKDB<\/code>, with repair options. Or, it should be a wakeup call to the fact that you might have to restore from a good backup.<\/p>\n<h1>Summary<\/h1>\n<p>In this article, I have discussed how to corrupt a database and delved into several undocumented DBCC options that will assist you when corruption happens to your data. Notice I said &#8220;when&#8221;. I have only touched the surface of the topic here by showing, at a very high level, how to translate pages to hexadecimal values and understand how to correlate the results of various DBCC commands, while troubleshooting corruption issues.<\/p>\n<p>I cannot stress enough that having a good backup plan is the most important task for the DBA. While I did not cover backups and restores in great depth in this article (an entire book can be written on this topic alone), I have at least shown the best reason to have such a good backup as part of your overall high availability and disaster recovery plan. A corrupt database will indeed be a disaster and could incur much downtime. You do not want to have to go to your boss, or your bosses&#8217; boss, and tell them that you have lost data irrevocably. If you do, you might as well pull your resume out from whatever disk drive it may be on (assuming that&#8217;s not corrupt as well) and update it.<\/p>\n<p>There is often panic when discovering any level of corruption in your databases. Without verified backups and some basic troubleshooting tips, there is no safe place to hide when the monster rears up. All you can do is perform a repair, potentially allowing data loss for hundreds of data pages, and then duck away into the nearest cubicle, which if it was yours will soon be empty.<\/p>\n<p>If you do have good backups and can repair the damage without data loss, then that cubicle may one day turn into an executive office with wall-to-wall tinted windows that reveal the flowing brook outside, where no monsters live.<\/p>\n<p class=\"NOTE\"><i>This article is taken from Chapter 8 of his new book, SQL Server Tacklebox. You can buy a hard copy of the book from <\/i><a href=\"http:\/\/www.amazon.com\/SQL-Server-Tacklebox-Rodney-Landrum\/dp\/1906434255\"><i>Amazon.com<\/i><\/a><i>, or download a <a href=\"http:\/\/www.simple-talk.com\/books\/sql-books\/sql-server-tacklebox\/\">free e-copy from Simple-Talk<\/a><\/i><\/p>\n<\/div>\n<p>&nbsp;<\/p>\n<div>\u00a0<\/div>\n","protected":false},"excerpt":{"rendered":"<p>In this article, taken from Chapter 8 of his new book, SQL Server Tacklebox, Rodney describes how a working DBA goes about troubleshooting data corruption. He demonstrates the tools and scripts required to seek out and fix data corruption in a timely manner, and so prevent it propagating into your backups.&hellip;<\/p>\n","protected":false},"author":221800,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[143527],"tags":[4248,5005,4170,5044,4150,4151],"coauthors":[11298],"class_list":["post-682","post","type-post","status-publish","format-standard","hentry","category-database-administration-sql-server","tag-backup","tag-data-corruption","tag-database-administration","tag-dba-example-code-tsql-sql-sample-free","tag-sql","tag-sql-server"],"acf":[],"_links":{"self":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/682","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/users\/221800"}],"replies":[{"embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/comments?post=682"}],"version-history":[{"count":7,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/682\/revisions"}],"predecessor-version":[{"id":84259,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/682\/revisions\/84259"}],"wp:attachment":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/media?parent=682"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/categories?post=682"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/tags?post=682"},{"taxonomy":"author","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/coauthors?post=682"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}