Logical Consistency-Based I/O Error in SQL Server (Errors 823 and 824)

🚨Part of the SQL Server Errors series, the exact messages and what actually causes them.

Error 823Msg 823  ·  Level 24  ·  logged
The operating system returned error 21(The device is not ready.) to SQL Server during a read at offset 0x00000002a4c000 in file ‘D:\SQLData\Sales.mdf’.
Error 824Msg 824  ·  Level 24  ·  logged
SQL Server detected a logical consistency-based I/O error: incorrect checksum (expected: 0x3f2b1a9c; actual: 0x3f2b1a80). It occurred during a read of page (1:5432) in database ID 7 at offset 0x000000015f0000 in file ‘D:\SQLData\Sales.mdf’.
🚨Do not restart the instance and do not run a repair. Check that you still have a good backup, before you do anything else. Both errors mean a page came back wrong. The difference between them tells you who to escalate to, and it is worth the thirty seconds it takes to read.

These are severity 24. SQL Server does not hand those out lightly: it is telling you that a read it depended on did not come back correctly, and that it cannot vouch for the integrity of what is on disk. The instinct is to make the error go away. That instinct is what turns a recoverable incident into a restore from a backup nobody checked.

This page is about the moment the error arrives. What comes before it, and what comes after, are separate jobs:


823 and 824 Are Not the Same Failure

They are printed in the same red and they are both severity 24, so they get treated as one thing. They are not, and the distinction is the most useful information on this page.

MsgWhat actually happenedWhat that tells you
823SQL Server asked the operating system for a block and the request failed. The OS returned an error code, which is quoted in the message.The storage stack could not complete the I/O. SQL Server never saw data to check. This is a disk, driver, HBA, path or volume problem, and the OS error code in the message is your first clue.
824The request succeeded, and what came back was wrong. SQL Server checked the page against its checksum and it did not match.Something wrote or returned bad data and nothing below SQL Server noticed. Worse in one specific way: the storage is reporting itself as healthy while handing back corruption.

Put plainly: 823 is the storage admitting it failed. 824 is the storage failing without admitting it. Both escalate to the storage or hardware team, but 824 also means you cannot trust “the SAN reports no errors” as reassurance, because that is exactly the condition 824 describes.

Microsoft’s reference covers error 823 and error 824 in full, and 2 details from those pages belong in the incident. For a read, SQL Server retries the request 4 times before it raises 823, and a retry that eventually succeeds is logged as 825 instead. And when a query hits either error, the message goes back to the application and the connection is terminated, so the first report you get is often a dropped connection rather than the error text.

Two more you may see in the same window, worth recognising rather than chasing separately:

MsgSeverityWhat it adds
60521A page was fetched and belongs to a different object than the allocation structures say. Allocation-level corruption, and firmly a restore conversation.
82921A page is marked RestorePending. Something already tried to deal with a bad page and did not finish. Check suspect_pages before assuming this is new.
Both of these were reproduced, not written from memory An 824 is hard to demonstrate on a real server and easy to demonstrate on a disposable one. Damaging a throwaway SQL Server 2022 container two different ways produced two genuinely different 824s, and the distinction between them is the same one this page is about.

Overwriting a database’s transaction log gave a torn page, where the page signature did not match at all:

Error: 824, Severity: 24, State: 2.
SQL Server detected a logical consistency-based I/O error: torn page
(expected signature: 0xaaaaaaaa; actual signature: 0x8834a602). It occurred during a
read of page (2:0) in database ID 1 at offset 0000000000000000
in file '/var/opt/mssql/data/mastlog.ldf'.

Scribbling over pages inside the data file, leaving the header valid, gave an incorrect checksum instead, and only after recovery had already rolled forward 16 transactions:

16 transactions rolled forward in database 'master' (1:0).
Error: 824, Severity: 24, State: 2.
SQL Server detected a logical consistency-based I/O error: incorrect checksum
(expected: 0x524b60b1; actual: 0x8af60429). It occurred during a read of page (1:56)
in database ID 1 at offset 0x00000000070000
in file '/var/opt/mssql/data/master.mdf'.

Same error number, same severity, two different reasons quoted in the message. That quoted reason, torn page or incorrect checksum, is worth reading before anything else: a torn page means a write was interrupted partway, a bad checksum means the bytes changed after they were written. Both of these were on database ID 1, which is master, and that is an instance that will not start rather than a database you can restore at leisure.


The First Five Minutes

In order, and the order matters. Every step here is read-only.

  1. Confirm you have a restorable backup, and stop it being overwritten. Before diagnosis, before anything. Backup retention windows have ended more recoveries than corruption has.
  2. Read the message properly. The file path and the offset tell you which database and which volume. The page ID in an 824 tells you how localised this is.
  3. Check suspect_pages. It answers “is this the first time” in one query, below.
  4. Search the error log for the neighbours. An 823 rarely arrives alone, and an 825 earlier in the week changes the story from “sudden” to “ignored”.
  5. Then, and only then, run DBCC CHECKDB to find out the actual extent. Without repair options.
-- 1. Has SQL Server already recorded bad pages on this instance?
--    Empty is the healthy answer. Any row is a page SQL Server could not read cleanly.
SELECT DB_NAME(database_id) AS database_name,
       file_id, page_id, event_type,
       -- 1 = 823/824 error   2 = bad checksum   3 = torn page
       -- 4 = restored        5 = repaired       7 = deallocated
       error_count, last_update_date
FROM msdb.dbo.suspect_pages
ORDER BY last_update_date DESC;

-- 2. What else did the error log say around the same time?
EXEC xp_readerrorlog 0, 1, N'823';
EXEC xp_readerrorlog 0, 1, N'824';
EXEC xp_readerrorlog 0, 1, N'825';   -- the warning that came first
Measured on a healthy instance, SQL Server 2025 msdb.dbo.suspect_pages returned 0 rows, and every database was on PAGE_VERIFY CHECKSUM. That is what you want to see, and it is worth running now rather than for the first time during an incident, so you know what normal looks like on your own servers.

How Long Has This Database Been Unchecked?

The question that decides how much of your backup chain is trustworthy is not “is it corrupt now”, it is “when did I last know it was clean”. Everything after that point may contain the problem, including your backups.

SELECT d.name,
       CONVERT(varchar(19), DATABASEPROPERTYEX(d.name,'LastGoodCheckDbTime'), 120) AS last_known_good,
       DATEDIFF(day, CAST(DATABASEPROPERTYEX(d.name,'LastGoodCheckDbTime') AS datetime), GETDATE()) AS days_ago,
       d.page_verify_option_desc AS page_verify
FROM sys.databases d
WHERE d.state_desc = 'ONLINE' AND d.database_id > 4
ORDER BY days_ago DESC;

Real output from running that, and it contains the trap:

namelast_known_gooddays_agopage_verify
DBAMonitor1900-01-01 00:00:0046251CHECKSUM
migdb_B8A15B782026-07-16 22:13:5134CHECKSUM
DemoDatabase2026-07-28 07:50:2522CHECKSUM

That first row does not mean the check is 126 years old. It means CHECKDB has never successfully completed on that database. LastGoodCheckDbTime returns 1900-01-01 when there is no value, so any “days since” calculation turns “never” into an enormous number that looks like a formatting bug and gets skipped over. If you build a report on this property, test for the 1900 date explicitly rather than trusting the arithmetic.

It is also worth knowing what a clean check looks like, because it looks like nothing:

Measured, same instance DBCC CHECKDB WITH NO_INFOMSGS, ALL_ERRORMSGS, TABLERESULTS returned 0 rows. A healthy database produces no output at all with NO_INFOMSGS, which is precisely why a scheduled CHECKDB that nobody reads is easy to mistake for one that is passing. Silence is the pass, so the job has to fail loudly rather than report quietly.

Drop NO_INFOMSGS and it does say so out loud. The same database, same run:

DBCC results for 'DemoDatabase'.
CHECKDB found 0 allocation errors and 0 consistency errors in database 'DemoDatabase'.
DBCC execution completed. If DBCC printed error messages, contact your system administrator.

That is the line to capture and keep, because “0 allocation errors and 0 consistency errors” plus a timestamp is the evidence that your last known good point is real. It is also what the LastGoodCheckDbTime property above is recording for you, which is why that property is worth more than a memory of having run it.


Nothing Tells You Unless You Set It Up

SQL Server ships with no alerts configured. Not fewer than you need, none. I checked msdb.dbo.sysalerts on a working instance while writing this and it held zero alerts for 823, 824 or 825, which is the out-of-the-box state on every install rather than an oversight on that one.

So the sequence on a default install is: 825 arrives at severity 10 and nothing happens, then 823 or 824 arrives at severity 24 and you find out from a user. Severity 24 does at least reach the error log and the Windows event log, both confirmed as is_event_logged = 1 in sys.messages, so there is a record. There is simply nobody watching it.

Alerting on all 3 is a 10-minute job and it is the difference between finding this in a maintenance window and finding it in an outage. The 825 post covers setting that up, and 825 is the one most people leave out.


What Not To Do

  • Do not restart the instance to see if it clears. It will not fix a bad page, it destroys the buffer pool evidence, and a database with corruption in the wrong place may not come back online.
  • Do not run REPAIR_ALLOW_DATA_LOSS as a first move. The name is the specification. It deallocates what it cannot fix, and it is a decision to take after you know the extent and have a backup, not instead of finding out.
  • Do not take a fresh full backup over your last known good one. A backup taken now may contain the corruption. Keep the older chain until you know which side of the problem it sits on.
  • Do not accept “the storage reports no errors” for an 824. That is the definition of an 824: the storage returned success and the data was wrong.
  • Do not turn off PAGE_VERIFY CHECKSUM. It does not stop corruption happening, only your ability to be told about it.

Common Questions

Is 824 worse than 823?
Neither is worse, they are different failures. 823 means the I/O request failed and the storage said so. 824 means the request succeeded and the data was wrong, which is more insidious because nothing below SQL Server flagged it. In practice 824 makes the storage team’s job harder, because their monitoring will show a healthy path.
I got one 824 and everything seems fine now. Can I move on?
No. The page is recorded in msdb.dbo.suspect_pages and the underlying cause has not changed. A single 824 with no other symptom is the best possible time to deal with this, because you still have a choice about when. Run CHECKDB, find the extent, and get the hardware looked at.
Does a clean DBCC CHECKDB mean the hardware is fine?
It means the pages CHECKDB read were consistent at that moment. It says nothing about the drive that returned an error an hour ago. An 823 in particular is a storage-layer event that a later clean CHECKDB does not retract, so the hardware investigation runs regardless of the CHECKDB result.
Can I just restore the single bad page?
Page-level restore exists and is genuinely useful for a small number of pages, in full recovery model with an unbroken log chain. It is a real option once CHECKDB has told you the extent. It is not a way to skip finding out the extent, and it does not address why the page went bad.
Why did nothing alert me?
Because SQL Server ships with no alerts at all, so severity 24 reaches the error log and the Windows event log and stops there. Checked on a real instance: zero alerts configured for 823, 824 or 825. Adding them is the single highest-value ten minutes available here.
What is error 605, and is it the same problem?
Related but distinct. 605 is severity 21 and means a page was fetched that belongs to a different object than the allocation structures expect. That is allocation-level corruption rather than a failed read, and it goes straight to the restore conversation rather than the hardware one.

Related Scripts

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *