Informix Error -126: ISAM error: bad row id.
Cause and resolution
ISAM error: bad row id.
The ISAM processor was asked to retrieve a row by its physical location but could not find a row at that location.
The error occurred due to one or more of the following reasons:
For queries using the dirty read isolation level, the data being queried was in a temporarily inconsistent state that prevented it from being read. This is normal behavior for the dirty read isolation level, which allows queries to access data that is being updated concurrently by other transactions. Resubmit the query. To prevent this error from occurring, use a higher isolation level.
For C-ISAM programs, if you are using access by record number, review the number stored in isrecnum; it is invalid. Otherwise, the current index might be damaged; run the bcheck or secheck utility.
For SQL products, the index has been damaged. Run the oncheck utility to check and repair the index if you are using IBM Informix Dynamic Server, IBM Informix Universal Server, or IBM Informix OnLine Dynamic Server. Run bcheck or secheck if you are using the IBM Informix SE database server. Run tbcheck if you are using the IBM Informix OnLine database server.
Oninit® Troubleshooting Guidance
Reasons / Common Causes
-126 means a lookup by physical row location (a ROWID, or a C-ISAM record number) didn't find anything there. The official text names three distinct scenarios, and they call for genuinely different responses — worth keeping separate rather than treating -126 as one uniform condition.
- Dirty Read isolation timing. Under Dirty Read, a query can capture a row's physical location at a moment when the data is transiently inconsistent — by the time the location is actually dereferenced, the row may have moved, been deleted, or had its space reused. This is an accepted trade-off of Dirty Read's weaker guarantees, not corruption.
- A stale or invalid record number in C-ISAM.
isrecnumholds a physical location that was valid when captured; if it's cached and reused after the underlying row has since moved (through an update that no longer fits in place, a delete, or a reorganization), the stored number no longer points at a real row. - Index damage. The index itself has an incorrect physical-location entry for a key — independent of any timing issue, the index is simply wrong about where a row lives, which the official text attributes to a damaged index needing the appropriate check utility.
Beyond the three named scenarios, the underlying mechanics worth understanding:
- ROWIDs aren't a stable long-term identifier. Any operation that physically relocates rows
— an update that grows past its original slot, a table reorganization, certain
ALTER TABLEoperations — can invalidate a previously captured ROWID, even outside Dirty Read specifically. Code that caches a ROWID across a window where such operations might occur is at risk regardless of isolation level. - Concurrent DML from other sessions physically relocating rows between when a ROWID was captured and when it's dereferenced, for reasons unrelated to the session doing the lookup.
- The same underlying corruption causes as -105 (unclean shutdown, hardware failure, an improperly taken hot copy) manifesting specifically as index damage, surfacing here rather than as -105 directly.
Solutions / Resolution
-
For Dirty Read timing, resubmit the query. If this happens often enough to be a practical problem rather than an occasional, acceptable cost of using Dirty Read for performance, reconsider whether a higher isolation level (Committed Read or above) is the better trade-off for this particular query.
-
For C-ISAM, don't cache
isrecnumacross operations that could invalidate it. If corruption is suspected instead, runbcheck/secheckagainst the relevant index to diagnose and repair. -
For SQL, run the version-appropriate check utility:
oncheck— Dynamic/Universal/OnLine Dynamic Serverbcheck/secheck— SE servertbcheck— older OnLine database server
against the affected table's indexes, and repair or rebuild as the utility's output recommends.
-
Don't treat ROWIDs as a stable long-term row identifier. If code needs to reliably refer back to a specific row across time or across operations that might relocate it, use a proper primary key or unique constraint instead of a physical location.
-
If index damage is confirmed and repair fails, rebuild the index from scratch (drop and recreate) rather than continuing to operate against a structure the check utility couldn't fully repair.
-
If corruption (not timing) is the underlying issue, investigate the same root causes as -105 — unclean shutdown, hardware failure, an improperly taken hot copy — rather than assuming this is only ever a Dirty Read artifact.
Examples
Dirty Read timing
SET ISOLATION TO DIRTY READ;
SELECT ROWID, * FROM orders WHERE status = 'pending';
-- capture a ROWID here...
-- ...meanwhile, another session updates that row in a way that
-- relocates it, or deletes it outright
-- Dereferencing the captured ROWID later:
SELECT * FROM orders WHERE ROWID = :captured_rowid;
-- -126: nothing is at that physical location anymore
Resubmitting the original query (rather than relying on the stale captured ROWID) is the correct response — this is expected behavior under Dirty Read, not a bug.
Caching a ROWID too long, outside Dirty Read
SET ISOLATION TO COMMITTED READ;
SELECT ROWID INTO :saved_rowid FROM inventory WHERE sku = 'WIDGET-1';
-- Much later, after an unrelated ALTER TABLE reorganizes the table:
SELECT * FROM inventory WHERE ROWID = :saved_rowid;
-- -126: the reorganization relocated rows; the saved ROWID is stale
The isolation level here was never the issue — the ROWID was simply held across an operation that
invalidated it. Use the row's actual key (sku) for a lookup meant to persist across time, not
its physical location.
Diagnosing index damage
oncheck -cI mydb:orders
Run this when -126 recurs without an obvious Dirty Read or stale-ROWID explanation — a consistent, reproducible -126 against the same index (rather than an occasional timing-related one) points at actual index damage.
Diagnostic Checks
- Check the isolation level in use for the failing query — Dirty Read is the first thing to rule in or out.
- Run the version-appropriate check utility against the relevant index:
oncheck -cI <database>:<table> - Review whether ROWIDs are being cached or reused across a window where the underlying rows
could plausibly have moved (an update, a delete, a reorganization, an
ALTER TABLE). - Check for concurrent DML activity around the failure time that could explain a row relocation unrelated to the failing session's own actions.
Related Errors / Related Topics
- -100 — "ISAM error: duplicate value for a record with unique key." The other foundational ISAM-level error in this family.
- -105 — "ISAM error: bad ISAM file format." The corruption sibling — when -126 isn't explained by Dirty Read timing or ROWID staleness, the same root-cause investigation that applies to -105 (unclean shutdown, hardware, improper backups) applies here too.
Rule out Dirty Read timing and ROWID staleness first — they're by far the more common causes — before treating -126 as evidence of index corruption.