Corruption on non-clustered indexes 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 jus
Trang 1Figure 8.11: Missing data after DBCC CHECKDB Repair_Allow_Data_Loss
These rows should be easily recovered if you have a good backup You have a
good backup, right? Right? Assuming you do, then you are faced with the task of
restoring from backup to another database, like NEO2, and syncing the two tables for the missing rows Syncing the two tables can be accomplished with a simple
INSERT INTO statement, like that shown in Listing 8.4
Trang 2INSERT INTO NEO ONE ( NEOID , NEOTEXT )
SELECT NEOID ,
NEOTEXT
FROM NEO2 ONE
WHERE NEOID NOT IN SELECT NEOID
FROM NEO ONE )
Listing 8.4: Syncing two tables to recover lost data rows
In this "controlled example", 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
Corruption on non-clustered indexes
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 "redundancies" of the actual data and can be easily rebuilt However, it would be interesting to prove this point I'll use the same Hexadecimal editor technique to corrupt the non-clustered index, and not the data, and see what the outcome would be
One indicator of whether the corruption is on an index or a table is the IndexID
provided with the DBCC output For our ONE heap table, I noted (in Listing 8.3) that the IndexID was 0 as there were no indexes defined for the table An
IndexID of 1 means a clustered index and a value of 2-250 indicates a non-clustered index
For the sake of brevity, let's assume that I have performed the necessary repair on the NEOID column and created a non-clustered index on the ONE table, for the
NEOID column
First, I need to find out the page value of the index I defined for the ONE table I will then plug this page of the non-clustered index into DBCC PAGE so that I know, again, exactly what data to modify to simulate index corruption, instead of data page corruption of the heap
To retrieve the page value of the index, I can use another DBCC command, call it undocumented again, DBCC INDID The syntax for this command is:
DBCC INDID (DBID, TABLEID,-1)
So, to execute this for my newly-indexed ONE table, the command will be:
DBCC ind ( 23 , 2121058592 , - )
Trang 3The results reveal several IndexIDs, mostly zero, along with several IndexID
values of 2, indicating a non-clustered index Notice in Figure 8.11 the IndexID of
2 and the associated page of that index, 180
Figure 8.12: Finding the page of the new non-clustered index
I can now run DBCC PAGE again, plugging in this page information:
DBCC TRACEON ( 3604 );
GO
DBCC PAGE ( NEO , , 180 , )
GO
The results look a lot different than when looking at a data page I see returned the Hexadecimal value (HEAP RID) that represents each row in the index for the page interrogated, as shown in Figure 8.12
Trang 4Figure 8.13: Looking at the non-clustered index for the ONE table with DBCC PAGE
I used the Hex editor again to modify, or zero out, the HEAP RID, 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 DBCC CHECKDB('neo') WITH PHYSICAL_ONLY, the IndexID of the corrupt object is reported as "2" i.e a non-clustered index
Trang 5Armed with this knowledge, I have open to me options for repairing the damage, other than restoring from backup, or running DBCC CHECKDB with
REPAIR_ALLOW_DATA_LOSS, with the potential loss of data that this entails
I can simply drop and recreate the non-clustered index using the code in Listing 8.5
USE [NEO]
GO
IF EXISTS SELECT FROM sys.indexes WHERE object_id
OBJECT_ID ( '[dbo].[ONE]' )
DROP INDEX [NEO_ID_NC] ON [dbo] [ONE] WITH ONLINE OFF )
GO
USE [NEO]
GO
CREATE NONCLUSTERED INDEX [NEO_ID_NC] ON [dbo] [ONE]
(
[NEOID] ASC
) WITH PAD_INDEX = OFF , STATISTICS_NORECOMPUTE = OFF ,
SORT_IN_TEMPDB OFF ,
IGNORE_DUP_KEY OFF , DROP_EXISTING OFF , ONLINE OFF ,
ALLOW_ROW_LOCKS = ON ,
ALLOW_PAGE_LOCKS = ON ) ON [PRIMARY]
GO
Listing 8.5: Drop and recreate corrupt non-clustered index
Now that I have delved somewhat into corrupting, finding and fixing some problems, let's turn now to the discovery process
Seeking out corruption
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?
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 DBCC CHECKDB