8 – Finding data corruption 225 With this code, and an easy way to read the error logs where the DBCC CHECKDB results will be written (which I covered in Chapter 7), 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. The custom query, in Listing 8.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. CREATE TABLE #CheckDBTemp ( Error INT , [Level] INT , [State] INT , MessageText NVARCHAR(1000) , RepairLevel NVARCHAR(1000) , [Status] INT , [DBID] INT , ObjectID INT , IndexID INT , PartitionID BIGINT , AllocUnitID BIGINT , [File] INT , Page INT , Slot INT , RefFile INT , RefPage INT , RefSlot INT , Allocation INT ) Needed variables DECLARE @TSQL NVARCHAR(1000) DECLARE @dbName NVARCHAR(100) DECLARE @dbErrorList NVARCHAR(1000) DECLARE @dbID INT DECLARE @ErrorCount INT DECLARE @EmailSubject NVARCHAR(255) DECLARE @ProfileName VARCHAR(100) DECLARE @EmailRecipient VARCHAR(255) Init variables SET @dbID = 0 SET @dbErrorList = '' SET @EmailSubject = 'Integrity Check Failure on ' + CAST(COALESCE(@@SERVERNAME, 'Server Name Not Available') AS NVARCHAR) SET @ProfileName = 'Notifications' SET @EmailRecipient = 'rlandrum13@cox.net' CYCLE THROUGH DATABASES WHILE(@@ROWCOUNT > 0) BEGIN 8 – Finding data corruption 226 IF( @dbID > 0 ) BEGIN SET @TSQL = 'DBCC CHECKDB(''' + @dbName + ''') WITH TABLERESULTS, PHYSICAL_ONLY, NO_INFOMSGS' INSERT INTO #CheckDBTemp EXEC(@TSQL) SELECT @ErrorCount = COUNT(*) FROM #CheckDBTemp IF( @ErrorCount > 0 ) BEGIN SET @dbErrorList = @dbErrorList + CHAR(10) + CHAR(13) + 'Issue found on database : ' + @dbName SET @dbErrorList = @dbErrorList + CHAR(10) + CHAR(13) + (Select Top 1 MessageText from #CheckDBTemp) END TRUNCATE TABLE #CheckDBTemp END IF SUBSTRING(CONVERT(varchar(50), SERVERPROPERTY('ProductVersion')),1,1) = '8' BEGIN SELECT TOP 1 @dbName = name, @dbID = dbid FROM sysdatabases WHERE dbid > @dbID AND name NOT IN ('tempdb') AND DATABASEPROPERTYEX(name, 'Status') = 'Online' ORDER by dbid END ELSE BEGIN SELECT TOP 1 @dbName = name, @dbID = database_ID FROM sys.databases WHERE database_ID > @dbID AND name NOT IN ('tempdb') AND DATABASEPROPERTYEX(name, 'Status') = 'Online' ORDER by database_ID END END If errors were found IF( @dbErrorList <> '' ) BEGIN IF SUBSTRING(CONVERT(varchar(50), SERVERPROPERTY('ProductVersion')),1,1) = '8' BEGIN EXEC master xp_sendmail @recipients = @EmailRecipient, @subject = @EmailSubject, @message = @dbErrorList END ELSE BEGIN 8 – Finding data corruption 227 EXEC msdb sp_send_dbmail @profile_name = @ProfileName, @recipients = @EmailRecipient, @subject = @EmailSubject, @body = @dbErrorList, @importance = 'High' END END DROP TABLE #CheckDBTemp Listing 8.6: A script for seeking out and reporting database corruption. You will notice that the code uses a DBCC CHECKDB option that I've not previously covered, and that is WITH TABLERESULTS. 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. This code can easily be modified to return an email reporting that all databases except NEO 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 8.14, which is truly the monster that wakes you up in the middle of the night in a cold sweat. Figure 8.14: The monster in email form. 8 – Finding data corruption 228 In this mail, I can see the ObjectID, the IndexID and the corrupted page, as well as the database name. This should be enough to go on for further investigation with the newfound tools, DBCC PAGE, DBCC INDID and DBCC CHECKDB, with repair options. Or, it should be a wakeup call to the fact that you might have to restore from a good backup. Summary In this final chapter, 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 "when". 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. 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 chapter (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' 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's not corrupt as well) and update it. 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. 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 where the wall-to-wall tinted windows reveal the flowing brook outside, where no monsters live. The End . once discovered. The custom query, in Listing 8.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. '' SET @EmailSubject = 'Integrity Check Failure on ' + CAST(COALESCE(@@SERVERNAME, &apos ;Server Name Not Available') AS NVARCHAR) SET @ProfileName = 'Notifications'. BEGIN SET @TSQL = 'DBCC CHECKDB(''' + @dbName + ''') WITH TABLERESULTS, PHYSICAL_ONLY, NO_INFOMSGS' INSERT INTO #CheckDBTemp EXEC(@TSQL) SELECT