Introduction
IDB Utils is a command-line toolkit and Rust library for inspecting, validating, and manipulating InnoDB database files. The CLI binary is called inno, and the library crate is idb.
IDB Utils operates directly on .ibd tablespace files, redo logs, and system tablespaces without requiring a running MySQL instance. It is written in Rust for performance and safety, parsing binary InnoDB page structures at the byte level.
Who Is This For?
IDB Utils serves three audiences:
- CLI users (DBAs and sysadmins) -- Use the
innocommand to inspect tablespace files, validate checksums, compare backups, monitor live changes, extract metadata, analyze redo logs, and assess data recoverability from the terminal. - Library users (Rust developers) -- Import the
idbcrate to build custom tooling on top of InnoDB page parsing, checksum validation, SDI extraction, and redo log analysis. - Web users -- Use the browser-based analyzer to drag and drop
.ibdfiles for instant visual inspection without installing anything.
Capabilities
- Inspect tablespace files -- Parse FIL headers, page types, INDEX structures, UNDO logs, LOB pages, and SDI records.
- Validate checksums -- Verify page integrity using CRC-32C, legacy InnoDB, and MariaDB full_crc32 algorithms.
- Compare files -- Diff two tablespace files page-by-page to identify changes between backups or replicas.
- Monitor changes -- Watch a tablespace file for live modifications using LSN-based change detection.
- Extract SDI metadata -- Read MySQL 8.0+ Serialized Dictionary Information, including table definitions and column metadata.
- Analyze redo logs -- Parse redo log file headers, checkpoint blocks, and log record blocks.
- Assess recoverability -- Evaluate page-level damage and estimate salvageable records for data recovery planning.
- Read encrypted tablespaces -- Decrypt tablespace pages using keyring files for inspection and validation.
- Audit data directories -- Search for specific pages across a data directory, list tablespace IDs, and cross-check LSN consistency between ibdata1 and redo logs.
- Intentional corruption -- Corrupt pages in a controlled manner for testing backup and recovery workflows.
Vendor Support
IDB Utils supports tablespace files from:
- MySQL 5.7 and later (including MySQL 8.0+ SDI metadata)
- Percona XtraDB (Percona Server for MySQL)
- MariaDB 10.1 and later (including MariaDB full_crc32 checksums)
Vendor detection is automatic based on FSP flags and redo log headers.
Links
- GitHub Repository -- Source code, issue tracker, and releases.
- API Documentation -- Rust library API reference on docs.rs.
- Web Analyzer -- Browser-based tablespace analysis tool.
Installation
There are several ways to install inno, depending on your platform and preferences.
From crates.io
If you have the Rust toolchain installed, the simplest method is:
cargo install innodb-utils
This compiles and installs the inno binary into ~/.cargo/bin/.
Homebrew (macOS and Linux)
brew install ringo380/tap/inno
The Homebrew formula is updated automatically with each release.
Pre-built Binaries
Pre-built binaries are available from GitHub Releases for the following targets:
| Platform | Architecture |
|---|---|
| Linux | x86_64 |
| Linux | aarch64 |
| macOS | x86_64 |
| macOS | aarch64 |
Download the appropriate archive, extract it, and place the inno binary somewhere on your PATH.
From Source
git clone https://github.com/ringo380/idb-utils.git
cd idb-utils
cargo build --release
The compiled binary will be at target/release/inno. Copy it to a directory on your PATH:
cp target/release/inno /usr/local/bin/
With MySQL Support
The inno info subcommand can optionally query a live MySQL instance to compare runtime state against tablespace files. This requires the mysql feature flag:
cargo build --release --features mysql
This pulls in mysql_async and tokio as additional dependencies. Without this feature, all other subcommands work normally -- only inno info MySQL query mode is gated.
Requirements
- Source builds: Rust 1.70 or later.
- Pre-built binaries: No dependencies required.
- Homebrew: Homebrew handles all dependencies automatically.
Verify Installation
After installing, confirm that inno is available:
inno --version
To see the full list of subcommands and options:
inno --help
Quick Start
This page walks through the most common inno operations using practical examples. Each example can be run against any .ibd tablespace file from MySQL 5.7+, Percona, or MariaDB 10.1+.
Parse a Tablespace
Display page headers and a summary of page types in a tablespace file:
inno parse -f /var/lib/mysql/mydb/users.ibd
Use -p to focus on a single page, -v for verbose output, or -e to include extent descriptor details:
inno parse -f users.ibd -p 0 -v
Validate Checksums
Verify the integrity of every page in a tablespace by checking CRC-32C, legacy InnoDB, or MariaDB full_crc32 checksums:
inno checksum -f users.ibd
Add -v to see per-page checksum details.
Inspect Page Structures
Get detailed structural analysis of specific page types. Use -t to filter by type:
inno pages -f users.ibd -t INDEX
This shows B+Tree node details for INDEX pages, including record counts, page level, and directory slot information.
Hex Dump
View raw bytes of any page:
inno dump -f users.ibd -p 3
Use --offset and -l to narrow the dump to a specific byte range within the page:
inno dump -f users.ibd -p 3 --offset 0 -l 38
Extract SDI Metadata
Read Serialized Dictionary Information from MySQL 8.0+ tablespace files. This contains table definitions, column types, and index metadata:
inno sdi -f users.ibd --pretty
Analyze Redo Logs
Parse InnoDB redo log file headers, checkpoint blocks, and log record blocks:
inno log -f /var/lib/mysql/ib_logfile0
Use -b to limit the number of blocks analyzed, or --no-empty to skip empty blocks:
inno log -f ib_logfile0 -b 10 --no-empty -v
Compare Two Tablespaces
Diff two tablespace files page-by-page to see what changed between a backup and the current state:
inno diff backup.ibd current.ibd -v
Use -b for byte-level detail on changed pages, or -p to compare a single page:
inno diff backup.ibd current.ibd -p 3 -b
Monitor Live Changes
Watch a tablespace file for modifications in real time. The watch loop uses LSN-based change detection:
inno watch -f users.ibd -i 500 -v
The -i flag sets the polling interval in milliseconds.
Assess Recoverability
Evaluate page-level damage and estimate how many records are salvageable:
inno recover -f users.ibd --force -v
The --force flag attempts recovery assessment even on pages that appear severely damaged.
Read Encrypted Tablespaces
Parse tablespace files that use InnoDB tablespace encryption by providing a keyring file:
inno parse -f encrypted.ibd --keyring /var/lib/mysql-keyring/keyring
Search a Data Directory
Find all tablespace files containing a specific page number:
inno find -d /var/lib/mysql -p 42
Add -s to filter by space ID, or --first to stop after the first match.
List and Look Up Tablespace IDs
List all tablespace IDs in a data directory, or look up a specific one:
inno tsid -d /var/lib/mysql -l
inno tsid -d /var/lib/mysql -t 15
Check LSN Consistency
Compare the LSN in ibdata1 against redo log checkpoints to detect sync issues:
inno info --lsn-check -d /var/lib/mysql
JSON Output
Every subcommand supports --json for machine-readable output, making it straightforward to integrate with scripts and pipelines:
inno parse -f users.ibd --json | jq '.pages | length'
inno checksum -f users.ibd --json
inno recover -f users.ibd --json
Getting Help
Every subcommand has its own --help flag with a full description of available options:
inno --help
inno parse --help
inno checksum --help
For detailed documentation on each subcommand, see the CLI Reference.
inno -- CLI Overview
inno is the command-line interface for IDB Utils, an InnoDB file analysis toolkit. It provides 30 subcommands for inspecting, validating, comparing, and manipulating InnoDB tablespace files, redo logs, binary logs, and system tablespaces.
Installation
# From crates.io
cargo install innodb-utils
# From Homebrew
brew install ringo380/tap/inno
Subcommand Reference
Inspection & Parsing
| Command | Description |
|---|---|
inno parse | Parse .ibd file, display page headers and type summary |
inno pages | Detailed page structure analysis (INDEX, UNDO, LOB, SDI) |
inno dump | Hex dump of raw page bytes |
inno sdi | Extract SDI metadata from MySQL 8.0+ tablespaces |
inno schema | Extract schema and reconstruct DDL from tablespace metadata |
inno export | Export record data as CSV, JSON, or hex dump |
inno info | Inspect ibdata1, compare LSNs, query MySQL |
Validation & Health
| Command | Description |
|---|---|
inno checksum | Validate page checksums (CRC-32C, legacy, MariaDB full_crc32) |
inno health | Per-index B+Tree health metrics (fill factor, fragmentation, bloat) |
inno verify | Verify structural integrity of a tablespace |
inno validate | Validate tablespace against live MySQL |
inno compat | Check upgrade compatibility between MySQL versions |
inno audit | Audit data directory for integrity, health, or checksum mismatches |
Comparison & Monitoring
| Command | Description |
|---|---|
inno diff | Compare two tablespace files page-by-page |
inno watch | Monitor a tablespace for page-level changes in real time |
inno find | Search a data directory for pages by number |
inno tsid | List or find tablespace IDs |
Recovery & Repair
| Command | Description |
|---|---|
inno recover | Assess page-level recoverability and count salvageable records |
inno repair | Recalculate and fix corrupt page checksums |
inno undelete | Recover deleted records from tablespace |
inno corrupt | Intentionally corrupt pages for testing |
inno defrag | Defragment tablespace, reorder INDEX pages |
inno transplant | Copy specific pages from a donor into a target tablespace |
inno simulate | Simulate InnoDB crash recovery levels 1-6 |
Log & Transaction Analysis
| Command | Description |
|---|---|
inno log | Analyze InnoDB redo log files |
inno undo | Analyze undo tablespace structure |
inno binlog | Analyze MySQL binary log files |
inno timeline | Unified modification timeline from redo, undo, and binary logs |
Backup Analysis
| Command | Description |
|---|---|
inno backup diff | Compare page LSNs between backup and current tablespace |
inno backup chain | Validate XtraBackup backup chain LSN continuity |
Utilities
| Command | Description |
|---|---|
inno completions | Generate shell completions for bash, zsh, fish, powershell |
Global Flags
These flags are available on every subcommand:
| Flag | Default | Description |
|---|---|---|
--color <auto|always|never> | auto | Control colored terminal output. auto enables color when stdout is a terminal. |
--format <text|json|csv> | text | Output format. Overrides per-subcommand --json flag. CSV support varies by subcommand. |
-o, --output <file> | stdout | Write output to a file instead of printing to stdout. |
--audit-log <path> | none | Append structured audit events (NDJSON) to the specified file. Used by write operations (repair, corrupt, defrag, transplant). |
Common Flags
Most subcommands also accept these flags (see individual pages for specifics):
| Flag | Description |
|---|---|
--json | Emit structured JSON output instead of human-readable text. All subcommands support this. |
-v, --verbose | Show additional detail (per-page checksums, FSEG internals, MLOG record types, etc.). |
--page-size <size> | Override the auto-detected page size. Accepts 4096, 8192, 16384 (default), 32768, or 65536. |
--keyring <path> | Path to a MySQL keyring file for decrypting encrypted tablespaces. |
Common Patterns
Page size auto-detection
By default, inno reads the FSP header flags from page 0 to determine the tablespace page size. If page 0 is corrupt or the file uses a non-standard size, use --page-size to override detection.
JSON output
Every subcommand supports --json for machine-readable output. JSON structs are serialized with serde_json and optional fields are omitted when empty. This makes inno suitable for integration into scripts and pipelines:
inno checksum -f table.ibd --json | jq '.invalid_pages'
Encrypted tablespaces
For tablespaces encrypted with MySQL's InnoDB tablespace encryption, provide the keyring file with --keyring. The tablespace key is extracted from the encryption info on page 0, decrypted using the master key from the keyring, and applied transparently to all page reads. Subcommands that support --keyring include parse, pages, dump, checksum, diff, watch, recover, and sdi.
Exit codes
- 0 -- Success.
- 1 -- An error occurred, or (for
inno checksum) invalid checksums were detected.
inno parse
Parse an InnoDB tablespace file and display page headers with a type summary.
Synopsis
inno parse -f <file> [-p <page>] [-v] [-e] [--json] [--page-size <size>] [--keyring <path>]
Description
Reads the 38-byte FIL header of every page in a tablespace, decodes the page type, checksum, LSN, prev/next pointers, and space ID, then prints a per-page breakdown followed by a page-type frequency summary table. Page 0 additionally shows the FSP header (space ID, tablespace size, free-page limit, and flags).
In single-page mode (-p N), only the specified page is printed with its full FIL header and trailer. In full-file mode (the default), all pages are listed and a frequency summary table is appended showing how many pages of each type exist.
Pages with zero checksum and type Allocated are skipped by default unless --verbose is set. The --no-empty flag additionally filters these from --json output.
With --verbose, each page also shows checksum validation status (algorithm, stored vs. calculated values) and LSN consistency between the FIL header and trailer.
Flags
| Flag | Short | Required | Default | Description |
|---|---|---|---|---|
--file <path> | -f | Yes | -- | Path to the InnoDB data file (.ibd). |
--page <number> | -p | No | All pages | Display a specific page number only. |
--verbose | -v | No | Off | Show checksum validation and LSN consistency per page. |
--no-empty | -e | No | Off | Skip empty/allocated pages (zero checksum, type Allocated). |
--json | -- | No | Off | Output in JSON format. |
--page-size <size> | -- | No | Auto-detect | Override page size (e.g., 4096, 8192, 16384, 32768, 65536). |
--keyring <path> | -- | No | -- | Path to MySQL keyring file for decrypting encrypted tablespaces. |
Examples
Parse all pages in a tablespace
inno parse -f /var/lib/mysql/sakila/actor.ibd
Parse a single page with verbose output
inno parse -f actor.ibd -p 3 -v
Parse with empty pages filtered out
inno parse -f actor.ibd -e
JSON output for scripting
inno parse -f actor.ibd --json | jq '.[].page_type_name'
Parse an encrypted tablespace
inno parse -f encrypted_table.ibd --keyring /var/lib/mysql-keyring/keyring
Output
In text mode, each page displays:
- Page number and byte offset range
- Page type with numeric code, name, description, and usage
- Prev/Next page pointers (or "Not used" for null pointers)
- LSN (Log Sequence Number)
- Space ID
- Checksum (stored value)
- Trailer with old-style checksum and LSN low 32 bits
Page 0 additionally shows the FSP header:
- Space ID, size (in pages), free-page limit, and flags
The full-file scan ends with a Page Type Summary table showing the count of each page type found in the file.
In JSON mode, the output is an array of page objects with page_number, header, page_type_name, page_type_description, byte_start, byte_end, and an optional fsp_header for page 0.
inno pages
Detailed page structure analysis for InnoDB tablespace files.
Synopsis
inno pages -f <file> [-p <page>] [-v] [-e] [-l] [-t <type>] [--json] [--page-size <size>] [--keyring <path>]
Description
Goes beyond FIL headers to decode the internal structure of each page type. Unlike inno parse, which only reads FIL headers, this command dives into page-type-specific fields:
- INDEX pages (type 17855): Decodes the index header (index ID, B+Tree level, record counts, heap top, garbage bytes, insert direction), FSEG inode pointers for leaf and non-leaf segments, and infimum/supremum system records.
- UNDO pages (type 2): Shows the undo page header (type, start/free offsets, used bytes) and segment header (state, last log offset).
- BLOB/ZBLOB pages (types 10, 11, 12): Shows data length and next-page chain pointer for old-style externally stored columns.
- LOB_FIRST pages (MySQL 8.0+): Shows version, flags, total data length, and transaction ID for new-style LOB first pages.
- Page 0 (FSP_HDR): Shows extended FSP header fields including compression algorithm, encryption flags, vendor detection, and first unused segment ID.
In list mode (-l), output is a compact one-line-per-page summary showing page number, type, description, index ID (for INDEX pages), and byte offset. In detail mode (the default), each page gets a full multi-section breakdown.
Flags
| Flag | Short | Required | Default | Description |
|---|---|---|---|---|
--file <path> | -f | Yes | -- | Path to the InnoDB data file (.ibd). |
--page <number> | -p | No | All pages | Display a specific page number only. |
--verbose | -v | No | Off | Show checksum status, FSEG internals, and additional detail. |
--show-empty | -e | No | Off | Include empty/allocated pages in the output. |
--list | -l | No | Off | Compact one-line-per-page listing mode. |
--type <filter> | -t | No | All types | Filter output to pages matching this type name. |
--json | -- | No | Off | Output in JSON format. |
--page-size <size> | -- | No | Auto-detect | Override page size. |
--keyring <path> | -- | No | -- | Path to MySQL keyring file for decrypting encrypted tablespaces. |
Type Filter Values
The -t flag accepts these values (case-insensitive):
| Filter | Matches |
|---|---|
INDEX | INDEX pages (B+Tree nodes) |
UNDO | Undo log pages |
BLOB | BLOB, ZBLOB, and ZBLOB2 pages |
LOB | LOB_INDEX, LOB_DATA, and LOB_FIRST pages |
SDI | SDI and SDI_BLOB pages |
COMPRESSED or COMP | All compressed page types |
ENCRYPTED or ENC | All encrypted page types |
INSTANT | INSTANT pages |
Any other string is matched as a substring against the page type name.
Examples
List all pages in compact mode
inno pages -f actor.ibd -l
Deep-dive into a single INDEX page
inno pages -f actor.ibd -p 3 -v
Show only INDEX pages
inno pages -f actor.ibd -t INDEX
Show only UNDO pages in list mode
inno pages -f actor.ibd -t UNDO -l
JSON output for INDEX pages
inno pages -f actor.ibd -t INDEX --json | jq '.[].index_header'
Output
Detail Mode (default)
For each page, displays sections based on page type:
FIL Header (all pages): Page number, byte offset, page type, prev/next pointers, LSN, space ID, checksum.
INDEX Header (INDEX pages): Index ID, node level, max transaction ID, directory slots, heap top, record counts, free list start, garbage bytes, last insert position and direction.
FSEG Header (INDEX pages): Inode space ID, page number, and offset for leaf and non-leaf segments.
System Records (INDEX pages): Record status, owned records, deleted flag, heap number, infimum/supremum next-record offsets.
BLOB Header (BLOB/ZBLOB pages): Data length and next page number in the chain.
LOB First Page Header (LOB_FIRST pages): Version, flags, total data length, transaction ID.
UNDO Header (UNDO pages): Undo type, start offset, free offset, used bytes, segment state, last log offset.
FSP Header (page 0): Vendor, space ID, size, flags, free-page limit, compression algorithm, encryption info, first unused segment ID.
FIL Trailer (all pages): Old-style checksum, LSN low 32 bits, byte end offset. With --verbose, includes checksum validation status and LSN consistency.
List Mode (-l)
One line per page:
-- Page 3 - INDEX: B-tree Node, Index ID: 157, Byte Start: 0x0000C000
inno dump
Hex dump of raw bytes from an InnoDB tablespace file.
Synopsis
inno dump -f <file> [-p <page>] [--offset <byte>] [-l <length>] [--raw] [--page-size <size>] [--keyring <path>] [--decrypt]
Description
Produces a hex dump of raw bytes from an InnoDB tablespace file. Operates in two modes:
-
Page mode (default): Opens the file as a tablespace, reads the page specified by
-p(or page 0 if omitted), and prints a formatted hex dump with file-relative byte offsets. The dump length defaults to the full page size but can be shortened with--length. -
Offset mode (
--offset): Reads bytes starting at an arbitrary absolute file position without page-size awareness. The default read length is 256 bytes. This is useful for inspecting raw structures that do not align to page boundaries (e.g., redo log headers, doublewrite buffer regions).
In either mode, --raw suppresses the formatted hex layout and writes the raw binary bytes directly to stdout, suitable for piping into xxd, hexdump, or other tools.
Flags
| Flag | Short | Required | Default | Description |
|---|---|---|---|---|
--file <path> | -f | Yes | -- | Path to the InnoDB data file. |
--page <number> | -p | No | 0 | Page number to dump (page mode). |
--offset <byte> | -- | No | -- | Absolute byte offset to start dumping (offset mode). Bypasses page mode. |
--length <bytes> | -l | No | Page size (page mode) or 256 (offset mode) | Number of bytes to dump. |
--raw | -- | No | Off | Output raw binary bytes instead of formatted hex dump. |
--page-size <size> | -- | No | Auto-detect | Override page size. |
--keyring <path> | -- | No | -- | Path to MySQL keyring file for decrypting encrypted tablespaces. |
--decrypt | -- | No | Off | Decrypt page before dumping. Requires --keyring. |
Examples
Dump page 0 (FSP header page)
inno dump -f actor.ibd
Dump the first 64 bytes of page 3
inno dump -f actor.ibd -p 3 -l 64
Dump bytes at an absolute file offset
inno dump -f actor.ibd --offset 49152 -l 128
Raw binary output piped to xxd
inno dump -f actor.ibd -p 0 --raw | xxd
Dump a decrypted page
inno dump -f encrypted_table.ibd -p 3 --keyring /path/to/keyring --decrypt
Extract a page to a file
inno dump -f actor.ibd -p 3 --raw -o page3.bin
Output
The formatted hex dump displays 16 bytes per line with file-relative offsets on the left, hexadecimal byte values in the center, and ASCII character representation on the right:
Hex dump of actor.ibd page 0 (16384 bytes):
00000000 A3 B1 C5 D7 00 00 00 00 FF FF FF FF FF FF FF FF |................|
00000010 00 00 00 00 00 00 15 A3 00 08 00 00 00 00 00 00 |................|
...
inno checksum
Validate page checksums for every page in an InnoDB tablespace.
Synopsis
inno checksum -f <file> [-v] [--json] [--page-size <size>] [--keyring <path>]
Description
Iterates over all pages in a tablespace and validates the stored checksum (bytes 0-3 of the FIL header) against multiple algorithms:
- CRC-32C (MySQL 5.7.7+): XORs two independent CRC-32C values computed over bytes [4..26) and [38..page_size-8). This is the default algorithm for modern MySQL.
- Legacy InnoDB (MySQL < 5.7.7): Uses
ut_fold_ulint_pairwith u32 wrapping arithmetic over the same two byte ranges. - MariaDB full_crc32 (MariaDB 10.5+): Single CRC-32C over bytes [0..page_size-4). Checksum is stored in the last 4 bytes of the page, not the FIL header. Detected via FSP flags bit 4.
A page is considered valid if any algorithm matches the stored checksum value.
Additionally checks LSN consistency: the low 32 bits of the header LSN (bytes 16-23) must match the LSN value in the 8-byte FIL trailer at the end of the page.
Special pages are handled as follows:
- Pages with checksum
0xDEADBEEF(magic value) are skipped. - All-zero pages are counted as empty and skipped.
The process exits with code 1 if any page has an invalid checksum, making this suitable for scripted integrity checks.
Flags
| Flag | Short | Required | Default | Description |
|---|---|---|---|---|
--file <path> | -f | Yes | -- | Path to the InnoDB data file (.ibd). |
--verbose | -v | No | Off | Show per-page checksum details (algorithm, stored/calculated values). |
--json | -- | No | Off | Output in JSON format. |
--page-size <size> | -- | No | Auto-detect | Override page size. |
--keyring <path> | -- | No | -- | Path to MySQL keyring file for decrypting encrypted tablespaces. |
Examples
Validate all page checksums
inno checksum -f /var/lib/mysql/sakila/actor.ibd
Verbose per-page output
inno checksum -f actor.ibd -v
JSON output for scripting
inno checksum -f actor.ibd --json
Use in a script with exit code
if inno checksum -f actor.ibd; then
echo "All checksums valid"
else
echo "Corrupt pages detected!"
fi
Output
Text Mode
Displays a progress bar during validation, then prints a summary:
Validating checksums for actor.ibd (7 pages, page size 16384)...
Summary:
Total pages: 7
Empty pages: 3
Valid checksums: 4
Invalid checksums: 0
With --verbose, each non-empty page is printed individually:
Page 0: OK (Crc32c, stored=2741936599, calculated=2741936599)
Page 1: OK (Crc32c, stored=1849326041, calculated=1849326041)
Page 3: INVALID checksum (stored=12345, calculated=2741936599, algorithm=Crc32c)
JSON Mode
{
"file": "actor.ibd",
"page_size": 16384,
"total_pages": 7,
"empty_pages": 3,
"valid_pages": 4,
"invalid_pages": 0,
"lsn_mismatches": 0,
"pages": []
}
With --verbose, the pages array includes every non-empty page. Without --verbose, only invalid pages and pages with LSN mismatches are included:
{
"pages": [
{
"page_number": 3,
"status": "invalid",
"algorithm": "crc32c",
"stored_checksum": 12345,
"calculated_checksum": 2741936599,
"lsn_valid": true
}
]
}
The algorithm field will be one of: crc32c, innodb, mariadb_full_crc32, or none.
inno diff
Compare two InnoDB tablespace files page-by-page.
Synopsis
inno diff <file1> <file2> [-v] [-b] [-p <page>] [--json] [--page-size <size>] [--keyring <path>]
Description
Reads two InnoDB tablespace files and compares them page-by-page, reporting which pages are identical, modified, or only present in one file.
Three levels of comparison detail are available:
-
Quick comparison (default): Performs full-page byte equality checks and reports a summary of identical, modified, and file-only page counts.
-
Header field diff (
-v): For modified pages, decodes and compares the FIL header fields (checksum, page number, prev/next pointers, LSN, page type, flush LSN, space ID) and reports which fields changed. -
Byte-range scan (
-v -b): In addition to header diffs, scans the full page content to identify the exact byte offset ranges where data differs, along with a total bytes-changed count and percentage.
When files have different page sizes, only the FIL headers (first 38 bytes) are compared and a warning is displayed.
Flags
| Flag | Short | Required | Default | Description |
|---|---|---|---|---|
<file1> | -- | Yes | -- | First InnoDB data file (.ibd). Positional argument. |
<file2> | -- | Yes | -- | Second InnoDB data file (.ibd). Positional argument. |
--verbose | -v | No | Off | Show per-page header field diffs for modified pages. |
--byte-ranges | -b | No | Off | Show exact byte-range diffs for changed pages. Requires -v. |
--page <number> | -p | No | All pages | Compare a single page only. |
--json | -- | No | Off | Output in JSON format. |
--page-size <size> | -- | No | Auto-detect | Override page size. |
--keyring <path> | -- | No | -- | Path to MySQL keyring file for decrypting encrypted tablespaces. |
Examples
Quick comparison of two tablespace files
inno diff original.ibd modified.ibd
Verbose field-level diff
inno diff original.ibd modified.ibd -v
Verbose with byte-range analysis
inno diff original.ibd modified.ibd -v -b
Compare a single page
inno diff original.ibd modified.ibd -p 3 -v
JSON output
inno diff original.ibd modified.ibd --json | jq '.summary'
Output
Text Mode
Comparing:
File 1: original.ibd (7 pages, 16384 bytes/page)
File 2: modified.ibd (7 pages, 16384 bytes/page)
Summary:
Identical pages: 5
Modified pages: 2
Only in file 1: 0
Only in file 2: 0
Modified pages: 3, 4
With -v, modified pages show field-level changes:
Page 3: MODIFIED
Checksum: 0xA3B1C5D7 -> 0x12345678
LSN: 5539 -> 6012
Page Type: INDEX (unchanged)
With -v -b, byte-range diffs are appended:
Byte diff ranges:
0-4 (4 bytes)
38-16376 (16338 bytes)
Total: 16342 bytes changed (99.7% of page)
JSON Mode
{
"file1": { "path": "original.ibd", "page_count": 7, "page_size": 16384 },
"file2": { "path": "modified.ibd", "page_count": 7, "page_size": 16384 },
"page_size_mismatch": false,
"summary": {
"identical": 5,
"modified": 2,
"only_in_file1": 0,
"only_in_file2": 0
},
"modified_pages": [
{
"page_number": 3,
"file1_header": { "checksum": "0xA3B1C5D7", "lsn": 5539, "page_type": "INDEX" },
"file2_header": { "checksum": "0x12345678", "lsn": 6012, "page_type": "INDEX" },
"changed_fields": [
{ "field": "Checksum", "old_value": "0xA3B1C5D7", "new_value": "0x12345678" },
{ "field": "LSN", "old_value": "5539", "new_value": "6012" }
]
}
]
}
inno watch
Monitor an InnoDB tablespace file for page-level changes in real time.
Synopsis
inno watch -f <file> [-i <ms>] [-v] [--json] [--page-size <size>] [--keyring <path>]
Description
Polls an InnoDB tablespace file at a configurable interval and reports which pages have been modified, added, or removed since the last poll.
Change detection is based on LSN comparison -- if a page's LSN changes between polls, it was modified by a write. Checksums are validated for each changed page to detect corruption during writes.
The tablespace is re-opened each cycle to detect file growth and avoid stale file handles. This means inno watch will correctly observe pages being added as the tablespace grows.
Three types of changes are reported:
- Modified: A page's LSN changed between polls.
- Added: A new page appeared (tablespace grew).
- Removed: A page disappeared (tablespace shrunk, which is rare).
Press Ctrl+C for a clean exit with a summary of total changes observed.
Flags
| Flag | Short | Required | Default | Description |
|---|---|---|---|---|
--file <path> | -f | Yes | -- | Path to the InnoDB data file (.ibd). |
--interval <ms> | -i | No | 1000 | Polling interval in milliseconds. |
--verbose | -v | No | Off | Show per-field diffs for changed pages (old LSN, new LSN, delta). |
--json | -- | No | Off | Output in NDJSON streaming format (one JSON object per line). |
--page-size <size> | -- | No | Auto-detect | Override page size. |
--keyring <path> | -- | No | -- | Path to MySQL keyring file for decrypting encrypted tablespaces. |
Examples
Watch a tablespace with default 1-second interval
inno watch -f /var/lib/mysql/sakila/actor.ibd
Poll every 500ms with verbose output
inno watch -f actor.ibd -i 500 -v
NDJSON streaming output for log processing
inno watch -f actor.ibd --json | tee changes.ndjson
Pipe to jq for live filtering
inno watch -f actor.ibd --json | jq -c 'select(.event == "poll")'
Output
Text Mode
On startup:
Watching actor.ibd (7 pages, 16384 bytes/page, MySQL)
Polling every 1000ms. Press Ctrl+C to stop.
When changes are detected:
14:32:15 2 pages modified
Page 3 INDEX LSN +473 checksum valid
Page 4 INDEX LSN +473 checksum valid
With --verbose, full LSN values are shown:
14:32:15 2 pages modified
Page 3 INDEX LSN 5539 -> 6012 (+473) checksum valid
Page 4 INDEX LSN 5540 -> 6013 (+473) checksum valid
On Ctrl+C:
Stopped after 12 polls. Total page changes: 6
NDJSON Mode
Each event is a single JSON line. Three event types are emitted:
Started (first line):
{"timestamp":"2026-02-15T14:32:14.123-06:00","event":"started","pages":7,"page_size":16384,"vendor":"MySQL"}
Poll (when changes detected):
{"timestamp":"2026-02-15T14:32:15.125-06:00","event":"poll","pages":7,"modified":2,"added":0,"removed":0,"changes":[{"page":3,"kind":"modified","page_type":"INDEX","old_lsn":5539,"new_lsn":6012,"lsn_delta":473,"checksum_valid":true}]}
Stopped (on Ctrl+C):
{"timestamp":"2026-02-15T14:32:26.130-06:00","event":"stopped","total_changes":6,"total_polls":12}
If an error occurs (e.g., file deleted), an error event is emitted:
{"timestamp":"2026-02-15T14:32:20.127-06:00","event":"error","error":"File no longer exists"}
inno corrupt
Intentionally corrupt pages in an InnoDB tablespace file for testing.
Synopsis
inno corrupt -f <file> [-p <page>] [-b <bytes>] [-k] [-r] [--offset <byte>] [--verify] [--json] [--page-size <size>]
Description
Writes random bytes into a tablespace file to simulate data corruption. This is designed for testing checksum validation (inno checksum), InnoDB crash recovery, and backup-restore verification workflows.
Warning: This command modifies the target file in place. Always work on a copy of the tablespace, never on a production file.
Three targeting modes are available:
-
Header mode (
-k): Writes into the 38-byte FIL header area (bytes 0-37 of the page), which will corrupt page metadata like the checksum, page number, LSN, or space ID. -
Records mode (
-r): Writes into the user data area (after the page header and before the FIL trailer), corrupting actual row or index data without necessarily invalidating the stored checksum. -
Offset mode (
--offset): Writes at an absolute file byte position, bypassing page calculations entirely. Note that--verifyis unavailable in this mode since there is no page context.
If no page number is specified, one is chosen at random. If neither -k nor -r is specified, bytes are written at the beginning of the page.
Flags
| Flag | Short | Required | Default | Description |
|---|---|---|---|---|
--file <path> | -f | Yes | -- | Path to the InnoDB data file (.ibd). |
--page <number> | -p | No | Random | Page number to corrupt. If omitted, a random page is chosen. |
--bytes <count> | -b | No | 1 | Number of random bytes to write. |
--header | -k | No | Off | Target the FIL header area (first 38 bytes of the page). |
--records | -r | No | Off | Target the record data area (after page header, before trailer). |
--offset <byte> | -- | No | -- | Absolute byte offset to corrupt. Bypasses page calculation. |
--verify | -- | No | Off | Show before/after checksum comparison to confirm corruption. |
--json | -- | No | Off | Output in JSON format. |
--page-size <size> | -- | No | Auto-detect | Override page size. |
Examples
Corrupt 1 byte on a random page
inno corrupt -f test_copy.ibd
Corrupt 16 bytes in the header of page 3
inno corrupt -f test_copy.ibd -p 3 -k -b 16
Corrupt the record area of page 5 and verify
inno corrupt -f test_copy.ibd -p 5 -r -b 32 --verify
Corrupt at an absolute file offset
inno corrupt -f test_copy.ibd --offset 65536 -b 8
JSON output for automation
inno corrupt -f test_copy.ibd -p 3 -b 4 --verify --json
Test-and-validate workflow
cp actor.ibd test.ibd
inno corrupt -f test.ibd -p 3 -b 4 --verify
inno checksum -f test.ibd
Output
Text Mode
Writing 16 bytes of random data to test.ibd at offset 49152 (page 3)...
Data written: A3 B1 C5 D7 E2 F0 11 22 33 44 55 66 77 88 99 AA
Completed.
With --verify:
Verification:
Before: OK (algorithm=Crc32c, stored=2741936599, calculated=2741936599)
After: INVALID (algorithm=Crc32c, stored=2741936599, calculated=1084227091)
JSON Mode
{
"file": "test.ibd",
"offset": 49152,
"page": 3,
"bytes_written": 16,
"data": "A3 B1 C5 D7 E2 F0 11 22 33 44 55 66 77 88 99 AA",
"verify": {
"page": 3,
"before": {
"valid": true,
"algorithm": "crc32c",
"stored_checksum": 2741936599,
"calculated_checksum": 2741936599
},
"after": {
"valid": false,
"algorithm": "crc32c",
"stored_checksum": 2741936599,
"calculated_checksum": 1084227091
}
}
}
inno recover
Assess page-level recoverability and count salvageable records in an InnoDB tablespace.
Synopsis
inno recover -f <file> [-p <page>] [-v] [--json] [--force] [--page-size <size>] [--keyring <path>]
Description
Scans a tablespace file and classifies each page into one of four states:
- Intact: Checksum is valid and LSN is consistent between header and trailer.
- Corrupt: Checksum mismatch or LSN inconsistency, but the FIL header is parseable.
- Empty: All-zero page (allocated but unused).
- Unreadable: Page data is too short or the FIL header cannot be parsed.
For INDEX pages, the compact record chain is walked to count recoverable user records. This gives a concrete estimate of how many rows can be salvaged from a damaged tablespace.
Smart page size fallback
When page 0 is damaged and auto-detection fails, inno recover tries common page sizes (16K, 8K, 4K, 32K, 64K) in order, selecting the first one that divides evenly into the file size. Use --page-size to force a specific size when the heuristic is not sufficient.
Force mode
By default, records are only counted on intact INDEX pages. With --force, records are also extracted from corrupt pages that have valid-looking FIL headers. This is useful when the checksum is damaged but the record chain is still intact, allowing more data to be recovered.
Flags
| Flag | Short | Required | Default | Description |
|---|---|---|---|---|
--file <path> | -f | Yes | -- | Path to the InnoDB data file (.ibd). |
--page <number> | -p | No | All pages | Analyze a single page instead of a full scan. |
--verbose | -v | No | Off | Show per-page details (type, status, LSN, record count). |
--json | -- | No | Off | Output in JSON format. |
--force | -- | No | Off | Extract records from corrupt pages with valid headers. |
--page-size <size> | -- | No | Auto-detect (with fallback) | Override page size. Critical when page 0 is corrupt. |
--keyring <path> | -- | No | -- | Path to MySQL keyring file for decrypting encrypted tablespaces. |
Examples
Quick recovery assessment
inno recover -f damaged_table.ibd
Verbose per-page analysis
inno recover -f damaged_table.ibd -v
Force-extract records from corrupt pages
inno recover -f damaged_table.ibd --force -v
Analyze a single page
inno recover -f damaged_table.ibd -p 3 -v
JSON output with per-page detail
inno recover -f damaged_table.ibd --json -v
Override page size for a file with corrupt page 0
inno recover -f damaged_table.ibd --page-size 16384
Output
Text Mode
Recovery Analysis: damaged_table.ibd
File size: 114688 bytes (7 pages x 16384 bytes)
Page size: 16384 (auto-detected)
Page Status Summary:
Intact: 5 pages
Corrupt: 1 pages (pages 3)
Empty: 1 pages
Unreadable: 0 pages
Total: 7 pages
Recoverable INDEX Pages: 3 of 4
Total user records: 200
Records on corrupt pages: 52 (use --force to include)
Overall: 83.3% of pages intact
With --verbose, per-page lines are displayed before the summary:
Page 0: FSP_HDR intact LSN=1000
Page 1: IBUF_BITMAP intact LSN=1001
Page 2: INODE intact LSN=1002
Page 3: INDEX CORRUPT LSN=5000 records=52 checksum mismatch
Page 4: INDEX intact LSN=5001 records=100
Page 5: INDEX intact LSN=5002 records=100
Page 6: Allocated empty LSN=0
JSON Mode
{
"file": "damaged_table.ibd",
"file_size": 114688,
"page_size": 16384,
"total_pages": 7,
"summary": {
"intact": 5,
"corrupt": 1,
"empty": 1,
"unreadable": 0
},
"recoverable_records": 200,
"force_recoverable_records": 52,
"pages": []
}
With --verbose, the pages array includes per-page detail. When combined with --verbose --json, each INDEX page also includes per-record data with byte offsets, heap numbers, delete marks, and hex-encoded record bytes.
inno find
Search a MySQL data directory for pages matching a given page number.
Synopsis
inno find -d <datadir> -p <page> [-c <checksum>] [-s <space_id>] [--first] [--json] [--page-size <size>]
Description
Recursively discovers all .ibd files under a MySQL data directory, opens each as a tablespace, and reads the FIL header of every page looking for a matching page_number field. This is useful for locating which tablespace file contains a specific page when you have a page number from an error log or diagnostic tool.
Optional filters narrow results when the same page number appears in multiple tablespaces:
--checksum: Only match pages whose stored checksum (bytes 0-3 of the FIL header) equals the given value.--space-id: Only match pages whose space ID (bytes 34-37 of the FIL header) equals the given value.
With --first, searching stops after the first match across all files, providing a fast lookup when only one hit is expected.
A progress bar is displayed for the file-level scan (suppressed in --json mode).
Flags
| Flag | Short | Required | Default | Description |
|---|---|---|---|---|
--datadir <path> | -d | Yes | -- | MySQL data directory path to search. |
--page <number> | -p | Yes | -- | Page number to search for across all tablespace files. |
--checksum <value> | -c | No | -- | Only match pages with this stored checksum value. |
--space-id <id> | -s | No | -- | Only match pages in this tablespace (by space ID). |
--first | -- | No | Off | Stop after the first match for faster lookups. |
--json | -- | No | Off | Output in JSON format. |
--page-size <size> | -- | No | Auto-detect | Override page size. |
Examples
Search for page 3 across all tablespaces
inno find -d /var/lib/mysql -p 3
Search with space ID filter
inno find -d /var/lib/mysql -p 3 -s 42
Stop at first match
inno find -d /var/lib/mysql -p 3 --first
JSON output
inno find -d /var/lib/mysql -p 3 --json | jq '.matches'
Search with checksum filter
inno find -d /var/lib/mysql -p 3 -c 2741936599
Output
Text Mode
Checking sakila/actor.ibd..
Checking sakila/film.ibd..
Found page 3 in sakila/actor.ibd (checksum: 2741936599, space_id: 42)
Found 1 match(es) in 2 file(s) searched.
If no match is found:
Page 3 not found in any .ibd file.
JSON Mode
{
"datadir": "/var/lib/mysql",
"target_page": 3,
"matches": [
{
"file": "sakila/actor.ibd",
"page_number": 3,
"checksum": 2741936599,
"space_id": 42
}
],
"files_searched": 2
}
inno tsid
List or look up tablespace IDs from files in a MySQL data directory.
Synopsis
inno tsid -d <datadir> [-l] [-t <tsid>] [--json] [--page-size <size>]
Description
Scans .ibd (tablespace) and .ibu (undo tablespace) files under a MySQL data directory, opens page 0 of each, and reads the space ID from the FSP header at offset FIL_PAGE_DATA (byte 38). The space ID uniquely identifies each tablespace within a MySQL instance and appears in error logs, INFORMATION_SCHEMA.INNODB_TABLESPACES, and the FIL header of every page.
Two modes are available:
-
List mode (
-l): Prints every discovered file alongside its space ID, sorted by file path. Useful for building a map of the data directory. -
Lookup mode (
-t <id>): Filters results to only the file(s) with the given space ID. Useful for resolving a space ID from an error message back to a physical.ibdfile on disk.
If neither -l nor -t is specified, all discovered tablespaces are listed (same behavior as -l).
Flags
| Flag | Short | Required | Default | Description |
|---|---|---|---|---|
--datadir <path> | -d | Yes | -- | MySQL data directory path to scan. |
--list | -l | No | Off | List all tablespace IDs found in the data directory. |
--tsid <id> | -t | No | -- | Find the tablespace file with this specific space ID. |
--json | -- | No | Off | Output in JSON format. |
--page-size <size> | -- | No | Auto-detect | Override page size. |
Examples
List all tablespace IDs
inno tsid -d /var/lib/mysql -l
Find which file owns space ID 42
inno tsid -d /var/lib/mysql -t 42
JSON output for all tablespaces
inno tsid -d /var/lib/mysql -l --json
Look up a space ID from an error log
# Error log says: "InnoDB: Error in space 42"
inno tsid -d /var/lib/mysql -t 42
Output
Text Mode
List mode:
sakila/actor.ibd - Space ID: 42
sakila/film.ibd - Space ID: 43
sakila/film_actor.ibd - Space ID: 44
Lookup mode when no match is found:
Tablespace ID 999 not found.
JSON Mode
{
"datadir": "/var/lib/mysql",
"tablespaces": [
{ "file": "sakila/actor.ibd", "space_id": 42 },
{ "file": "sakila/film.ibd", "space_id": 43 },
{ "file": "sakila/film_actor.ibd", "space_id": 44 }
]
}
inno sdi
Extract SDI (Serialized Dictionary Information) metadata from MySQL 8.0+ tablespaces.
Synopsis
inno sdi -f <file> [--pretty] [--page-size <size>] [--keyring <path>]
Description
Locates SDI pages in a tablespace by scanning for page type 17853 (FIL_PAGE_SDI), then reassembles multi-page SDI records by following the page chain. The zlib-compressed payload is decompressed and printed as JSON.
Each tablespace in MySQL 8.0+ embeds its own table, column, and index definitions as SDI records, eliminating the need for the .frm files used in MySQL 5.x. This command extracts that embedded metadata for inspection, backup verification, or schema reconstruction.
SDI records contain:
- Table SDI (type 1): Full table definition including column names, types, charset, default values, and index definitions.
- Tablespace SDI (type 2): Tablespace properties.
Use --pretty for indented JSON output. Without it, the raw JSON string from the SDI record is printed as-is.
MariaDB tablespaces do not use SDI. Running this command against a MariaDB tablespace will produce an error message explaining that MariaDB stores metadata differently.
Flags
| Flag | Short | Required | Default | Description |
|---|---|---|---|---|
--file <path> | -f | Yes | -- | Path to the InnoDB data file (.ibd). |
--pretty | -p | No | Off | Pretty-print the extracted JSON with indentation. |
--page-size <size> | -- | No | Auto-detect | Override page size. |
--keyring <path> | -- | No | -- | Path to MySQL keyring file for decrypting encrypted tablespaces. |
Examples
Extract SDI metadata
inno sdi -f /var/lib/mysql/sakila/actor.ibd
Pretty-print the JSON output
inno sdi -f actor.ibd --pretty
Pipe to jq for column extraction
inno sdi -f actor.ibd --pretty | jq '.dd_object.columns[].name'
Extract SDI from an encrypted tablespace
inno sdi -f encrypted_table.ibd --keyring /path/to/keyring --pretty
Output
Found 1 SDI page(s): [3]
=== SDI Record: type=1 (Table), id=373
Compressed: 482 bytes, Uncompressed: 2048 bytes
{
"mysqld_version_id": 80400,
"dd_version": 80300,
"sdi_version": 80019,
"dd_object_type": "Table",
"dd_object": {
"name": "actor",
"columns": [
{ "name": "actor_id", "type": 4, ... },
{ "name": "first_name", "type": 16, ... },
{ "name": "last_name", "type": 16, ... },
{ "name": "last_update", "type": 18, ... }
],
"indexes": [
{ "name": "PRIMARY", "type": 1, ... },
{ "name": "idx_actor_last_name", "type": 2, ... }
],
...
}
}
Total SDI records: 1
If the tablespace has no SDI pages (e.g., a pre-MySQL 8.0 file):
No SDI pages found in actor.ibd.
SDI is only available in MySQL 8.0+ tablespaces.
inno log
Analyze InnoDB redo log files.
Synopsis
inno log -f <file> [-b <blocks>] [--no-empty] [-v] [--json]
Description
Opens an InnoDB redo log file and displays its internal structure. Supports both legacy and modern redo log formats:
- Legacy format (MySQL < 8.0.30):
ib_logfile0andib_logfile1files in the data directory. - Modern format (MySQL 8.0.30+):
#ib_redo*files in the#innodb_redo/subdirectory.
InnoDB redo logs are organized as a sequence of 512-byte blocks:
- Block 0: Log file header (group ID, start LSN, file number, creator string).
- Block 1: Checkpoint record 1 (checkpoint number, LSN, offset, buffer size, archived LSN).
- Block 2: Reserved/unused.
- Block 3: Checkpoint record 2.
- Blocks 4+: Data blocks containing the actual redo log records.
For each data block, the header is decoded to show the block number, data length, first-record-group offset, checkpoint number, flush flag, and CRC-32C checksum validation status.
With --verbose, the payload bytes of each non-empty data block are scanned for MLOG record type bytes (e.g., MLOG_REC_INSERT, MLOG_UNDO_INSERT, MLOG_WRITE_STRING) and a frequency summary is printed. MLOG record type decoding is skipped for MariaDB redo logs due to incompatible format.
Vendor detection is performed automatically from the log file header's creator string.
Flags
| Flag | Short | Required | Default | Description |
|---|---|---|---|---|
--file <path> | -f | Yes | -- | Path to redo log file (ib_logfile0, ib_logfile1, or #ib_redo*). |
--blocks <count> | -b | No | All data blocks | Limit output to the first N data blocks. |
--no-empty | -- | No | Off | Skip blocks that contain no redo log data. |
--verbose | -v | No | Off | Decode and display MLOG record types within each data block. |
--json | -- | No | Off | Output in JSON format. |
Examples
Analyze a legacy redo log
inno log -f /var/lib/mysql/ib_logfile0
Analyze a MySQL 8.0.30+ redo log
inno log -f '/var/lib/mysql/#innodb_redo/#ib_redo10'
Show only the first 10 data blocks
inno log -f ib_logfile0 -b 10
Skip empty blocks
inno log -f ib_logfile0 --no-empty
Verbose output with MLOG record types
inno log -f ib_logfile0 -v
JSON output
inno log -f ib_logfile0 --json | jq '.header'
Output
Text Mode
InnoDB Redo Log File
File: ib_logfile0
Size: 50331648 bytes
Blocks: 98304 total (98300 data)
Log File Header (block 0)
Group ID: 0
Start LSN: 19217920
File No: 0
Created by: MySQL 8.0.40
Vendor: MySQL
Checkpoint 1 (block 1)
Number: 42
LSN: 19218432
Offset: 1024
Buffer size: 8388608
Checkpoint 2 (block 3)
Number: 41
LSN: 19218000
Offset: 512
Buffer size: 8388608
Data Blocks
Block 4 no=4 len=492 first_rec=12 chk_no=42 csum=OK
Block 5 no=5 len=512 first_rec=0 chk_no=42 csum=OK FLUSH
Block 6 no=6 len=200 first_rec=12 chk_no=42 csum=OK
Displayed 3 data blocks (of 98300)
With --verbose, record type summaries appear under each data block:
Block 4 no=4 len=492 first_rec=12 chk_no=42 csum=OK
record types: MLOG_COMP_REC_INSERT(12), MLOG_WRITE_STRING(8), MLOG_COMP_PAGE_CREATE(2)
JSON Mode
{
"file": "ib_logfile0",
"file_size": 50331648,
"total_blocks": 98304,
"data_blocks": 98300,
"header": {
"group_id": 0,
"start_lsn": 19217920,
"file_no": 0,
"created_by": "MySQL 8.0.40"
},
"checkpoint_1": {
"number": 42,
"lsn": 19218432,
"offset": 1024,
"buf_size": 8388608,
"archived_lsn": 0
},
"checkpoint_2": { "..." : "..." },
"blocks": [
{
"block_index": 4,
"block_no": 4,
"flush_flag": false,
"data_len": 492,
"first_rec_group": 12,
"checkpoint_no": 42,
"checksum_valid": true,
"record_types": ["MLOG_COMP_REC_INSERT", "MLOG_WRITE_STRING"]
}
]
}
inno info
Inspect InnoDB system files, compare LSNs, or query a live MySQL instance.
Synopsis
inno info [--ibdata] [--lsn-check] [-d <datadir>] [-D <database> -t <table>] [--host <host>] [--port <port>] [--user <user>] [--password <pass>] [--defaults-file <path>] [--json] [--page-size <size>]
Description
Operates in three mutually exclusive modes:
ibdata1 inspection (--ibdata)
Reads page 0 of ibdata1 (the system tablespace) and decodes its FIL header -- checksum, page type, LSN, flush LSN, and space ID. Also attempts to read checkpoint LSNs from the redo log, trying the MySQL 8.0.30+ #innodb_redo/#ib_redo* directory first, then falling back to the legacy ib_logfile0.
This gives a quick snapshot of the system tablespace state without starting MySQL.
LSN consistency check (--lsn-check)
Compares the LSN from the ibdata1 page 0 header with the latest redo log checkpoint LSN. If they match, the system is "in sync" and InnoDB shut down cleanly. If not, the difference in bytes is reported, indicating that crash recovery may be needed.
MySQL query mode (-D <database> -t <table>)
Requires the mysql feature (cargo build --features mysql).
Connects to a live MySQL instance and queries INFORMATION_SCHEMA.INNODB_TABLES and INNODB_INDEXES for the space ID, table ID, index names, and root page numbers. Also parses SHOW ENGINE INNODB STATUS for the current log sequence number and transaction ID counter.
Connection parameters come from CLI flags or a .my.cnf defaults file. CLI flags override defaults file values.
Flags
| Flag | Short | Required | Default | Description |
|---|---|---|---|---|
--ibdata | -- | No | -- | Inspect ibdata1 page 0 header and redo log checkpoints. |
--lsn-check | -- | No | -- | Compare ibdata1 and redo log LSNs for sync status. |
--datadir <path> | -d | No | /var/lib/mysql | MySQL data directory path. Used by --ibdata and --lsn-check. |
--database <name> | -D | No | -- | Database name for MySQL query mode. |
--table <name> | -t | No | -- | Table name for MySQL query mode. |
--host <host> | -- | No | localhost | MySQL host for live queries. |
--port <port> | -- | No | 3306 | MySQL port for live queries. |
--user <user> | -- | No | root | MySQL user for live queries. |
--password <pass> | -- | No | -- | MySQL password for live queries. |
--defaults-file <path> | -- | No | Auto-detect .my.cnf | Path to a MySQL defaults file. |
--json | -- | No | Off | Output in JSON format. |
--page-size <size> | -- | No | Auto-detect | Override page size for ibdata1 reading. |
Examples
Inspect ibdata1 header
inno info --ibdata -d /var/lib/mysql
Check LSN sync status
inno info --lsn-check -d /var/lib/mysql
Query table info from a live MySQL instance
inno info -D sakila -t actor --host 127.0.0.1 --user root --password secret
Use a defaults file for MySQL connection
inno info -D sakila -t actor --defaults-file ~/.my.cnf
JSON output for ibdata1 inspection
inno info --ibdata -d /var/lib/mysql --json
Scripted sync check
if inno info --lsn-check -d /var/lib/mysql --json | jq -e '.in_sync'; then
echo "InnoDB is in sync"
else
echo "InnoDB may need crash recovery"
fi
Output
ibdata1 Inspection (Text Mode)
ibdata1 Page 0 Header
File: /var/lib/mysql/ibdata1
Checksum: 2741936599
Page No: 0
Page Type: 8 (FSP_HDR)
LSN: 19218432
Flush LSN: 19218432
Space ID: 0
Redo Log Checkpoint 1 LSN: 19218432
Redo Log Checkpoint 2 LSN: 19218000
ibdata1 Inspection (JSON Mode)
{
"ibdata_file": "/var/lib/mysql/ibdata1",
"page_checksum": 2741936599,
"page_number": 0,
"page_type": 8,
"lsn": 19218432,
"flush_lsn": 19218432,
"space_id": 0,
"redo_checkpoint_1_lsn": 19218432,
"redo_checkpoint_2_lsn": 19218000
}
LSN Check (Text Mode)
LSN Sync Check
ibdata1 LSN: 19218432
Redo checkpoint LSN: 19218432
Status: IN SYNC
If out of sync:
LSN Sync Check
ibdata1 LSN: 19218432
Redo checkpoint LSN: 19217920
Status: OUT OF SYNC
Difference: 512 bytes
LSN Check (JSON Mode)
{
"ibdata_lsn": 19218432,
"redo_checkpoint_lsn": 19218432,
"in_sync": true
}
MySQL Query Mode
Table: sakila.actor
Space ID: 42
Table ID: 1234
Indexes:
PRIMARY (index_id=157, root_page=3)
idx_actor_last_name (index_id=158, root_page=4)
InnoDB Status:
Log sequence number 19218432
Log flushed up to 19218432
Trx id counter 12345
inno repair
Recalculate and fix corrupt page checksums.
Usage
# Repair a single file
inno repair -f table.ibd
# Repair a specific page
inno repair -f table.ibd -p 5
# Dry run (preview without modifying)
inno repair -f table.ibd --dry-run
# Batch repair all files in a directory
inno repair --batch /var/lib/mysql
# Force a specific algorithm
inno repair -f table.ibd -a crc32c
# Skip backup creation
inno repair -f table.ibd --no-backup
Options
| Option | Description |
|---|---|
-f, --file | Path to InnoDB data file |
--batch | Repair all .ibd files under a directory |
-p, --page | Repair only a specific page number |
-a, --algorithm | Checksum algorithm: auto, crc32c, innodb, full_crc32 (default: auto) |
--no-backup | Skip creating a .bak backup |
--dry-run | Preview repairs without modifying files |
-v, --verbose | Show per-page repair details |
--json | Output in JSON format |
--page-size | Override page size |
--keyring | Path to MySQL keyring file |
Behavior
- Auto-detects the checksum algorithm from page 0 unless
--algorithmis specified - Creates a
.bakbackup before modifying the file (unless--no-backup) - Only rewrites pages with invalid checksums
- Batch mode processes files in parallel using rayon
- Compatible with
--audit-logfor write operation tracking
inno defrag
Defragment a tablespace by reclaiming free space and reordering pages.
Usage
inno defrag -f table.ibd -o table_defrag.ibd
inno defrag -f table.ibd -o table_defrag.ibd -v --json
Options
| Option | Description |
|---|---|
-f, --file | Path to source InnoDB data file |
-o, --output | Path to output file (required) |
-v, --verbose | Show per-page details |
--json | Output in JSON format |
--page-size | Override page size |
--keyring | Path to MySQL keyring file |
Behavior
- Reads all pages from the source file
- Removes empty and corrupt pages
- Sorts INDEX pages by (index_id, level, page_number)
- Fixes prev/next chain pointers within each index group
- Renumbers pages sequentially
- Rebuilds page 0 (FSP_HDR)
- Recalculates all checksums
- Writes to a new output file (source is never modified)
inno transplant
Copy specific pages from a donor tablespace into a target.
Usage
# Transplant pages 3 and 5 from donor to target
inno transplant donor.ibd target.ibd -p 3,5
# Preview without modifying
inno transplant donor.ibd target.ibd -p 3,5 --dry-run
# Force transplant (skip safety checks)
inno transplant donor.ibd target.ibd -p 3,5 --force
Options
| Option | Description |
|---|---|
donor | Path to donor tablespace file (source of pages) |
target | Path to target tablespace file (destination) |
-p, --pages | Page numbers to transplant (comma-separated) |
--no-backup | Skip creating a backup of the target |
--force | Allow space ID mismatch, corrupt donor pages, and page 0 transplant |
--dry-run | Preview without modifying the target |
-v, --verbose | Show per-page details |
--json | Output in JSON format |
--page-size | Override page size |
--keyring | Path to MySQL keyring file |
Safety Checks
- Page sizes must match between donor and target
- Space IDs must match (unless
--force) - Page 0 (FSP_HDR) is rejected (unless
--force) - Donor pages with invalid checksums are skipped (unless
--force) - A backup of the target is created by default
inno schema
Extract schema and reconstruct DDL from tablespace metadata.
Usage
# Show DDL
inno schema -f users.ibd
# Verbose output with column/index breakdown
inno schema -f users.ibd -v
# JSON output
inno schema -f users.ibd --json
Options
| Option | Description |
|---|---|
-f, --file | Path to InnoDB data file |
-v, --verbose | Show structured schema breakdown above the DDL |
--json | Output in JSON format |
--page-size | Override page size |
--keyring | Path to MySQL keyring file |
Behavior
For MySQL 8.0+ tablespaces with SDI metadata:
- Parses the embedded data dictionary JSON
- Extracts column definitions, indexes, and foreign keys
- Reconstructs a complete
CREATE TABLEDDL statement - Resolves column types, defaults, character sets, and collations
For pre-8.0 tablespaces without SDI:
- Scans INDEX pages to infer basic index structure
- Determines record format (compact vs. redundant)
- Provides limited structural information
inno export
Export record-level data from a tablespace.
Usage
# CSV export (default)
inno export -f users.ibd
# JSON export
inno export -f users.ibd --format json
# Hex dump
inno export -f users.ibd --format hex
# Export specific page
inno export -f users.ibd -p 3
# Include delete-marked records only
inno export -f users.ibd --where-delete-mark
# Include system columns
inno export -f users.ibd --system-columns
Options
| Option | Description |
|---|---|
-f, --file | Path to InnoDB data file |
-p, --page | Export records from a specific page only |
--format | Output format: csv, json, or hex (default: csv) |
--where-delete-mark | Include only delete-marked records |
--system-columns | Include DB_TRX_ID and DB_ROLL_PTR columns |
-v, --verbose | Show additional details |
--page-size | Override page size |
--keyring | Path to MySQL keyring file |
Supported Types
See the Data Type Decoding guide for the full list of supported column types and encoding details.
inno health
Per-index B+Tree health metrics including fill factor, fragmentation, and garbage ratio.
Usage
# Text output
inno health -f users.ibd
# JSON output
inno health -f users.ibd --json
# Prometheus metrics
inno health -f users.ibd --prometheus
# Verbose output
inno health -f users.ibd -v
Options
| Option | Description |
|---|---|
-f, --file | Path to InnoDB data file |
-v, --verbose | Show additional detail (records, empty leaves) |
--json | Output in JSON format |
--prometheus | Output in Prometheus exposition format |
--page-size | Override page size |
--keyring | Path to MySQL keyring file |
--bloat | Compute bloat scores (grade A-F) per index |
--cardinality | Estimate cardinality of leading primary key columns |
--sample-size | Number of leaf pages to sample per index for cardinality (default: 100) |
Metrics
| Metric | Description |
|---|---|
| Fill factor | Average, min, and max page utilization (0-100%) |
| Garbage ratio | Percentage of space occupied by deleted records |
| Fragmentation | How out-of-order pages are relative to sequential layout |
| Tree depth | B+Tree depth per index |
| Page counts | Total, leaf, and internal page counts per index |
Bloat Scoring
Use --bloat to compute a weighted bloat score and letter grade (A-F) for each index:
inno health -f users.ibd --bloat
The score combines fill factor deficit (30%), garbage ratio (25%), fragmentation (25%), and delete-mark ratio (20%). See the Bloat Scoring guide for the full formula and grade definitions.
Cardinality Estimation
Use --cardinality to estimate distinct values for the leading primary key column using deterministic page sampling:
inno health -f users.ibd --cardinality --sample-size 200
inno audit
Audit a MySQL data directory for integrity, health, or corruption.
Usage
# Default: integrity check (checksum validation)
inno audit -d /var/lib/mysql
# Health mode: per-tablespace metrics
inno audit -d /var/lib/mysql --health
# Checksum mismatch mode: list only corrupt pages
inno audit -d /var/lib/mysql --checksum-mismatch
# Filter unhealthy tablespaces
inno audit -d /var/lib/mysql --health --min-fill-factor 50
# JSON output
inno audit -d /var/lib/mysql --json
# Prometheus metrics
inno audit -d /var/lib/mysql --health --prometheus
Options
| Option | Description |
|---|---|
-d, --datadir | MySQL data directory path |
--health | Show per-tablespace health metrics |
--checksum-mismatch | List only pages with checksum mismatches |
-v, --verbose | Show additional details |
--json | Output in JSON format |
--prometheus | Output in Prometheus exposition format |
--page-size | Override page size |
--keyring | Path to MySQL keyring file |
--min-fill-factor | Filter: show tables with fill factor below threshold (0-100) |
--max-fragmentation | Filter: show tables with fragmentation above threshold (0-100) |
--bloat | Enable bloat scoring in health mode |
--max-bloat-grade | Filter: show tables with worst bloat grade at or worse than threshold (A-F) |
--depth | Maximum directory recursion depth (default: 2, 0 = unlimited) |
Modes
Integrity Mode (default)
Validates checksums across all tablespace files. Reports per-file pass/fail with a directory-wide integrity percentage.
Health Mode
Computes per-tablespace fill factor, fragmentation, and garbage ratio, ranked worst-first. Use threshold filters to focus on unhealthy tablespaces.
Bloat Alerts
Use --bloat with health mode to compute per-tablespace bloat scores and grades:
inno audit -d /var/lib/mysql --health --bloat
inno audit -d /var/lib/mysql --health --max-bloat-grade C --json
The --max-bloat-grade flag filters to tables at or worse than the given grade (A through F). It implies --bloat. See the Bloat Scoring guide for grade definitions.
Checksum Mismatch Mode
Compact listing of only corrupt pages with stored vs. calculated checksums. Suitable for piping to inno repair.
inno compat
Check tablespace compatibility with a target MySQL version.
Usage
# Single file check
inno compat -f users.ibd -t 8.4.0
# Directory scan
inno compat --scan /var/lib/mysql -t 9.0.0
# Verbose output
inno compat -f users.ibd -t 8.4.0 -v
# JSON output
inno compat -f users.ibd -t 8.4.0 --json
Options
| Option | Description |
|---|---|
-f, --file | Path to InnoDB data file (mutually exclusive with --scan) |
-s, --scan | Scan a data directory (mutually exclusive with --file) |
-t, --target | Target MySQL version (e.g., "8.4.0", "9.0.0") |
-v, --verbose | Show detailed check information |
--json | Output in JSON format |
--page-size | Override page size |
--keyring | Path to MySQL keyring file |
--depth | Maximum directory recursion depth (default: 2, 0 = unlimited) |
Checks
See the Upgrade Compatibility guide for the full list of checks and common upgrade scenarios.
See the MySQL Version Matrix for a reference of feature support across MySQL versions.
inno verify
Verify structural integrity of a tablespace.
Usage
# Basic verification
inno verify -f users.ibd
# Verbose output
inno verify -f users.ibd -v
# JSON output
inno verify -f users.ibd --json
# Verify with redo log
inno verify -f users.ibd --redo ib_logfile0
# Verify backup chain
inno verify --chain full.ibd incr1.ibd incr2.ibd
Options
| Option | Description |
|---|---|
-f, --file | Path to InnoDB data file |
-v, --verbose | Show per-page findings |
--json | Output in JSON format |
--page-size | Override page size |
--keyring | Path to MySQL keyring file |
--redo | Path to redo log file for LSN continuity check |
--chain | Verify backup chain (accepts multiple files) |
--backup-meta | Path to XtraBackup checkpoint file for LSN cross-reference |
Structural Checks
| Check | Description |
|---|---|
| PageNumberSequence | Page numbers match expected file positions |
| SpaceIdConsistency | All pages have consistent space IDs |
| LsnMonotonicity | LSNs are non-decreasing |
| BTreeLevelConsistency | B+Tree levels are valid |
| PageChainBounds | prev/next pointers within bounds |
| TrailerLsnMatch | Trailer LSN matches header LSN |
Backup Metadata Verification
Use --backup-meta to cross-reference tablespace LSNs against an XtraBackup checkpoint file:
inno verify -f users.ibd --backup-meta /backups/full/xtrabackup_checkpoints
Pages with LSNs outside the checkpoint's from_lsn..to_lsn window are reported as inconsistent with the backup point-in-time.
See the Backup Verification guide for detailed usage of --chain, --redo, and --backup-meta.
inno validate
Cross-validate tablespace files against live MySQL metadata.
Usage
# Disk-only scan
inno validate -d /var/lib/mysql
# MySQL cross-validation
inno validate -d /var/lib/mysql --host 127.0.0.1 --user root
# Filter by database
inno validate -d /var/lib/mysql --host 127.0.0.1 --user root -D mydb
# Deep table validation
inno validate -d /var/lib/mysql --host 127.0.0.1 --user root --table mydb.users
# JSON output
inno validate -d /var/lib/mysql --host 127.0.0.1 --user root --json
Options
| Option | Description |
|---|---|
-d, --datadir | Path to MySQL data directory |
-D, --database | Database name to filter |
-t, --table | Deep-validate a specific table (format: db.table) |
--host | MySQL server hostname |
--port | MySQL server port |
-u, --user | MySQL username |
-p, --password | MySQL password |
--defaults-file | Path to MySQL defaults file |
-v, --verbose | Show detailed output |
--json | Output in JSON format |
--page-size | Override page size |
--depth | Maximum directory recursion depth |
Note: MySQL cross-validation requires the
mysqlfeature:cargo build --features mysql
See the Live MySQL Validation guide for detailed usage.
inno undo
Analyze undo tablespace files (.ibu or .ibd) for rollback segment and transaction history.
Usage
# Text summary
inno undo -f undo_001.ibu
# JSON output
inno undo -f undo_001.ibu --json
# Specific undo page
inno undo -f undo_001.ibu -p 3
# Verbose with undo records
inno undo -f undo_001.ibu -v
# Encrypted tablespace
inno undo -f undo_001.ibu --keyring /var/lib/mysql-keyring/keyring
Options
| Option | Description |
|---|---|
-f, --file | Path to InnoDB undo tablespace file (.ibu or .ibd) |
-p, --page | Show a specific undo page only |
-v, --verbose | Show additional detail including undo records |
--json | Output in JSON format |
--page-size | Override page size (default: auto-detect) |
--keyring | Path to MySQL keyring file for encrypted tablespaces |
--mmap | Use memory-mapped I/O (faster for large files) |
Output
Text Mode
Text output shows three sections:
-
RSEG Array — rollback segment slots from the RSEG array header page, including which slots are active vs empty.
-
Segment Summary — per-segment state (Active, Cached, ToPurge, ToFree, Prepared), transaction count, and undo log type.
-
Transaction Listing — undo log headers with transaction IDs, transaction numbers, type (INSERT/UPDATE), and XID presence.
JSON Mode
Returns a structured UndoAnalysis object:
{
"rseg_slots": [3, 4, 5],
"rseg_headers": [...],
"segments": [
{
"page_no": 3,
"page_header": { "type_code": 2, "start": 150, "free": 256 },
"segment_header": { "state": "Active", "last_log": 150 },
"log_headers": [
{
"trx_id": 12345,
"trx_no": 12344,
"del_marks": false,
"log_start": 176,
"xid_exists": true,
"dict_trans": false
}
],
"record_count": 8
}
],
"total_transactions": 42,
"active_transactions": 3
}
Undo Segment States
| State | Description |
|---|---|
| Active | Currently in use by an active transaction |
| Cached | Available for reuse, contains committed history |
| ToPurge | Marked for purge by the background purge thread |
| ToFree | Marked for segment deallocation |
| Prepared | Part of an XA transaction in the PREPARED state |
Background
MySQL 8.0+ supports dedicated undo tablespaces (.ibu files) that can be created, dropped, and truncated independently. Each undo tablespace contains a rollback segment (RSEG) array header page that points to up to 128 rollback segments, each of which manages up to 1024 undo log slots.
The inno undo subcommand reads the RSEG array, follows the slot pointers to rollback segment header pages, then walks undo segment pages to extract log headers and (with -v) individual undo records.
inno binlog
Parse and analyze MySQL binary log files.
Usage
# Text summary
inno binlog -f mysql-bin.000001
# JSON output
inno binlog -f mysql-bin.000001 --json
# Limit event listing
inno binlog -f mysql-bin.000001 -l 100
# Filter by event type
inno binlog -f mysql-bin.000001 --filter-type TABLE_MAP
# Verbose (show column types)
inno binlog -f mysql-bin.000001 -v
Options
| Option | Description |
|---|---|
-f, --file | Path to MySQL binary log file |
-l, --limit | Maximum number of events to display |
--filter-type | Filter events by type name (e.g. TABLE_MAP, WRITE_ROWS) |
-v, --verbose | Show additional detail (column types for TABLE_MAP events) |
--json | Output in JSON format |
Output
Text Mode
Text output shows:
-
Format Description — server version, binlog version, creation timestamp, checksum algorithm.
-
Event Type Distribution — counts per event type, sorted by frequency.
-
Table Maps — databases and tables referenced in TABLE_MAP events with column counts.
-
Event Listing — chronological event table with offset, type, timestamp, server ID, and event length.
JSON Mode
Returns a structured BinlogAnalysis object:
{
"format_description": {
"binlog_version": 4,
"server_version": "8.0.35",
"create_timestamp": 1700000000,
"header_length": 19,
"checksum_alg": 1
},
"event_count": 1542,
"event_type_counts": {
"TABLE_MAP_EVENT": 200,
"WRITE_ROWS_EVENT_V2": 180,
"QUERY_EVENT": 150
},
"table_maps": [
{
"table_id": 108,
"database_name": "mydb",
"table_name": "users",
"column_count": 5,
"column_types": [3, 15, 15, 12, 3]
}
],
"events": [...]
}
Event Types
The parser recognizes all standard MySQL binary log event types (0-40+). Key event types:
| Type Code | Name | Description |
|---|---|---|
| 2 | QUERY_EVENT | SQL statement (DDL or statement-based DML) |
| 15 | FORMAT_DESCRIPTION_EVENT | Binlog file header metadata |
| 16 | XID_EVENT | Transaction commit marker |
| 19 | TABLE_MAP_EVENT | Table schema mapping for row events |
| 30 | WRITE_ROWS_EVENT_V2 | Row-based INSERT data |
| 31 | UPDATE_ROWS_EVENT_V2 | Row-based UPDATE data (before/after) |
| 32 | DELETE_ROWS_EVENT_V2 | Row-based DELETE data |
| 33 | GTID_LOG_EVENT | Global Transaction ID |
Background
MySQL binary logs record all data-modifying operations for replication and point-in-time recovery. Binary log files start with a 4-byte magic number (\xfe\x62\x69\x6e) followed by a FORMAT_DESCRIPTION_EVENT, then a sequence of events.
The inno binlog subcommand reads the file header, validates the magic bytes, parses the format description, then streams through all events to produce type distribution statistics and a detailed event listing. Row-based events are parsed to extract table mappings and row counts.
Binary log files use little-endian byte order, unlike InnoDB tablespace files which use big-endian.
inno undelete
Recover deleted records from InnoDB tablespace files using multiple recovery strategies.
Usage
# Scan for deleted records (CSV output)
inno undelete -f employees.ibd
# JSON record output
inno undelete -f employees.ibd --format json
# SQL INSERT statements
inno undelete -f employees.ibd --format sql
# Filter by minimum confidence
inno undelete -f employees.ibd --confidence 0.5
# Include undo log scanning for deeper recovery
inno undelete -f employees.ibd --undo-file undo_001.ibu
# Full metadata JSON envelope
inno undelete -f employees.ibd --json
# Filter by table name
inno undelete -f employees.ibd --table employees
# Scan a specific page only
inno undelete -f employees.ibd -p 4
# Verbose output with recovery details
inno undelete -f employees.ibd -v
# Encrypted tablespace
inno undelete -f employees.ibd --keyring /var/lib/mysql-keyring/keyring
Options
| Option | Description |
|---|---|
-f, --file | Path to the InnoDB tablespace file (.ibd) |
--undo-file | Path to an undo tablespace (ibdata1 or .ibu) for undo log scanning |
--table | Filter by table name |
--min-trx-id | Minimum transaction ID to include |
--confidence | Minimum confidence threshold (0.0–1.0, default: 0.0) |
--format | Record output format: csv, json, sql, hex (default: csv) |
--json | Output full metadata JSON envelope (overrides --format) |
-v, --verbose | Show additional recovery detail |
-p, --page | Recover from a specific page only |
--page-size | Override page size (default: auto-detect) |
--keyring | Path to MySQL keyring file for encrypted tablespaces |
--mmap | Use memory-mapped I/O (faster for large files) |
Recovery Strategies
inno undelete uses three complementary strategies, each with a different confidence level:
1. Delete-Marked Records (confidence: 1.0)
Scans INDEX pages for records with the delete mark flag set. These are records that have been DELETEd but not yet purged by the InnoDB background purge thread. Highest confidence because the record data is fully intact.
2. Free-List Records (confidence: 0.2–0.7)
Walks the page-level free list to find records that have been removed from the active record chain but still exist in page memory. Confidence varies based on how much of the record structure is intact.
3. Undo Log Records (confidence: 0.1–0.3)
When --undo-file is provided, parses undo log entries to reconstruct before-images of modified or deleted rows. Lower confidence because undo records may be incomplete or partially overwritten.
Output Formats
CSV (default)
Comma-separated values with column headers. Suitable for import into spreadsheets or databases.
JSON
One JSON object per record with column names as keys.
SQL
INSERT INTO statements that can be executed directly against a MySQL table to restore records.
Hex
Raw hex dump of recovered record bytes for manual inspection.
JSON Envelope (--json)
Full metadata output including scan summary, recovery statistics per strategy, and all recovered records with confidence scores and source information.
{
"table_name": "employees",
"strategies_used": ["delete_marked", "free_list"],
"summary": {
"total_records": 15,
"by_source": {
"delete_marked": 10,
"free_list": 5
}
},
"records": [
{
"source": "delete_marked",
"confidence": 1.0,
"page_number": 4,
"columns": { "id": 42, "name": "John Doe", ... }
}
]
}
Background
When a row is deleted in InnoDB, it is not immediately removed from the tablespace. Instead, the record is delete-marked and later purged by a background thread. Until purge completes, the record data remains on the page and can be recovered with high confidence.
Even after purge, record data may persist in page free space or in undo log entries. The inno undelete subcommand combines all three recovery approaches to maximize the number of recoverable records.
Use --confidence to filter results by reliability — setting --confidence 0.5 excludes low-confidence free-list and undo log recoveries, returning only records that are likely intact.
inno simulate
Simulate InnoDB crash recovery at different innodb_force_recovery levels.
Usage
# Simulate level 1 recovery
inno simulate -f ibdata1 -d /var/lib/mysql --level 1
# Simulate all levels (1-6) and show results
inno simulate -f ibdata1 -d /var/lib/mysql -v
# JSON output
inno simulate -f ibdata1 -d /var/lib/mysql --level 3 --json
Options
| Option | Description |
|---|---|
-f, --file | Path to InnoDB system tablespace (ibdata1) |
-d, --datadir | MySQL data directory path |
--level | Force recovery level to simulate (1-6, default: simulate all) |
-v, --verbose | Show per-page details |
--json | Output in JSON format |
--page-size | Override page size |
--keyring | Path to MySQL keyring file |
Recovery Levels
| Level | Name | Effect |
|---|---|---|
| 1 | SRV_FORCE_IGNORE_CORRUPT | Skip corrupt pages during recovery |
| 2 | SRV_FORCE_NO_BACKGROUND | Prevent background operations (purge, insert buffer merge) |
| 3 | SRV_FORCE_NO_TRX_UNDO | Skip transaction rollbacks after recovery |
| 4 | SRV_FORCE_NO_IBUF_MERGE | Skip insert buffer merge |
| 5 | SRV_FORCE_NO_UNDO_LOG_SCAN | Skip undo log scanning |
| 6 | SRV_FORCE_NO_LOG_REDO | Skip redo log apply entirely |
Output
For each simulated level, the report includes:
- Whether the tablespace would survive recovery at that level
- Number of pages that would be affected
- Specific risks or data loss implications
- Recommended recovery actions
See the Crash Recovery guide for decision trees and real-world scenarios.
inno timeline
Build a unified modification timeline by correlating entries from redo logs, undo tablespaces, and binary logs. Orders all events by LSN to show the sequence of changes across multiple log sources.
Usage
# Timeline from redo log only
inno timeline --redo-log /var/lib/mysql/ib_logfile0
# Combine redo + undo + binlog sources
inno timeline --redo-log ib_logfile0 --undo-file undo_001 --binlog binlog.000001
# Filter to a specific tablespace/page
inno timeline --redo-log ib_logfile0 --undo-file undo_001 -s 5 -p 3
# Filter by table name (binlog entries)
inno timeline --binlog binlog.000001 -d /var/lib/mysql --table users
# Limit output and use JSON
inno timeline --redo-log ib_logfile0 --binlog binlog.000001 --limit 50 --json
Options
| Option | Description |
|---|---|
--redo-log | Path to InnoDB redo log file |
--undo-file | Path to undo tablespace file |
--binlog | Path to MySQL binary log file |
-d, --datadir | MySQL data directory (resolves table names to space IDs for binlog entries) |
-s, --space-id | Filter entries by tablespace ID |
-p, --page | Filter entries by page number |
--table | Filter binlog entries by table name (case-insensitive substring match) |
--limit | Maximum number of entries to display |
-v, --verbose | Show additional detail per entry |
--json | Output in JSON format |
--page-size | Override page size |
--keyring | Path to MySQL keyring file |
At least one of --redo-log, --undo-file, or --binlog is required.
Output
The timeline displays entries sorted by LSN with columns:
- SEQ -- Sequence number
- LSN -- Log sequence number (used for ordering)
- SOURCE -- Origin:
redo,undo, orbinlog - SPACE:PAGE -- Tablespace ID and page number (when available)
- ACTION -- Description of the modification
A Page Summary section groups entries by space:page, showing how many redo, undo, and binlog entries touch each page along with first/last LSN.
Correlation
When --datadir is provided, the timeline resolves binlog table names to InnoDB space IDs by scanning .ibd files in the data directory. This enables cross-referencing binlog row events with redo/undo page-level changes affecting the same tablespace.
See Also
inno log-- Redo log analysisinno undo-- Undo tablespace analysisinno binlog-- Binary log analysis
inno backup
Analyze incremental backups and validate backup chains. Two subcommands: diff compares page LSNs between backup snapshots, chain validates XtraBackup checkpoint continuity.
backup diff
Compare page LSNs between a base (backup) and current tablespace to identify changed pages.
# Compare backup vs current
inno backup diff --base backup/users.ibd --current /var/lib/mysql/mydb/users.ibd
# Verbose: show per-page details
inno backup diff --base backup/users.ibd --current current/users.ibd -v
# JSON output
inno backup diff --base backup/users.ibd --current current/users.ibd --json
Options
| Option | Description |
|---|---|
--base | Path to base (backup) tablespace file |
--current | Path to current tablespace file |
-v, --verbose | Show per-page change details |
--json | Output in JSON format |
--page-size | Override page size |
--keyring | Path to MySQL keyring file |
Page Change States
| Status | Description |
|---|---|
| Unchanged | Same LSN in both files |
| Modified | Current LSN > base LSN |
| Added | Page only exists in current file |
| Removed | Page only exists in base file |
| Regressed | Current LSN < base LSN (unusual) |
backup chain
Validate XtraBackup backup chain continuity by reading xtrabackup_checkpoints files.
# Validate a backup directory
inno backup chain -d /backups/mysql
# Verbose output
inno backup chain -d /backups/mysql -v
# JSON output
inno backup chain -d /backups/mysql --json
Options
| Option | Description |
|---|---|
-d, --dir | Directory containing backup subdirectories |
-v, --verbose | Show detailed checkpoint info |
--json | Output in JSON format |
Chain Validation
The chain validator reads xtrabackup_checkpoints files from each backup subdirectory and checks:
- A full backup exists in the chain
- LSN ranges are contiguous (no gaps between
to_lsnof one backup andfrom_lsnof the next) - Overlapping LSN ranges are flagged as warnings but do not break the chain
See the Backup Analysis guide for workflows and XtraBackup integration.
inno completions
Generate shell completion scripts for inno subcommands, options, and arguments.
Usage
# Generate completions for your shell
inno completions bash
inno completions zsh
inno completions fish
inno completions powershell
Supported Shells
| Shell | Description |
|---|---|
bash | Bash completions (source in .bashrc or /etc/bash_completion.d/) |
zsh | Zsh completions (place in $fpath directory) |
fish | Fish completions (place in ~/.config/fish/completions/) |
powershell | PowerShell completions |
Installation
Bash
inno completions bash > /etc/bash_completion.d/inno
# or for current session:
source <(inno completions bash)
Zsh
inno completions zsh > "${fpath[1]}/_inno"
# Rebuild completion cache:
rm -f ~/.zcompdump && compinit
Fish
inno completions fish > ~/.config/fish/completions/inno.fish
PowerShell
inno completions powershell >> $PROFILE
Library API Overview
The idb crate provides Rust types and functions for parsing InnoDB tablespace files, redo logs, and related binary structures programmatically. It is published as the innodb-utils package on crates.io.
Adding as a Dependency
[dependencies]
idb = { package = "innodb-utils", version = "2" }
For library-only usage (no CLI modules), disable default features:
[dependencies]
idb = { package = "innodb-utils", version = "2", default-features = false }
Feature Flags
| Feature | Default | Description |
|---|---|---|
cli | Yes | CLI-specific modules (clap, colored, indicatif, etc.). Disable with default-features = false for library-only usage. |
mysql | No | Live MySQL queries via mysql_async + tokio. Implies cli. |
Key Entry Points
| Type / Function | Module | Purpose |
|---|---|---|
Tablespace | idb::innodb::tablespace | Open .ibd files, read pages, iterate over all pages |
FilHeader | idb::innodb::page | Parse the 38-byte FIL header from any InnoDB page |
FilTrailer | idb::innodb::page | Parse the 8-byte FIL trailer from any InnoDB page |
FspHeader | idb::innodb::page | Parse the FSP header from page 0 (space ID, size, flags) |
PageType | idb::innodb::page_types | Map page type codes to names, descriptions, and usage notes |
validate_checksum | idb::innodb::checksum | CRC-32C, legacy InnoDB, and MariaDB full_crc32 validation |
validate_lsn | idb::innodb::checksum | LSN consistency check between header and trailer |
find_sdi_pages | idb::innodb::sdi | Locate SDI pages in a tablespace |
extract_sdi_from_pages | idb::innodb::sdi | Extract and decompress SDI metadata records |
LogFile | idb::innodb::log | Open and parse redo log files |
VendorInfo | idb::innodb::vendor | Vendor detection (MySQL, Percona, MariaDB) |
Module Overview
The library is organized under idb::innodb:
| Module | Purpose |
|---|---|
tablespace | File I/O abstraction, page size auto-detection, page iteration |
page | FIL header (38 bytes), FIL trailer (8 bytes), FSP header parsing |
page_types | Page type enum mapping u16 codes to names and descriptions |
checksum | CRC-32C and legacy InnoDB checksum validation |
index | INDEX page internals -- B+Tree header, FSEG, system records |
record | Row-level record parsing -- compact format, variable-length fields |
sdi | SDI metadata extraction from MySQL 8.0+ tablespaces |
log | Redo log file header, checkpoints, and data block parsing |
undo | UNDO log page header and segment header parsing |
lob | Large object page headers (old-style BLOB and MySQL 8.0+ LOB) |
compression | Compression algorithm detection and decompression (zlib, LZ4) |
encryption | Encryption detection from FSP flags, encryption info parsing |
keyring | MySQL keyring_file plugin format reader |
decryption | AES-256-CBC page decryption using tablespace keys |
vendor | Vendor detection (MySQL, Percona, MariaDB) and format variants |
constants | InnoDB page/file structure constants from MySQL source headers |
Quick Example
use idb::innodb::tablespace::Tablespace;
use idb::innodb::page::FilHeader;
use idb::innodb::checksum::validate_checksum;
let mut ts = Tablespace::open("employees.ibd").unwrap();
println!("Page size: {} bytes", ts.page_size());
println!("Total pages: {}", ts.page_count());
println!("Vendor: {}", ts.vendor_info());
// Read and inspect page 0
let page = ts.read_page(0).unwrap();
let header = FilHeader::parse(&page).unwrap();
println!("Page type: {}", header.page_type);
// Validate checksum
let result = validate_checksum(&page, ts.page_size(), Some(ts.vendor_info()));
println!("Checksum valid: {}", result.valid);
API Reference
Full API documentation is available on docs.rs: https://docs.rs/innodb-utils
Tablespace
The Tablespace struct (idb::innodb::tablespace::Tablespace) is the primary entry point for reading InnoDB .ibd tablespace files. It handles file I/O, automatic page size detection from FSP flags, vendor identification, and encryption awareness.
Opening a Tablespace
From a file path
use idb::innodb::tablespace::Tablespace;
// Auto-detect page size from FSP flags on page 0
let mut ts = Tablespace::open("table.ibd").unwrap();
// Override page size detection (useful for corrupted page 0)
let mut ts = Tablespace::open_with_page_size("table.ibd", 16384).unwrap();
Tablespace::open and Tablespace::open_with_page_size are not available when compiling for wasm32 targets. Use from_bytes instead.
From an in-memory buffer
use idb::innodb::tablespace::Tablespace;
let data: Vec<u8> = std::fs::read("table.ibd").unwrap();
// Auto-detect page size
let mut ts = Tablespace::from_bytes(data).unwrap();
// Or with explicit page size
let data2: Vec<u8> = std::fs::read("table.ibd").unwrap();
let mut ts2 = Tablespace::from_bytes_with_page_size(data2, 16384).unwrap();
The from_bytes constructors are available on all targets, including WASM.
Reading Pages
Single page by number
use idb::innodb::tablespace::Tablespace;
use idb::innodb::page::FilHeader;
let mut ts = Tablespace::open("table.ibd").unwrap();
let page = ts.read_page(0).unwrap();
let header = FilHeader::parse(&page).unwrap();
println!("Page 0 type: {}", header.page_type);
println!("Space ID: {}", header.space_id);
println!("LSN: {}", header.lsn);
read_page returns an Err if the page number is out of range (beyond page_count()). If a decryption context has been set, encrypted pages are transparently decrypted before being returned.
Iterating all pages
use idb::innodb::tablespace::Tablespace;
use idb::innodb::page::FilHeader;
let mut ts = Tablespace::open("table.ibd").unwrap();
ts.for_each_page(|page_num, page_data| {
let header = FilHeader::parse(page_data).unwrap();
println!("Page {}: type={}, LSN={}", page_num, header.page_type, header.lsn);
Ok(())
}).unwrap();
The callback receives (page_number: u64, page_data: &[u8]) for each page. Returning an error from the callback stops iteration. If a decryption context is set, encrypted pages are decrypted before being passed to the callback.
Metadata Accessors
| Method | Return Type | Description |
|---|---|---|
page_size() | u32 | Detected or configured page size in bytes (4096, 8192, 16384, 32768, or 65536) |
page_count() | u64 | Total number of pages in the file (file_size / page_size) |
file_size() | u64 | File size in bytes |
fsp_header() | Option<&FspHeader> | FSP header from page 0, if parseable |
vendor_info() | &VendorInfo | Detected vendor (MySQL, Percona, or MariaDB with format variant) |
encryption_info() | Option<&EncryptionInfo> | Encryption info from page 0, if present |
is_encrypted() | bool | Whether the tablespace has encryption info on page 0 |
Helper Methods
Parsing headers from page buffers
use idb::innodb::tablespace::Tablespace;
let mut ts = Tablespace::open("table.ibd").unwrap();
let page = ts.read_page(3).unwrap();
// Static method -- parse FIL header from any page buffer
let header = Tablespace::parse_fil_header(&page).unwrap();
println!("Page number: {}", header.page_number);
// Instance method -- parse FIL trailer (needs page_size from tablespace)
let trailer = ts.parse_fil_trailer(&page).unwrap();
println!("Trailer LSN low32: {}", trailer.lsn_low32);
Encryption Support
use idb::innodb::tablespace::Tablespace;
use idb::innodb::decryption::DecryptionContext;
let mut ts = Tablespace::open("encrypted_table.ibd").unwrap();
if ts.is_encrypted() {
println!("Encryption info: {:?}", ts.encryption_info());
// Set up decryption for transparent page decryption
// (requires the tablespace key from the MySQL keyring)
// let ctx = DecryptionContext::new(key, iv);
// ts.set_decryption_context(ctx);
}
When a DecryptionContext is set via set_decryption_context, both read_page and for_each_page automatically decrypt pages with encrypted page types (15, 16, 17) before returning them.
Page Size Detection
On initialization, Tablespace reads page 0 and parses the FSP header to determine the page size from the FSP flags field. The detection is vendor-aware:
- MySQL / Percona: Page size encoded in FSP flags bits 6-9 as
ssize, wherepage_size = 1 << (ssize + 9). A value of 0 means the default 16384 bytes. - MariaDB full_crc32: Page size encoded in FSP flags bits 0-3 (same
ssizeformula). The full_crc32 marker at bit 4 triggers this alternate layout.
Supported page sizes: 4096, 8192, 16384, 32768, 65536.
Error Handling
All fallible methods return Result<T, IdbError>. Common error conditions:
- File too small to be a valid tablespace (less than FIL header + FSP header size)
- Page number out of range
- I/O errors during seek or read operations
Page Parsing
Every InnoDB page has a fixed-layout header and trailer that frame the page-type-specific data. The idb::innodb::page module provides types for parsing these structures.
FIL Header
The FilHeader struct represents the 38-byte header present at the start of every InnoDB page.
Byte Layout
Offset Size Field
------ ---- -----
0 4 Checksum (or space ID in older formats)
4 4 Page number within the tablespace
8 4 Previous page in doubly-linked list (FIL_NULL = 0xFFFFFFFF if unused)
12 4 Next page in doubly-linked list (FIL_NULL = 0xFFFFFFFF if unused)
16 8 LSN of newest modification to this page
24 2 Page type
26 8 Flush LSN (only meaningful for page 0 of system tablespace)
34 4 Space ID
Parsing
use idb::innodb::page::FilHeader;
// page_data must be at least 38 bytes
let page_data: Vec<u8> = vec![0u8; 16384];
let header = FilHeader::parse(&page_data);
match header {
Some(hdr) => {
println!("Page number: {}", hdr.page_number);
println!("Page type: {}", hdr.page_type);
println!("LSN: {}", hdr.lsn);
println!("Space ID: {}", hdr.space_id);
println!("Checksum: 0x{:08X}", hdr.checksum);
}
None => println!("Buffer too small for FIL header"),
}
Fields
| Field | Type | Description |
|---|---|---|
checksum | u32 | Stored checksum (bytes 0-3) |
page_number | u32 | Page number within the tablespace (bytes 4-7) |
prev_page | u32 | Previous page pointer, 0xFFFFFFFF if unused (bytes 8-11) |
next_page | u32 | Next page pointer, 0xFFFFFFFF if unused (bytes 12-15) |
lsn | u64 | LSN of newest modification (bytes 16-23) |
page_type | PageType | Page type enum (bytes 24-25) |
flush_lsn | u64 | Flush LSN, only valid on page 0 of system tablespace (bytes 26-33) |
space_id | u32 | Space ID this page belongs to (bytes 34-37) |
Page Chain Methods
use idb::innodb::page::FilHeader;
let page_data = vec![0u8; 38];
let header = FilHeader::parse(&page_data).unwrap();
// Check if the page has prev/next pointers set
if header.has_prev() {
println!("Previous page: {}", header.prev_page);
}
if header.has_next() {
println!("Next page: {}", header.next_page);
}
Both has_prev() and has_next() return false when the pointer is FIL_NULL (0xFFFFFFFF) or 0.
FIL Trailer
The FilTrailer struct represents the 8-byte trailer at the end of every InnoDB page.
Parsing
use idb::innodb::page::FilTrailer;
// The trailer is the last 8 bytes of the page
let page_data: Vec<u8> = vec![0u8; 16384];
let trailer_bytes = &page_data[16384 - 8..];
let trailer = FilTrailer::parse(trailer_bytes);
match trailer {
Some(trl) => {
println!("Trailer checksum: 0x{:08X}", trl.checksum);
println!("Trailer LSN low32: 0x{:08X}", trl.lsn_low32);
}
None => println!("Buffer too small for FIL trailer"),
}
Fields
| Field | Type | Description |
|---|---|---|
checksum | u32 | Old-style checksum (bytes 0-3 of trailer) |
lsn_low32 | u32 | Low 32 bits of the LSN (bytes 4-7 of trailer). Should match the low 32 bits of FilHeader.lsn. |
FSP Header
The FspHeader struct represents the FSP (File Space) header found on page 0 of every tablespace, starting at byte offset 38 (immediately after the FIL header).
Parsing
use idb::innodb::tablespace::Tablespace;
let mut ts = Tablespace::open("table.ibd").unwrap();
if let Some(fsp) = ts.fsp_header() {
println!("Space ID: {}", fsp.space_id);
println!("Tablespace size: {} pages", fsp.size);
println!("Free limit: {} pages", fsp.free_limit);
println!("Flags: 0x{:08X}", fsp.flags);
println!("Page size from flags: {}", fsp.page_size_from_flags());
}
You can also parse the FSP header directly from a page buffer:
use idb::innodb::page::FspHeader;
// page_data must be a full page 0 buffer
let page_data = vec![0u8; 16384];
let fsp = FspHeader::parse(&page_data);
Fields
| Field | Type | Description |
|---|---|---|
space_id | u32 | Tablespace space ID |
size | u32 | Tablespace size in pages |
free_limit | u32 | Minimum page number not yet initialized |
flags | u32 | Space flags (encodes page size, compression, encryption info) |
frag_n_used | u32 | Number of used pages in the FSP_FREE_FRAG list |
Page Size Extraction
use idb::innodb::page::FspHeader;
use idb::innodb::vendor::VendorInfo;
let page_data = vec![0u8; 16384];
let fsp = FspHeader::parse(&page_data).unwrap();
// Auto-detect vendor from flags, then extract page size
let page_size = fsp.page_size_from_flags();
// Or with explicit vendor info
let vendor = VendorInfo::mysql();
let page_size = fsp.page_size_from_flags_with_vendor(&vendor);
PageType
The PageType enum (idb::innodb::page_types::PageType) maps the 2-byte page type field to named variants covering all InnoDB page types from MySQL 5.7 through 9.x, plus MariaDB-specific types.
Parsing
use idb::innodb::page_types::PageType;
use idb::innodb::vendor::VendorInfo;
// Basic parsing (value 18 defaults to SdiBlob / MySQL interpretation)
let pt = PageType::from_u16(17855);
assert_eq!(pt, PageType::Index);
// Vendor-aware parsing (resolves type 18 ambiguity)
let mariadb = VendorInfo::mariadb(idb::innodb::vendor::MariaDbFormat::FullCrc32);
let pt = PageType::from_u16_with_vendor(18, &mariadb);
assert_eq!(pt, PageType::Instant);
let mysql = VendorInfo::mysql();
let pt = PageType::from_u16_with_vendor(18, &mysql);
assert_eq!(pt, PageType::SdiBlob);
Metadata
Each PageType variant provides metadata through three methods:
use idb::innodb::page_types::PageType;
let pt = PageType::Index;
println!("Name: {}", pt.name()); // "INDEX"
println!("Description: {}", pt.description()); // "B+Tree index"
println!("Usage: {}", pt.usage()); // "Table and index data stored in B+Tree structure."
Raw Value
use idb::innodb::page_types::PageType;
let pt = PageType::Index;
let raw: u16 = pt.as_u16();
assert_eq!(raw, 17855);
Common Page Types
| Variant | Value | Description |
|---|---|---|
Allocated | 0 | Freshly allocated, not yet initialized |
UndoLog | 2 | Undo log page |
Inode | 3 | File segment inode |
FspHdr | 8 | File space header (page 0) |
Xdes | 9 | Extent descriptor |
Blob | 10 | Uncompressed BLOB data |
Sdi | 17853 | SDI metadata (MySQL 8.0+) |
SdiBlob | 17854 | SDI BLOB overflow (MySQL 8.0+) |
Index | 17855 | B+Tree index page |
Rtree | 17856 | R-tree spatial index |
LobIndex | 20 | LOB index (MySQL 8.0+) |
LobData | 21 | LOB data (MySQL 8.0+) |
LobFirst | 22 | LOB first page (MySQL 8.0+) |
Encrypted | 15 | Encrypted page |
PageCompressed | 34354 | MariaDB page-level compression |
Instant | 18 | MariaDB instant ALTER (conflicts with SdiBlob in MySQL) |
Checksums
The idb::innodb::checksum module implements the checksum algorithms used by MySQL and MariaDB InnoDB to validate page integrity. It provides a single entry point, validate_checksum, that tries the applicable algorithms and returns a detailed result.
Validating a Page Checksum
use idb::innodb::tablespace::Tablespace;
use idb::innodb::checksum::{validate_checksum, ChecksumAlgorithm};
let mut ts = Tablespace::open("table.ibd").unwrap();
let page = ts.read_page(0).unwrap();
let result = validate_checksum(&page, ts.page_size(), Some(ts.vendor_info()));
println!("Valid: {}", result.valid);
println!("Algorithm: {:?}", result.algorithm);
println!("Stored checksum: 0x{:08X}", result.stored_checksum);
println!("Calculated checksum: 0x{:08X}", result.calculated_checksum);
Function Signature
pub fn validate_checksum(
page_data: &[u8],
page_size: u32,
vendor_info: Option<&VendorInfo>,
) -> ChecksumResult
Validation Logic
The function applies different algorithms depending on the vendor and stored checksum value:
- All-zeros page: If the entire page is zeroed, it is considered valid with algorithm
None. - No-checksum magic: If the stored checksum is
0xDEADBEEF(BUF_NO_CHECKSUM_MAGIC), the page was written withinnodb_checksum_algorithm=noneand is considered valid. - MariaDB full_crc32: When
vendor_infoindicates MariaDB full_crc32 format, the full_crc32 algorithm is tried. If it does not match, failure is reported immediately (no fallback to other algorithms). - CRC-32C (tried first for MySQL/Percona): XOR of two independent CRC-32C values over the page data.
- Legacy InnoDB (fallback): The
ut_fold_ulint_pair-based algorithm used before MySQL 5.7.7.
If neither CRC-32C nor legacy InnoDB matches, the result reports failure with CRC-32C as the expected algorithm.
ChecksumResult
| Field | Type | Description |
|---|---|---|
algorithm | ChecksumAlgorithm | The algorithm that was detected or attempted |
valid | bool | Whether the stored checksum matches the calculated value |
stored_checksum | u32 | The checksum value stored in the page |
calculated_checksum | u32 | The checksum value calculated from the page data |
ChecksumAlgorithm
| Variant | Description |
|---|---|
Crc32c | CRC-32C (hardware accelerated, MySQL 5.7.7+ default) |
InnoDB | Legacy InnoDB checksum (buf_calc_page_new_checksum equivalent) |
MariaDbFullCrc32 | MariaDB full_crc32 (single CRC-32C over entire page minus last 4 bytes) |
None | No checksum (innodb_checksum_algorithm=none or all-zeros page) |
LSN Validation
In addition to checksum validation, you can verify LSN consistency between the FIL header and FIL trailer:
use idb::innodb::tablespace::Tablespace;
use idb::innodb::checksum::validate_lsn;
let mut ts = Tablespace::open("table.ibd").unwrap();
let page = ts.read_page(0).unwrap();
let lsn_ok = validate_lsn(&page, ts.page_size());
println!("LSN consistent: {}", lsn_ok);
validate_lsn compares the low 32 bits of the 8-byte LSN at bytes 16-23 (FIL header) with the 4-byte value at the end of the FIL trailer (bytes page_size - 4 to page_size). A mismatch indicates the page may be torn or corrupted.
Algorithm Details
CRC-32C (MySQL 5.7.7+)
The MySQL CRC-32C checksum is computed over two disjoint byte ranges and XORed together (not chained):
- Range 1: bytes
[4..26)-- page number through page type (skipping the stored checksum at bytes 0-3) - Range 2: bytes
[38..page_size-8)-- page data area (skipping flush LSN, space ID, and the 8-byte trailer)
stored_checksum = CRC32C(bytes[4..26]) XOR CRC32C(bytes[38..page_size-8])
The checksum is stored in bytes 0-3 of the page (the FIL header checksum field).
Legacy InnoDB (MySQL < 5.7.7)
Uses ut_fold_ulint_pair with wrapping u32 arithmetic, processing each byte individually over the same two ranges. The two fold values are summed (with wrapping) rather than XORed.
stored_checksum = fold(bytes[4..26]) + fold(bytes[38..page_size-8])
MariaDB full_crc32 (MariaDB 10.5+)
A single CRC-32C over bytes [0..page_size-4). The checksum is stored in the last 4 bytes of the page (not in the FIL header at bytes 0-3 like MySQL).
stored_checksum (at page_size-4) = CRC32C(bytes[0..page_size-4])
Combining Checksum and LSN Validation
For thorough page integrity checking, validate both:
use idb::innodb::tablespace::Tablespace;
use idb::innodb::checksum::{validate_checksum, validate_lsn};
let mut ts = Tablespace::open("table.ibd").unwrap();
ts.for_each_page(|page_num, page_data| {
let cksum = validate_checksum(page_data, ts.page_size(), Some(ts.vendor_info()));
let lsn_ok = validate_lsn(page_data, ts.page_size());
if !cksum.valid || !lsn_ok {
println!(
"Page {}: checksum={} ({:?}), LSN consistent={}",
page_num, cksum.valid, cksum.algorithm, lsn_ok
);
}
Ok(())
}).unwrap();
SDI Extraction
The idb::innodb::sdi module provides functions for extracting Serialized Dictionary Information (SDI) from MySQL 8.0+ tablespaces. SDI replaced .frm files as the mechanism for embedding table, column, and index definitions directly inside each .ibd file. SDI data is stored on dedicated SDI pages (page type 17853) as zlib-compressed JSON.
Finding SDI Pages
use idb::innodb::tablespace::Tablespace;
use idb::innodb::sdi;
let mut ts = Tablespace::open("users.ibd").unwrap();
let sdi_pages = sdi::find_sdi_pages(&mut ts).unwrap();
println!("Found {} SDI pages: {:?}", sdi_pages.len(), sdi_pages);
find_sdi_pages uses a two-phase approach:
- Fast path: Reads the SDI root page number from page 0 (located after the FIL header, FSP header, and XDES array). If the SDI version marker (value 1) is found and the root page is valid, it reads the root page and walks its prev/next linked list to collect all SDI leaf pages.
- Fallback: If the fast path fails (e.g., corrupted page 0, pre-8.0 tablespace), it scans every page in the tablespace looking for SDI page types.
Extracting SDI Records
use idb::innodb::tablespace::Tablespace;
use idb::innodb::sdi;
let mut ts = Tablespace::open("users.ibd").unwrap();
let sdi_pages = sdi::find_sdi_pages(&mut ts).unwrap();
let records = sdi::extract_sdi_from_pages(&mut ts, &sdi_pages).unwrap();
for rec in &records {
println!("Type: {} ({})", rec.sdi_type, sdi::sdi_type_name(rec.sdi_type));
println!("Object ID: {}", rec.sdi_id);
println!("Compressed: {} bytes -> {} bytes uncompressed",
rec.compressed_len, rec.uncompressed_len);
// rec.data contains the decompressed JSON string
println!("Data (first 200 chars): {}", &rec.data[..rec.data.len().min(200)]);
println!("---");
}
Function Signature
pub fn extract_sdi_from_pages(
ts: &mut Tablespace,
sdi_pages: &[u64],
) -> Result<Vec<SdiRecord>, IdbError>
The function:
- Reads each SDI page and verifies it has page type SDI (17853)
- Parses the INDEX page header to confirm it is a leaf page with records
- Walks compact-format records on the page
- For each ordinary record, extracts the SDI header fields (type, ID, lengths)
- If compressed data fits within the page, decompresses it directly
- If compressed data spans multiple pages, follows the next-page chain to collect all compressed bytes before decompression
SdiRecord
| Field | Type | Description |
|---|---|---|
sdi_type | u32 | SDI type: 1 = Table, 2 = Tablespace |
sdi_id | u64 | SDI object ID |
uncompressed_len | u32 | Expected uncompressed data length in bytes |
compressed_len | u32 | Compressed data length in bytes |
data | String | Decompressed JSON string containing the dictionary definition |
SDI Type Names
use idb::innodb::sdi;
assert_eq!(sdi::sdi_type_name(1), "Table");
assert_eq!(sdi::sdi_type_name(2), "Tablespace");
assert_eq!(sdi::sdi_type_name(99), "Unknown");
Checking Individual Pages
use idb::innodb::sdi;
let page_data = vec![0u8; 16384];
// Check if a page buffer is an SDI page
if sdi::is_sdi_page(&page_data) {
// Extract records from a single page (no multi-page reassembly)
if let Some(records) = sdi::extract_sdi_from_page(&page_data) {
println!("Found {} SDI records on this page", records.len());
}
}
extract_sdi_from_page works on a single page buffer without requiring a Tablespace handle. It does not follow page chains, so records spanning multiple pages may have truncated or incomplete data. Use extract_sdi_from_pages with a Tablespace for full multi-page reassembly.
Reading the SDI Root Page
For advanced use, you can read the SDI root page number directly from page 0:
use idb::innodb::tablespace::Tablespace;
use idb::innodb::sdi;
let mut ts = Tablespace::open("users.ibd").unwrap();
let page0 = ts.read_page(0).unwrap();
if let Some(root_page) = sdi::read_sdi_root_page(&page0, ts.page_size(), ts.page_count()) {
println!("SDI root page: {}", root_page);
}
This returns None if the SDI version marker is not found, the version is not 1, or the root page number is out of range.
Limitations
- SDI is a MySQL 8.0+ feature. MariaDB tablespaces do not contain SDI pages.
- Pre-MySQL 8.0 tablespaces will return an empty list from
find_sdi_pages. - The SDI JSON schema is defined by MySQL's data dictionary and varies between MySQL versions.
Redo Logs
The idb::innodb::log module provides types and functions for reading InnoDB redo log files. These are ib_logfile0/ib_logfile1 for MySQL versions before 8.0.30, or #ib_redo* numbered files for MySQL 8.0.30 and later.
File Layout
An InnoDB redo log file consists of 512-byte blocks:
- Block 0: Log file header (group ID, start LSN, file number, creator string)
- Block 1: Checkpoint slot 0
- Block 2: Reserved
- Block 3: Checkpoint slot 1
- Blocks 4+: Data blocks containing redo log records
Each block has a 14-byte header and a 4-byte trailer (CRC-32C checksum at bytes 508-511).
Opening a Redo Log
From a file path
use idb::innodb::log::LogFile;
let mut log = LogFile::open("/var/lib/mysql/ib_logfile0").unwrap();
println!("File size: {} bytes", log.file_size());
println!("Total blocks: {}", log.block_count());
println!("Data blocks: {}", log.data_block_count());
LogFile::open is not available on wasm32 targets.
From an in-memory buffer
use idb::innodb::log::LogFile;
let data: Vec<u8> = std::fs::read("/var/lib/mysql/ib_logfile0").unwrap();
let mut log = LogFile::from_bytes(data).unwrap();
The file must be at least 2048 bytes (4 blocks of 512 bytes for the header and checkpoint areas).
Reading the File Header
use idb::innodb::log::LogFile;
let mut log = LogFile::open("/var/lib/mysql/ib_logfile0").unwrap();
let header = log.read_header().unwrap();
println!("Group ID: {}", header.group_id);
println!("Start LSN: {}", header.start_lsn);
println!("File number: {}", header.file_no);
println!("Created by: {}", header.created_by);
LogFileHeader Fields
| Field | Type | Description |
|---|---|---|
group_id | u32 | Log group ID |
start_lsn | u64 | Start LSN of this log file |
file_no | u32 | File number within the log group |
created_by | String | MySQL version string (e.g., "MySQL 8.0.32", "MariaDB 10.11.4") |
The created_by string can be used for vendor detection:
use idb::innodb::log::LogFile;
use idb::innodb::vendor::detect_vendor_from_created_by;
let mut log = LogFile::open("/var/lib/mysql/ib_logfile0").unwrap();
let header = log.read_header().unwrap();
let vendor = detect_vendor_from_created_by(&header.created_by);
println!("Vendor: {}", vendor);
Reading Checkpoints
InnoDB maintains two checkpoint slots for crash recovery. Slot 0 is in block 1, slot 1 is in block 3.
use idb::innodb::log::LogFile;
let mut log = LogFile::open("/var/lib/mysql/ib_logfile0").unwrap();
let cp0 = log.read_checkpoint(0).unwrap();
println!("Checkpoint 0: number={}, LSN={}, offset={}, buf_size={}",
cp0.number, cp0.lsn, cp0.offset, cp0.buf_size);
let cp1 = log.read_checkpoint(1).unwrap();
println!("Checkpoint 1: number={}, LSN={}, offset={}, buf_size={}",
cp1.number, cp1.lsn, cp1.offset, cp1.buf_size);
LogCheckpoint Fields
| Field | Type | Description |
|---|---|---|
number | u64 | Checkpoint sequence number |
lsn | u64 | LSN at the time of this checkpoint |
offset | u32 | Byte offset of the checkpoint within the log file |
buf_size | u32 | Log buffer size at checkpoint time |
archived_lsn | u64 | LSN up to which log has been archived |
Reading Data Blocks
use idb::innodb::log::{LogFile, LogBlockHeader, LogBlockTrailer, LOG_FILE_HDR_BLOCKS};
let mut log = LogFile::open("/var/lib/mysql/ib_logfile0").unwrap();
// Read data blocks (starting at block 4, after header/checkpoint blocks)
for block_no in LOG_FILE_HDR_BLOCKS..log.block_count() {
let block = log.read_block(block_no).unwrap();
let header = LogBlockHeader::parse(&block).unwrap();
if header.has_data() {
println!("Block {}: data_len={}, flush={}, checkpoint_no={}",
header.block_no, header.data_len, header.flush_flag, header.checkpoint_no);
}
}
LogBlockHeader Fields
| Field | Type | Description |
|---|---|---|
block_no | u32 | Block number (with flush bit masked out) |
flush_flag | bool | Whether this block was the first in a flush batch (bit 31 of raw block number) |
data_len | u16 | Number of bytes of log data in this block (including the 14-byte header) |
first_rec_group | u16 | Offset of the first log record group starting in this block |
checkpoint_no | u32 | Checkpoint number when this block was written |
The has_data() method returns true when data_len exceeds the header size (14 bytes), indicating the block contains log record data.
Block Checksum Validation
Each 512-byte block has a CRC-32C checksum stored in the last 4 bytes (offset 508-511), covering bytes 0-507.
use idb::innodb::log::{LogFile, validate_log_block_checksum, LOG_FILE_HDR_BLOCKS};
let mut log = LogFile::open("/var/lib/mysql/ib_logfile0").unwrap();
for block_no in LOG_FILE_HDR_BLOCKS..log.block_count() {
let block = log.read_block(block_no).unwrap();
let valid = validate_log_block_checksum(&block);
if !valid {
println!("Block {} has invalid checksum", block_no);
}
}
MLOG Record Types
The MlogRecordType enum classifies redo log record types from MySQL's mtr0types.h. This is useful for analyzing what operations are recorded in the redo log.
use idb::innodb::log::MlogRecordType;
let rec_type = MlogRecordType::from_u8(9);
println!("{}", rec_type); // "MLOG_REC_INSERT"
println!("{}", rec_type.name()); // "MLOG_REC_INSERT"
let unknown = MlogRecordType::from_u8(200);
println!("{}", unknown); // "UNKNOWN(200)"
Common MLOG Record Types
| Value | Variant | Name |
|---|---|---|
| 1 | Mlog1Byte | MLOG_1BYTE |
| 2 | Mlog2Bytes | MLOG_2BYTES |
| 4 | Mlog4Bytes | MLOG_4BYTES |
| 8 | Mlog8Bytes | MLOG_8BYTES |
| 9 | MlogRecInsert | MLOG_REC_INSERT |
| 13 | MlogRecUpdate | MLOG_REC_UPDATE_IN_PLACE |
| 14 | MlogRecDelete | MLOG_REC_DELETE |
| 19 | MlogPageCreate | MLOG_PAGE_CREATE |
| 22 | MlogUndoInit | MLOG_UNDO_INIT |
| 30 | MlogInitFilePage | MLOG_INIT_FILE_PAGE |
| 31 | MlogWriteString | MLOG_WRITE_STRING |
| 32 | MlogMultiRecEnd | MLOG_MULTI_REC_END |
| 36 | MlogCompRecInsert | MLOG_COMP_REC_INSERT |
| 44 | MlogCompPageReorganize | MLOG_COMP_PAGE_REORGANIZE |
| 52 | MlogFileCreate | MLOG_FILE_CREATE |
Unknown type codes are represented as MlogRecordType::Unknown(value).
Block Count Methods
| Method | Description |
|---|---|
block_count() | Total number of 512-byte blocks in the file |
data_block_count() | Number of data blocks (total minus 4 header/checkpoint blocks) |
file_size() | File size in bytes |
Constants
The module exports several constants matching MySQL's log0log.h:
| Constant | Value | Description |
|---|---|---|
LOG_BLOCK_SIZE | 512 | Size of a redo log block in bytes |
LOG_BLOCK_HDR_SIZE | 14 | Size of the block header |
LOG_BLOCK_TRL_SIZE | 4 | Size of the block trailer |
LOG_BLOCK_FLUSH_BIT_MASK | 0x80000000 | Bitmask for the flush flag in block number |
LOG_BLOCK_CHECKSUM_OFFSET | 508 | Byte offset of CRC-32C checksum within a block |
LOG_FILE_HDR_BLOCKS | 4 | Number of reserved header/checkpoint blocks |
Web Analyzer
IDB Utils includes a browser-based InnoDB file analyzer powered by WebAssembly. The web UI provides the same core analysis capabilities as the CLI, running entirely client-side with no server uploads.
Live instance: innodb.fyi
What It Does
The web analyzer lets you drag and drop .ibd tablespace files or redo log files directly into your browser for instant analysis:
- Parse tablespace files and view page headers, type summaries, and FIL header details
- Validate checksums across all pages (CRC-32C, legacy InnoDB, MariaDB full_crc32)
- Inspect page structure with deep analysis of INDEX, UNDO, BLOB/LOB, and SDI pages
- Hex dump individual pages with offset/hex/ASCII formatting
- Extract SDI metadata from MySQL 8.0+ tablespaces
- Assess recovery potential of damaged tablespaces
- Compare two tablespace files page-by-page
- Analyze redo logs with header, checkpoint, and block detail
Privacy
All file processing happens locally in your browser via WebAssembly. Your .ibd files are never uploaded to any server. The WASM module runs the same Rust parsing code as the CLI inno binary, compiled to WebAssembly.
Technology
- WASM: Rust library compiled with
wasm-packto WebAssembly - Frontend: Vite + Tailwind CSS single-page application
- Source:
web/directory in the repository - Bindings:
src/wasm.rsprovides thin wrappers over the library API, returning JSON strings viawasm-bindgen
Limitations
- No filesystem access — files must be loaded via drag-and-drop or file picker
- No encryption support — the
--keyringoption is CLI-only - Large files may be slow to process depending on browser memory limits
- No
watchmode — real-time monitoring requires the CLI
Getting Started
Opening the Analyzer
Visit innodb.fyi or run it locally:
cd web
npm ci
npm run dev
Loading Files
Drag and drop an .ibd tablespace file or redo log file onto the drop zone, or click to open a file picker.
Supported file types:
.ibd— InnoDB tablespace filesib_logfile*/#ib_redo*— InnoDB redo log filesibdata1— InnoDB system tablespace
Analysis Tabs
Once a file is loaded, the analyzer provides several tabs:
Parse
Displays the FIL header for every page in the tablespace: page number, checksum, page type, LSN, prev/next page, and space ID. A summary table shows the count of each page type.
Pages
Deep structural analysis of page internals. For INDEX pages, shows the B+Tree level, record count, and index ID. For UNDO pages, shows the segment state and log type. For BLOB pages, shows the data length and chain pointers.
Checksum
Validates every page's checksum and LSN consistency. Shows per-page status (valid/invalid), the algorithm used (CRC-32C, legacy InnoDB, or MariaDB full_crc32), and stored vs calculated values.
Hex Dump
Raw hex dump of any page in the tablespace. Select a page number to view offset/hex/ASCII output with 16 bytes per line.
SDI
Extracts Serialized Dictionary Information from MySQL 8.0+ tablespaces. Shows the table and tablespace definitions as formatted JSON, including column types, indexes, and partitions.
Recovery
Assesses page-level data recoverability. Classifies each page as intact, corrupt, or empty, and counts recoverable user records on INDEX pages.
Diff
Compare two tablespace files. Load a second file to see which pages are identical, modified, or only present in one file.
Redo Log
For redo log files, displays the file header (creator string, start LSN), checkpoint records, and data block details with checksum validation.
Tips
- The page type summary in the Parse tab gives a quick overview of tablespace composition
- Use the Checksum tab to verify data integrity after backups or file transfers
- SDI extraction works on any MySQL 8.0+
.ibdfile without needing a running MySQL instance - For large files, analysis may take a few seconds depending on your browser
InnoDB File Format Primer
Understanding the on-disk format helps when interpreting inno output.
Tablespace Files (.ibd)
Each .ibd file is a tablespace divided into fixed-size pages (default 16,384 bytes). Every page has:
+------------------+ byte 0
| FIL Header | 38 bytes -- checksum, page number, prev/next, LSN, type, space_id
+------------------+ byte 38
| Page Body | varies by page type
| |
+------------------+ byte (page_size - 8)
| FIL Trailer | 8 bytes -- old checksum, LSN low 32 bits
+------------------+
Page 0 is always FSP_HDR with tablespace metadata including the space ID, tablespace size, FSP flags, and extent descriptors.
Page Types
| Type | Value | Description |
|---|---|---|
ALLOCATED | 0 | Freshly allocated, type field not yet initialized |
UNDO_LOG | 2 | Stores previous values of modified records |
INODE | 3 | File segment inode bookkeeping |
TRX_SYS | 7 | Transaction system header (system tablespace only) |
FSP_HDR | 8 | File space header, always page 0 |
XDES | 9 | Extent descriptor for 16,384-page blocks |
BLOB | 10 | Externally stored column data |
SDI | 17853 | Serialized Dictionary Information (MySQL 8.0+) |
INDEX | 17855 | B+Tree index node -- table and index data |
See the Page Types reference for the full list.
Checksums
Two primary algorithms protect page integrity:
- CRC-32C (default since MySQL 5.7.7) -- hardware-accelerated, XOR of two independent CRC-32C values computed over bytes
[4..26)and[38..page_size-8). - Legacy InnoDB (MySQL < 5.7.7) -- custom
ut_fold_ulint_pairhash with wrapping u32 arithmetic, computed over the same two byte ranges.
MariaDB 10.5+ introduces a third algorithm, full_crc32, which computes a single CRC-32C over [0..page_size-4) and stores the result in the last 4 bytes of the page. See Checksum Algorithms for details.
LSN (Log Sequence Number)
The LSN is a monotonically increasing counter that tracks position in the redo log. Each page records the LSN in two places:
- FIL header (8 bytes at offset 16) -- full 64-bit LSN of the last modification
- FIL trailer (4 bytes at offset
page_size - 4) -- low 32 bits of the same LSN
A mismatch between the header and trailer LSN values indicates a torn page write -- the page was only partially flushed to disk. The inno checksum command reports these as LSN mismatches.
Redo Log Structure
InnoDB redo logs are organized as a sequence of 512-byte blocks:
| Block | Purpose |
|---|---|
| 0 | File header (group_id, start_lsn, file_no, creator string) |
| 1 | Checkpoint 1 |
| 2 | Reserved |
| 3 | Checkpoint 2 |
| 4+ | Data blocks (14-byte header, up to 494 bytes of log records, 4-byte checksum) |
The creator string in block 0 identifies the server version and vendor (e.g., "MySQL 8.0.32", "Percona XtraDB 8.0.35"). See Redo Log Format for the complete specification.
Page Sizes
InnoDB supports five page sizes: 4K, 8K, 16K (default), 32K, and 64K. The page size is encoded in the FSP flags on page 0 and auto-detected by inno. All byte offsets and ranges in this documentation assume the default 16K page size unless stated otherwise.
Further Reading
- Checksum Algorithms -- detailed algorithm specifications
- Page Types -- complete page type table
- Redo Log Format -- block and record layout
- Vendor Support -- MySQL, Percona, and MariaDB differences
Encrypted Tablespaces
inno can read encrypted InnoDB tablespaces when provided with the MySQL keyring file.
Basic Usage
Pass the --keyring option to any subcommand that reads tablespace pages:
inno parse -f encrypted.ibd --keyring /var/lib/mysql-keyring/keyring
inno pages -f encrypted.ibd --keyring /var/lib/mysql-keyring/keyring
inno sdi -f encrypted.ibd --keyring /var/lib/mysql-keyring/keyring --pretty
The --keyring option works with: parse, pages, dump, checksum, recover, sdi, diff, and watch.
Hex Dump with Decryption
For the dump subcommand, add --decrypt along with --keyring to see decrypted page content:
inno dump -f encrypted.ibd -p 3 --keyring /path/keyring --decrypt
Without --decrypt, the hex dump shows raw (encrypted) bytes.
Supported Keyring Format
Legacy keyring_file plugin (binary format, MySQL 5.7.11+) is supported. This is the file created by the keyring_file plugin, typically located at /var/lib/mysql-keyring/keyring.
The newer component_keyring_file (JSON format, MySQL 8.0.34+) is not yet supported.
How Encryption Works
MySQL uses a two-tier key architecture:
- Master key -- stored in the keyring file, identified by a key ID embedded in the tablespace
- Per-tablespace key and IV -- stored on page 0 of the tablespace, encrypted with the master key
- Page encryption -- each page body is encrypted with AES-256-CBC using the per-tablespace key and IV
When inno opens an encrypted tablespace with --keyring:
- It reads the encryption info from page 0
- Looks up the master key by ID in the keyring file
- Decrypts the per-tablespace key and IV
- Decrypts individual pages on demand
Troubleshooting
"no encryption info on page 0"
The tablespace either is not encrypted or uses a different encryption method. Verify that the table was created with ENCRYPTION='Y' or that the tablespace has encryption enabled.
Wrong keyring file
If the master key ID stored in the tablespace does not match any key in the provided keyring file, decryption will fail. Make sure you are using the keyring file from the same MySQL instance that encrypted the tablespace.
MariaDB encrypted tablespaces
MariaDB uses per-page encryption with a different on-disk format (page type 37401). This is distinct from MySQL's tablespace-level encryption. MariaDB keyring decryption is not supported by inno.
Rotated master keys
If the master key was rotated with ALTER INSTANCE ROTATE INNODB MASTER KEY, the keyring file must contain the current master key. Older keys may have been removed during rotation.
Identifying Encrypted Pages
Use inno parse to identify encrypted pages by their page type:
inno parse -f encrypted.ibd --json | jq '[.[] | select(.page_type == "ENCRYPTED")]'
On encrypted tablespaces without the keyring, pages will show as type ENCRYPTED (15), COMPRESSED_ENCRYPTED (16), or ENCRYPTED_RTREE (17). With the correct keyring, inno decrypts them transparently and reports their actual underlying page type.
JSON Output
Every inno subcommand supports --json for structured, machine-readable output. This makes it straightforward to integrate with scripts, CI pipelines, and tools like jq.
Examples with jq
Filter Pages by Type
inno parse -f users.ibd --json | jq '[.[] | select(.page_type == "INDEX")]'
Checksum Validation in CI
if ! inno checksum -f users.ibd --json | jq -e '.invalid_pages == 0' > /dev/null; then
echo "Checksum validation failed"
exit 1
fi
Extract Column Names from SDI
inno sdi -f users.ibd | jq -r '.dd_object.columns[].name'
Count Pages by Type
inno parse -f users.ibd --json | jq 'group_by(.page_type) | map({type: .[0].page_type, count: length})'
Monitor Changes as NDJSON Stream
inno watch -f users.ibd --json | jq -c 'select(.event == "poll")'
Diff Summary
inno diff old.ibd new.ibd --json | jq '{modified: .modified, identical: .identical}'
Recovery Assessment
inno recover -f damaged.ibd --json | jq '.summary'
Find Tablespace by Space ID
inno find -d /var/lib/mysql -p 0 -s 42 --json | jq -r '.[].file'
Redo Log Creator String
inno log -f ib_logfile0 --json | jq -r '.header.creator'
Output Structure
Each subcommand produces a consistent JSON structure. Below are representative examples.
Checksum Output
{
"file": "users.ibd",
"page_size": 16384,
"total_pages": 100,
"empty_pages": 10,
"valid_pages": 88,
"invalid_pages": 2,
"lsn_mismatches": 0,
"pages": [
{
"page_number": 0,
"page_type": "FSP_HDR",
"stored_checksum": 3456789012,
"computed_crc32c": 3456789012,
"computed_legacy": 1234567890,
"valid": true
}
]
}
Parse Output
[
{
"page_number": 0,
"page_type": "FSP_HDR",
"space_id": 42,
"lsn": 12345678,
"checksum": 3456789012
},
{
"page_number": 1,
"page_type": "IBUF_BITMAP",
"space_id": 42,
"lsn": 12345678,
"checksum": 2345678901
}
]
Diff Output
{
"file1": "old.ibd",
"file2": "new.ibd",
"page_size": 16384,
"total_pages": 100,
"identical": 95,
"modified": 5,
"differences": [
{
"page_number": 3,
"page_type": "INDEX",
"lsn1": 12345678,
"lsn2": 12345999
}
]
}
Tips
- All JSON output goes to stdout. Progress bars, warnings, and verbose messages go to stderr, so piping to
jqworks without interference. - The
watchsubcommand with--jsonemits newline-delimited JSON (NDJSON), one object per poll cycle. Usejq -cfor compact processing. - Optional fields are omitted from JSON output rather than set to null. Use
jq's//operator to provide defaults:.field // "default". - Combine
--jsonwith-p(page number) to get output for a single page.
Vendor Support
IDB Utils supports three InnoDB implementations: MySQL, Percona XtraDB, and MariaDB. Each has differences in on-disk format, checksum algorithms, and feature availability.
Compatibility Matrix
| Feature | MySQL | Percona XtraDB | MariaDB |
|---|---|---|---|
| Detection | Default | Redo log created_by | FSP flags bit 4/16 |
| Checksums | CRC-32C, Legacy | Same as MySQL | full_crc32 (10.5+), CRC-32C |
| Page Types | All standard | Same as MySQL | +PageCompressed, +Instant |
| Compression | zlib, LZ4 | Same as MySQL | zlib, LZ4, LZO*, LZMA*, bzip2*, Snappy* |
| Encryption | Tablespace-level | Same as MySQL | Per-page only |
| SDI | Yes (8.0+) | Yes (8.0+) | N/A |
| Redo Logs | Full parsing | Full parsing | Header + checkpoints only |
*detection only, not decompressed
Vendor Detection Logic
inno auto-detects the vendor from on-disk metadata. The detection priority is:
- FSP flags bit 4 set -- MariaDB
full_crc32format (10.5+). This is unambiguous. - FSP flags bit 16 set, bits 11-14 zero -- MariaDB original format.
- Redo log
created_bystring -- contains "Percona" for Percona XtraDB, otherwise MySQL. - Default -- MySQL. Percona XtraDB is binary-compatible with MySQL at the tablespace level, so tablespace-only analysis cannot distinguish them.
MariaDB Notes
Checksum Differences
MariaDB 10.5+ uses the full_crc32 checksum format:
- Single CRC-32C computed over bytes
[0..page_size-4) - Checksum stored in the last 4 bytes of the page, not in the FIL header
- Detected via FSP flags bit 4
Earlier MariaDB versions use the same CRC-32C and Legacy algorithms as MySQL.
Page Type Ambiguity
Page type value 18 has different meanings depending on the vendor:
- MySQL:
SDI_BLOB(SDI overflow data) - MariaDB:
INSTANT(instant ALTER TABLE metadata)
inno uses the detected vendor to resolve this ambiguity automatically.
SDI Not Available
MariaDB does not use Serialized Dictionary Information. Running inno sdi on a MariaDB tablespace will return an error. MariaDB stores table metadata in .frm files (10.x) or its own data dictionary format.
Compression Algorithms
MariaDB supports additional page compression algorithms beyond MySQL's zlib and LZ4. inno detects the compression type from page headers but only decompresses zlib and LZ4. Other algorithms (LZO, LZMA, bzip2, Snappy) are identified in output but their data is not decompressed.
MySQL Version Support
| Version | Tablespace Files | Redo Log Files | SDI |
|---|---|---|---|
| MySQL 5.7 | .ibd | ib_logfile0, ib_logfile1 | N/A |
| MySQL 8.0 | .ibd | ib_logfile0, ib_logfile1 | Yes |
| MySQL 8.0.30+ | .ibd | #innodb_redo/#ib_redo* | Yes |
| MySQL 8.4 | .ibd | #innodb_redo/#ib_redo* | Yes |
| MySQL 9.x | .ibd | #innodb_redo/#ib_redo* | Yes |
Page Sizes
All vendors support the same set of page sizes: 4K, 8K, 16K (default), 32K, and 64K. The page size is encoded in the FSP flags on page 0 and is auto-detected by inno regardless of vendor.
Percona XtraDB Notes
Percona XtraDB is a fork of MySQL's InnoDB engine. At the tablespace level, the on-disk format is binary-compatible with MySQL. The only reliable way to distinguish Percona from MySQL is through the redo log created_by string, which Percona sets to include "Percona" or "XtraDB".
All inno subcommands work identically for MySQL and Percona tablespace files.
Upgrade Compatibility Checking
The inno compat subcommand analyzes InnoDB tablespace files to determine whether they are compatible with a target MySQL version. This is useful when planning MySQL version upgrades, especially across major versions (e.g., 5.7 to 8.0, or 8.0 to 9.0).
Quick Start
Check a single tablespace against MySQL 8.4:
inno compat -f /var/lib/mysql/mydb/users.ibd -t 8.4.0
Scan an entire data directory:
inno compat --scan /var/lib/mysql -t 9.0.0
What Gets Checked
| Check | Description | Severity |
|---|---|---|
| page_size | Non-default page sizes (4K/8K/32K/64K) require MySQL 5.7.6+ | Error |
| sdi | SDI metadata is required for MySQL 8.0+ but absent in pre-8.0 files | Error |
| encryption | Tablespace-level encryption requires MySQL 5.7.11+ | Error |
| vendor | MariaDB tablespaces are incompatible with MySQL (divergent formats) | Error |
| row_format | COMPRESSED is deprecated in MySQL 8.4+, REDUNDANT in 9.0+ | Warning |
| compression | Page compression detected (informational) | Info |
Severity Levels
- Error: The tablespace cannot be used with the target version. Migration or conversion is required.
- Warning: The tablespace will work but uses deprecated features. Plan for future migration.
- Info: Informational finding, no action required.
Single-File Mode
Analyze one tablespace in detail:
inno compat -f table.ibd -t 8.4.0 -v
The -v (verbose) flag shows current and expected values for each check. JSON output is available with --json:
inno compat -f table.ibd -t 8.4.0 --json
Example JSON output:
{
"file": "table.ibd",
"target_version": "8.4.0",
"source_version": "8.0.32",
"compatible": true,
"checks": [
{
"check": "vendor",
"message": "MySQL tablespace detected",
"severity": "info"
}
],
"summary": {
"total_checks": 1,
"errors": 0,
"warnings": 0,
"info": 1
}
}
Directory Scan Mode
Scan all .ibd files under a data directory:
inno compat --scan /var/lib/mysql -t 9.0.0
The scan uses parallel processing for fast analysis of large data directories. Add --depth 0 for unlimited recursion, or --depth 3 to limit traversal depth.
With --json, the output includes per-file results and aggregate counts:
inno compat --scan /var/lib/mysql -t 9.0.0 --json
Common Upgrade Scenarios
MySQL 5.7 to 8.0
Key checks:
- SDI metadata is required (added in 8.0). Pre-8.0 tablespaces will report an SDI error.
- Encryption support is available but the format changed.
MySQL 8.0 to 8.4
Key checks:
ROW_FORMAT=COMPRESSEDis deprecated. Tables using it will get a warning.- All other formats (DYNAMIC, COMPACT) remain fully supported.
MySQL 8.x to 9.0
Key checks:
ROW_FORMAT=REDUNDANTis deprecated in 9.0+.ROW_FORMAT=COMPRESSEDdeprecation continues from 8.4.
MariaDB to MySQL
MariaDB tablespaces are flagged as incompatible with MySQL due to divergent on-disk formats (different checksum algorithms, FSP flags, and page types). Migration requires a logical dump and reload.
Backup Verification
The inno verify subcommand performs structural integrity checks on InnoDB tablespace files without requiring valid checksums. It can also verify backup chain continuity and redo log consistency.
Quick Start
Verify a single tablespace:
inno verify -f /var/lib/mysql/mydb/users.ibd
Verify a backup chain:
inno verify --chain full_backup.ibd incremental_1.ibd incremental_2.ibd
Verify redo log continuity:
inno verify -f users.ibd --redo /var/lib/mysql/ib_logfile0
Structural Checks
The verify subcommand runs six structural checks on every page in the tablespace:
| Check | Description |
|---|---|
| PageNumberSequence | The page number stored at offset 4 matches its expected position in the file |
| SpaceIdConsistency | All pages have the same space ID as page 0 |
| LsnMonotonicity | LSNs are non-decreasing across pages (within tolerance) |
| BTreeLevelConsistency | INDEX leaf pages have level 0, internal pages have level > 0 |
| PageChainBounds | prev/next pointers are within file bounds; first page has prev = FIL_NULL |
| TrailerLsnMatch | The trailer LSN low-32 bits match the header LSN low-32 bits |
Usage
Basic Verification
inno verify -f table.ibd
With verbose output showing per-page findings:
inno verify -f table.ibd -v
JSON Output
inno verify -f table.ibd --json
Example output:
{
"file": "table.ibd",
"total_pages": 128,
"page_size": 16384,
"passed": true,
"findings": [],
"summary": [
{"kind": "PageNumberSequence", "pages_checked": 128, "issues_found": 0, "passed": true},
{"kind": "SpaceIdConsistency", "pages_checked": 128, "issues_found": 0, "passed": true}
]
}
Backup Chain Verification
The --chain flag accepts multiple tablespace files and verifies that they form a valid backup chain based on LSN ordering.
inno verify --chain full.ibd incr1.ibd incr2.ibd
The chain verifier checks:
- Files are ordered by ascending max LSN
- No LSN gaps between consecutive files in the chain (the max LSN of file N should be less than or equal to the max LSN of file N+1)
- All files have the same space ID
- At least two files are provided
JSON Output
inno verify --chain full.ibd incr1.ibd --json
Returns a ChainReport with per-file info and any detected gaps.
Redo Log Continuity
The --redo flag verifies that a tablespace's LSN state is consistent with a redo log file.
inno verify -f table.ibd --redo /var/lib/mysql/ib_logfile0
This compares the checkpoint LSN from the redo log with the maximum LSN found in the tablespace. If the tablespace contains changes beyond the redo log's checkpoint, the redo log may be behind.
When to Use
- After backups: Verify that backup files are structurally sound before relying on them for disaster recovery.
- Before restores: Check that a backup chain is complete and contiguous.
- Corruption diagnosis: Identify structural issues that go beyond checksum validation (which
inno checksumhandles). - Monitoring: Include in periodic health checks alongside
inno auditandinno health.
Live MySQL Validation
The inno validate subcommand cross-validates on-disk tablespace files against live MySQL metadata. It detects orphan files, missing tablespaces, and space ID mismatches between the filesystem and MySQL's internal registry.
Note: MySQL cross-validation requires the
mysqlfeature. Build with:cargo build --release --features mysql
Quick Start
Scan a data directory without MySQL (disk-only mode):
inno validate -d /var/lib/mysql
Cross-validate against a live MySQL instance:
inno validate -d /var/lib/mysql --host 127.0.0.1 --user root
Modes
Disk-Only Mode
Without MySQL connection options, validate scans for .ibd files and reports what it finds on disk:
inno validate -d /var/lib/mysql
This lists each tablespace file with its space ID (read from page 0). Useful for quick inventory without a running MySQL instance.
MySQL Cross-Validation Mode
With --host and --user (or --defaults-file), validate queries INFORMATION_SCHEMA.INNODB_TABLESPACES and compares the results against disk:
inno validate -d /var/lib/mysql --host 127.0.0.1 --user root --password secret
Filter to a specific database:
inno validate -d /var/lib/mysql --host 127.0.0.1 --user root -D mydb
Deep Table Validation
The --table flag performs deep validation of a specific table, verifying index root pages and space ID consistency:
inno validate -d /var/lib/mysql --host 127.0.0.1 --user root --table mydb.users
This checks:
- The
.ibdfile exists at the expected path - The space ID in the file matches MySQL's registry
- Each index root page exists and contains a valid INDEX page
- Row format consistency between MySQL and the on-disk file
What Gets Detected
| Finding | Description |
|---|---|
| Orphan files | .ibd files on disk that have no matching entry in MySQL's tablespace registry |
| Missing files | Tablespaces registered in MySQL but with no corresponding .ibd file on disk |
| Space ID mismatches | Files where the on-disk space ID differs from MySQL's recorded space ID |
| Invalid index roots | Root pages that don't exist or aren't INDEX pages (--table mode) |
JSON Output
inno validate -d /var/lib/mysql --host 127.0.0.1 --user root --json
The cross-validation report includes:
{
"disk_files": 42,
"mysql_tablespaces": 42,
"orphans": [],
"missing": [],
"mismatches": [],
"passed": true
}
Connection Options
| Option | Description |
|---|---|
--host | MySQL server hostname or IP |
--port | MySQL server port (default: 3306) |
--user / -u | MySQL username |
--password / -p | MySQL password |
--defaults-file | Path to MySQL defaults file (.my.cnf) |
You can also use a defaults file:
inno validate -d /var/lib/mysql --defaults-file ~/.my.cnf
Common Use Cases
Post-Migration Check
After migrating tablespace files or restoring from backup, verify that all files match MySQL's internal state:
inno validate -d /var/lib/mysql --host 127.0.0.1 --user root --json
Orphan File Cleanup
Find .ibd files left behind after DROP TABLE operations that didn't fully clean up:
inno validate -d /var/lib/mysql --host 127.0.0.1 --user root -v
Orphan files are listed with their paths and space IDs for manual review.
Disaster Recovery Triage
After a crash, quickly assess whether all tablespaces match MySQL's expectations:
inno validate -d /var/lib/mysql --host 127.0.0.1 --user root
A FAIL result with missing files indicates tablespaces that MySQL expects but cannot find, which may need restoration from backup.
Data Type Decoding
The inno export subcommand extracts record-level data from InnoDB tablespace files. It uses SDI metadata (MySQL 8.0+) to decode field types and column names, producing CSV, JSON, or hex output.
Supported Data Types
| Type | Decoding | Notes |
|---|---|---|
| TINYINT, SMALLINT, MEDIUMINT, INT, BIGINT | Full | Signed and unsigned; high-bit XOR for InnoDB ordering |
| FLOAT | Full | IEEE 754 single-precision |
| DOUBLE | Full | IEEE 754 double-precision |
| DECIMAL | Full | InnoDB packed BCD encoding |
| DATE | Full | 3-byte packed format (day + month32 + year16*32) |
| DATETIME / DATETIME2 | Full | 5-byte packed bit-field + FSP bytes |
| TIMESTAMP / TIMESTAMP2 | Full | 4-byte UTC epoch + FSP bytes |
| TIME / TIME2 | Full | 3-byte offset encoding + FSP bytes |
| YEAR | Full | 1-byte, offset from 1900 |
| CHAR, VARCHAR | Full | Length-prefixed with character set |
| ENUM | Full | 1-2 byte index into element list from SDI |
| SET | Full | 1-8 byte bitmask into element list from SDI |
| TEXT, BLOB | Partial | Inline data as hex; off-page references noted |
| JSON | Partial | Inline data as hex; off-page references noted |
| GEOMETRY | Partial | Raw WKB bytes as hex |
| Other | Hex fallback | Unknown types shown as hex strings |
Usage
Basic CSV Export
inno export -f users.ibd
JSON Export
inno export -f users.ibd --format json
Hex Dump
inno export -f users.ibd --format hex
Export Specific Page
inno export -f users.ibd -p 3
Include Delete-Marked Records
Useful for forensic recovery of recently deleted rows:
inno export -f users.ibd --where-delete-mark
Include System Columns
Show InnoDB internal columns (DB_TRX_ID, DB_ROLL_PTR):
inno export -f users.ibd --system-columns
DECIMAL Encoding
InnoDB stores DECIMAL values in packed BCD (Binary-Coded Decimal) format. Key details:
- Groups of 9 digits are stored as 4-byte integers
- Leftover digits (1-8) use 1-3 bytes based on digit count
- The sign is encoded in the high bit of the first byte (1 = positive)
- Negative values have all bytes XOR'd with 0xFF
For DECIMAL(10,2), the value 1234567.89 is stored as two groups: 1234567 (integer part) and 89 (fractional part), each packed as a 4-byte integer with appropriate padding.
TIME2 Encoding
InnoDB TIME2 uses offset encoding (not sign-bit XOR like some other types):
- 3-byte base value stores
hours << 12 | minutes << 6 | seconds - The stored value is offset by
0x800000to handle negative times - Fractional seconds (FSP) follow in 0-3 additional bytes
- Negative times (e.g.,
-12:30:45) are supported
ENUM and SET Types
When SDI metadata is available, ENUM and SET values are decoded using the element list from the data dictionary:
- ENUM: 1-2 byte index (1-based) mapped to the element name
- SET: 1-8 byte bitmask where each bit corresponds to a set element
Without SDI, these types fall back to numeric representation.
Limitations
- Off-page data: BLOB, TEXT, JSON, and GEOMETRY values stored off-page (extern bit set) are reported as
[OFF-PAGE]with the space ID, page number, and offset. The actual external data is not followed. - Pre-8.0 tablespaces: Without SDI metadata, column types cannot be determined and all fields are exported as hex.
- Compressed tablespaces: Records in compressed pages are not decompressed for export.
Audit Logging
IDB Utils provides structured audit logging for all write operations. Every page write, backup creation, and repair operation can be recorded as NDJSON events for compliance, forensics, and operational traceability.
Quick Start
Enable audit logging with the global --audit-log flag:
inno repair -f table.ibd --audit-log /var/log/inno-audit.jsonl
All write operations in that session are recorded to the specified file.
Supported Operations
Audit logging is available on all subcommands that modify tablespace files:
| Subcommand | Events Logged |
|---|---|
inno repair | page_write per repaired page, backup_created |
inno repair --batch | Same as above, per file in the batch |
inno corrupt | page_write per corrupted page |
inno defrag | file_write for the output file |
inno transplant | page_write per transplanted page, backup_created |
Event Format
Each event is a single JSON line (NDJSON format):
{"timestamp":"2025-06-15T13:30:45.123Z","event":"session_start","session_id":"abc123","args":["inno","repair","-f","table.ibd"]}
{"timestamp":"2025-06-15T13:30:45.200Z","event":"backup_created","session_id":"abc123","path":"table.ibd.bak"}
{"timestamp":"2025-06-15T13:30:45.300Z","event":"page_write","session_id":"abc123","file":"table.ibd","page":5,"algorithm":"crc32c"}
{"timestamp":"2025-06-15T13:30:45.400Z","event":"session_end","session_id":"abc123"}
Event Types
| Event | Description | Fields |
|---|---|---|
session_start | Beginning of a write session | session_id, args |
page_write | A page was modified | session_id, file, page, algorithm |
file_write | A new file was created | session_id, path |
backup_created | A backup file was created | session_id, path |
session_end | End of a write session | session_id |
Batch Repair with Audit
The --batch mode repairs all .ibd files under a directory, with full audit logging:
inno repair --batch /var/lib/mysql --audit-log /var/log/repair-audit.jsonl
Each file is processed in parallel, and all page writes are logged with the correct file path.
File Locking
The audit log file uses fs2 file-level locking to prevent corruption when multiple inno processes write to the same audit log simultaneously. This is safe for concurrent batch operations.
Integration
Parsing Audit Logs
Since the format is NDJSON, standard tools work:
# Count page writes
grep '"page_write"' /var/log/inno-audit.jsonl | wc -l
# Extract all modified files
grep '"page_write"' /var/log/inno-audit.jsonl | jq -r .file | sort -u
# Filter by session
grep '"abc123"' /var/log/inno-audit.jsonl | jq .
Watch Events
The inno watch --events flag produces similar NDJSON output for real-time monitoring:
inno watch -f table.ibd --events
Events include watch_start, page_change, watch_error, and watch_stop.
MySQL Version Compatibility Reference
This page documents the InnoDB on-disk format differences across MySQL versions that affect inno tool behavior and compatibility checking.
Version Feature Matrix
| Feature | 5.6 | 5.7 | 8.0 | 8.4 | 9.0 |
|---|---|---|---|---|---|
| Default page size | 16K | 16K | 16K | 16K | 16K |
| Non-default page sizes (4K/8K/32K/64K) | - | 5.7.6+ | Yes | Yes | Yes |
| CRC-32C checksums | - | 5.7.7+ | Yes | Yes | Yes |
| Legacy InnoDB checksums | Yes | Yes | Yes | Yes | Yes |
| SDI metadata | - | - | Yes | Yes | Yes |
| Tablespace-level encryption | - | 5.7.11+ | Yes | Yes | Yes |
| ROW_FORMAT=COMPRESSED | Yes | Yes | Yes | Deprecated | Deprecated |
| ROW_FORMAT=REDUNDANT | Yes | Yes | Yes | Yes | Deprecated |
| ROW_FORMAT=DYNAMIC | Yes | Default | Default | Default | Default |
| Instant ADD COLUMN | - | - | 8.0.12+ | Yes | Yes |
| Instant ADD COLUMN v2 | - | - | 8.0.29+ | Yes | Yes |
| Redo log format | Pre-8.0 | Pre-8.0 | Pre-8.0.30 / 8.0.30+ | 8.0.30+ | 8.0.30+ |
Checksum Algorithms by Version
MySQL
| Version Range | Algorithm | Detection |
|---|---|---|
| < 5.7.7 | Legacy InnoDB | ut_fold_ulint_pair byte-by-byte |
| 5.7.7+ | CRC-32C | XOR of two CRC32c ranges |
| All versions | Page 0 | May use either algorithm |
MariaDB
| Version Range | Algorithm | Detection |
|---|---|---|
| < 10.5 | Legacy InnoDB or CRC-32C | Same as MySQL |
| 10.5+ | full_crc32 | Single CRC-32C over entire page; checksum in last 4 bytes |
The full_crc32 format is detected via FSP flags bit 4 and is mutually exclusive with MySQL's checksum format.
Percona XtraDB
Percona XtraDB uses the same checksum algorithms as MySQL. Tablespace files are binary-compatible.
Redo Log Format Changes
| Version | Format | Files |
|---|---|---|
| < 8.0.30 | Legacy | ib_logfile0, ib_logfile1 |
| 8.0.30+ | New | #ib_redo* numbered files |
The inno log subcommand supports both formats. The inno verify --redo flag accepts either format for LSN continuity verification.
SDI (Serialized Dictionary Information)
MySQL 8.0 introduced SDI, embedding the data dictionary directly in each tablespace file. This replaces the .frm files used in MySQL 5.7 and earlier.
SDI enables:
- Schema extraction (
inno schema) - Column type decoding (
inno export) - Index name resolution (
inno health) - Version identification (via
mysqld_version_id) - Compatibility analysis (
inno compat)
Pre-8.0 tablespaces without SDI have limited functionality:
inno schemafalls back to index structure inferenceinno exportoutputs hex-only datainno compatreports SDI absence as an error when targeting 8.0+
Using inno compat for Version Checking
The inno compat subcommand codifies these version rules into automated checks:
# Check single file
inno compat -f table.ibd -t 9.0.0
# Scan entire data directory
inno compat --scan /var/lib/mysql -t 8.4.0
# JSON output for scripting
inno compat --scan /var/lib/mysql -t 9.0.0 --json
See the Upgrade Compatibility guide for detailed usage.
Undo Log Analysis
This guide explains how to use inno undo to inspect undo tablespace internals, diagnose long-running transactions, and understand rollback segment utilization.
When to Use
- Long-running transaction investigation — identify active transactions holding undo history
- Undo tablespace bloat — check segment states and purge backlog
- XA transaction recovery — find PREPARED transactions that may need manual resolution
- Forensic analysis — review transaction IDs and types from undo log headers
Undo Tablespace Structure
MySQL 8.0+ dedicated undo tablespaces (.ibu files) have this internal layout:
Page 0: FSP_HDR (file space header)
Page 1: IBUF_BITMAP (unused in undo tablespaces)
Page 2: INODE
Page 3: RSEG_ARRAY — rollback segment slot directory
Page 4+: RSEG header pages and undo segment pages
The RSEG array page contains up to 128 slots, each pointing to a rollback segment header page. Each rollback segment header manages up to 1024 undo log slots.
Basic Analysis
inno undo -f /var/lib/mysql/undo_001.ibu
This shows the RSEG slot layout, per-segment states, and transaction summary.
Identifying Active Transactions
inno undo -f undo_001.ibu --json | jq '.segments[] | select(.segment_header.state == "Active")'
Active segments are currently in use by running transactions. If you see many active segments while no queries are running, you may have abandoned XA transactions or a hung purge thread.
Checking Purge Backlog
inno undo -f undo_001.ibu --json | jq '{
total: .total_transactions,
active: .active_transactions,
to_purge: [.segments[] | select(.segment_header.state == "ToPurge")] | length
}'
A large number of ToPurge segments indicates the purge thread is falling behind, which causes undo tablespace growth.
Verbose Record Inspection
inno undo -f undo_001.ibu -v
Verbose mode walks individual undo records within each segment, reporting record types (INSERT, UPDATE_EXIST, UPDATE_DEL, DEL_MARK) and table IDs. This is useful for understanding which tables are generating the most undo history.
Single Page Inspection
inno undo -f undo_001.ibu -p 5
Inspect a specific undo page to see its page header, segment header, and all undo log headers on that page.
Encrypted Undo Tablespaces
MySQL 8.0.16+ supports encryption of undo tablespaces:
inno undo -f undo_001.ibu --keyring /var/lib/mysql-keyring/keyring
The keyring file must contain the master encryption key matching the tablespace.
Undo Segment Lifecycle
Understanding segment states helps diagnose undo tablespace issues:
┌─────────┐ ┌─────────┐ ┌──────────┐ ┌─────────┐
│ Free │───▶│ Active │───▶│ ToPurge │───▶│ Cached │
└─────────┘ └─────────┘ └──────────┘ └─────────┘
│ │
▼ │
┌──────────┐ │
│ Prepared │ │
└──────────┘ │
│ ┌──────────┐ │
└─────────────▶│ ToFree │◀───┘
└──────────┘
- Free — slot is empty, available for allocation
- Active — transaction is actively writing undo records
- ToPurge — transaction committed, waiting for purge
- Cached — purged and available for reuse without reallocation
- Prepared — XA transaction in PREPARED state
- ToFree — segment being deallocated during truncation
LOB Chain Traversal
This guide explains how to inspect Large Object (LOB/BLOB) storage chains in InnoDB tablespaces using the --lob-chain flag on inno pages.
When to Use
- Diagnosing LOB corruption — verify chain integrity when BLOB data reads fail
- Storage analysis — understand how much space LOB data consumes across pages
- Version investigation — examine LOB version chains for MVCC-related issues
- Recovery assessment — determine which LOB pages are intact before attempting recovery
LOB Storage in InnoDB
InnoDB stores large values (TEXT, BLOB, JSON, long VARCHAR) externally when they exceed the inline threshold. There are two storage formats:
Old-Style BLOB Chains (Pre-8.0.12)
Simple linked list of BLOB pages (type FIL_PAGE_TYPE_BLOB):
INDEX page record ──▶ BLOB page 1 ──▶ BLOB page 2 ──▶ BLOB page 3
(first page) (next page) (next page)
New-Style LOB (MySQL 8.0.12+)
Three-level structure with index entries for partial updates:
INDEX page record ──▶ LOB_FIRST page
├── LOB index entries (60 bytes each)
│ ├── entry 1 ──▶ LOB_DATA page
│ ├── entry 2 ──▶ LOB_DATA page
│ └── entry 3 ──▶ LOB_DATA page
└── LOB_INDEX pages (overflow index entries)
Compressed LOBs use ZLOB variants (ZLOB_FIRST, ZLOB_DATA, ZLOB_FRAG, ZLOB_FRAG_ENTRY, ZLOB_INDEX).
Using --lob-chain
# Show LOB chain info for all LOB pages
inno pages -f table.ibd --lob-chain
# Focus on a specific LOB first page
inno pages -f table.ibd -p 42 --lob-chain
# JSON output with chain details
inno pages -f table.ibd --lob-chain --json
When --lob-chain is enabled, LOB-type pages (Blob, ZBlob, LobFirst, LobData, LobIndex, ZlobFirst, ZlobData, ZlobIndex, ZlobFrag, ZlobFragEntry) display additional chain traversal information.
Chain Output
For each LOB chain origin (BLOB first page or LOB_FIRST page), the output includes:
| Field | Description |
|---|---|
| Chain type | old_blob, lob, or zlob |
| First page | Page number of the chain start |
| Total data length | Sum of data across all pages in the chain |
| Page count | Number of pages in the chain |
| Pages | List of page numbers with per-page data lengths |
Identifying LOB Pages
Use page type filtering to find LOB pages:
# List all LOB-related pages
inno pages -f table.ibd -t Blob
inno pages -f table.ibd -t LobFirst
inno pages -f table.ibd -t LobData
Diagnosing Broken Chains
If a LOB chain is broken (missing page, corrupt pointer), the chain traversal will stop at the break point and report the last valid page. Compare the total_data_length against the expected column size to assess data loss.
inno pages -f table.ibd --lob-chain --json | jq '.[] | select(.lob_chain.page_count < 2)'
Binary Log Parsing
This guide explains how to use inno binlog to analyze MySQL binary log files for replication debugging, forensic analysis, and data change auditing.
When to Use
- Replication debugging — inspect binary log events to diagnose replication lag or errors
- Change auditing — review which tables were modified and how many rows were affected
- Forensic analysis — trace data changes by timestamp, server ID, and transaction
- Format validation — verify binary log integrity and event structure
Binary Log Structure
MySQL binary log files have a simple structure:
┌──────────────────────────────────────┐
│ Magic bytes: 0xfe 0x62 0x69 0x6e │ 4 bytes
├──────────────────────────────────────┤
│ FORMAT_DESCRIPTION_EVENT │ Variable length
├──────────────────────────────────────┤
│ Event 1 │
├──────────────────────────────────────┤
│ Event 2 │
├──────────────────────────────────────┤
│ ... │
└──────────────────────────────────────┘
Each event starts with a 19-byte common header containing timestamp, type code, server ID, event length, and next position.
Basic Analysis
inno binlog -f mysql-bin.000001
This shows the format description, event type distribution, table maps, and a chronological event listing.
Filtering Events
By Type
# Show only TABLE_MAP events
inno binlog -f mysql-bin.000001 --filter-type TABLE_MAP
# Show only row insert events
inno binlog -f mysql-bin.000001 --filter-type WRITE_ROWS
Type names are matched as substrings, so ROWS matches all row event types.
By Count
# Show first 50 events
inno binlog -f mysql-bin.000001 -l 50
JSON Analysis
# Event type distribution
inno binlog -f mysql-bin.000001 --json | jq '.event_type_counts'
# Tables with most row events
inno binlog -f mysql-bin.000001 --json | jq '[.table_maps[] | {db: .database_name, table: .table_name, cols: .column_count}]'
# Server version
inno binlog -f mysql-bin.000001 --json | jq '.format_description.server_version'
Verbose Mode
inno binlog -f mysql-bin.000001 -v
Verbose mode adds column type codes for TABLE_MAP events, which helps identify the column layout used in row events.
Key Event Types
Transaction Flow
A typical row-based transaction produces this sequence:
GTID_LOG_EVENT → Transaction identifier
QUERY_EVENT → BEGIN
TABLE_MAP_EVENT → Table schema for following row events
WRITE_ROWS_EVENT_V2 → INSERT data
TABLE_MAP_EVENT → Table schema (may differ for UPDATE)
UPDATE_ROWS_EVENT_V2 → UPDATE before/after images
XID_EVENT → COMMIT
DDL Events
DDL statements appear as QUERY_EVENT with the full SQL statement. They are not wrapped in BEGIN/COMMIT.
Web UI
The web analyzer can also parse binary log files. Drop a binary log file onto the dropzone and it will auto-detect the file type from the magic bytes, showing a dedicated Binary Log tab with event listing, type distribution, and table maps.
Technical Notes
- Binary logs use little-endian byte order (unlike InnoDB tablespace pages which use big-endian)
- The
--filter-typeflag matches against the string representation of event types - Event timestamps are Unix timestamps (seconds since epoch)
- Row events reference TABLE_MAP events by
table_id— the parser maintains an internal mapping
Spatial and Full-Text Indexes
This guide covers inspection and visualization of InnoDB R-tree spatial indexes and full-text search (FTS) auxiliary tables.
Spatial Indexes (R-tree)
Background
InnoDB spatial indexes use R-tree data structures to index geometry columns (POINT, LINESTRING, POLYGON, etc.). R-tree pages have the same basic structure as B+Tree INDEX pages but store Minimum Bounding Rectangles (MBRs) instead of key values.
Each MBR consists of four 64-bit floats (big-endian):
┌─────────┬─────────┬─────────┬─────────┐
│ min_x │ min_y │ max_x │ max_y │
│ 8 bytes │ 8 bytes │ 8 bytes │ 8 bytes │
└─────────┴─────────┴─────────┴─────────┘
CLI Inspection
R-tree pages appear in inno pages output with additional MBR detail:
# Show all pages including R-tree
inno pages -f spatial_table.ibd
# Focus on a specific R-tree page
inno pages -f spatial_table.ibd -p 4 -v
R-tree page output includes the tree level, record count, and enclosing MBR for the page.
Health Metrics
The inno health subcommand reports spatial index metrics alongside B+Tree indexes:
inno health -f spatial_table.ibd --json | jq '.indexes[] | select(.index_type == "RTREE")'
Web UI Visualization
The web analyzer includes a Spatial tab (keyboard shortcut: S) that provides:
- Summary cards — R-tree page count, tree levels, leaf pages, total MBRs
- Spatial extent — bounding box coordinates covering all MBRs
- Canvas visualization — interactive MBR rectangle rendering with:
- Color-coded rectangles (hue varies by MBR index)
- Coordinate grid with axis labels
- Tree level selector for multi-level indexes
- Page summary table — per-page level, record count, MBR count, and enclosing MBR
Full-Text Indexes (FTS)
Background
InnoDB full-text indexes are implemented using auxiliary tables stored as separate .ibd files in the same schema directory. Each FTS index creates multiple auxiliary files:
| File Pattern | Purpose |
|---|---|
FTS_<table_id>_CONFIG.ibd | FTS configuration and state |
FTS_<table_id>_<index_id>_INDEX_<N>.ibd | Inverted index partitions (0-5) |
FTS_<table_id>_DELETE.ibd | Deleted document IDs |
FTS_<table_id>_BEING_DELETED.ibd | Documents being removed from index |
FTS_<table_id>_DELETE_CACHE.ibd | Cached deletes pending merge |
FTS_<table_id>_BEING_DELETED_CACHE.ibd | Cached being-deleted entries |
Detection
IDB Utils can detect FTS auxiliary files by filename pattern:
# Audit a data directory to identify FTS tables
inno audit -d /var/lib/mysql/mydb --health --json | jq '.files[] | select(.name | startswith("FTS_"))'
FTS File Analysis
Each FTS auxiliary file is a regular InnoDB tablespace and can be inspected with any subcommand:
# Check FTS config table
inno pages -f /var/lib/mysql/mydb/FTS_0000000000000437_CONFIG.ibd
# Verify FTS index partition integrity
inno checksum -f /var/lib/mysql/mydb/FTS_0000000000000437_00000000000004a2_INDEX_1.ibd
# Health of FTS inverted index
inno health -f /var/lib/mysql/mydb/FTS_0000000000000437_00000000000004a2_INDEX_0.ibd
FTS in Health Reports
When running health analysis, FTS auxiliary files are identified and summarized:
inno health -f table.ibd --json
The health report includes an fts_info section when FTS auxiliary tables are detected, showing:
- Table ID and associated index count
- Whether CONFIG and DELETE tables exist
- Index partition coverage (expected: 6 partitions numbered 0-5)
Combining Spatial and FTS Analysis
For tables with both spatial and full-text indexes:
# Full health picture
inno health -f geosearch_table.ibd -v
# Audit entire schema
inno audit -d /var/lib/mysql/mydb --health
The audit subcommand automatically identifies R-tree pages within tablespaces and FTS auxiliary files in the directory, providing a comprehensive view of all index types.
Crash Recovery Simulation
The inno simulate subcommand helps you understand what would happen at each InnoDB innodb_force_recovery level before you commit to setting it in production.
When to Use
- MySQL refuses to start after a crash and you need to choose a recovery level
- You want to assess the risk of data loss before setting
innodb_force_recovery - You need to document the state of a corrupted tablespace for incident response
- You want to verify that a tablespace can survive recovery without data loss
Quick Start
# Simulate all recovery levels against your system tablespace
inno simulate -f /var/lib/mysql/ibdata1 -d /var/lib/mysql
# Focus on a specific level
inno simulate -f /var/lib/mysql/ibdata1 -d /var/lib/mysql --level 3 -v
Recovery Level Decision Tree
Start at level 1 and only increase if the previous level is insufficient:
-
Level 1 — Try first. Skips corrupt pages but preserves all recoverable data. Safe for most single-page corruption.
-
Level 2 — If level 1 hangs on background operations. Prevents purge and insert buffer merge.
-
Level 3 — If crash recovery itself fails. Skips transaction rollback, which means uncommitted transactions remain visible.
-
Level 4 — If insert buffer corruption is suspected. Skips merge operations entirely.
-
Level 5 — If undo log is corrupted. Skips undo scanning, meaning the undo tablespace is treated as empty.
-
Level 6 — Last resort. Skips redo log entirely. The database starts in whatever state the data files are in, ignoring any pending redo operations.
JSON Output
inno simulate -f ibdata1 -d /var/lib/mysql --json
{
"file": "ibdata1",
"levels": [
{
"level": 1,
"name": "SRV_FORCE_IGNORE_CORRUPT",
"survivable": true,
"affected_pages": 2,
"risks": ["2 corrupt pages will be skipped"]
}
]
}
Workflow: Post-Crash Recovery
- Assess: Run
inno simulateto understand the damage - Back up: Copy all data files before attempting recovery
- Recover: Set
innodb_force_recoveryto the lowest effective level - Export: Use
mysqldumpto export all data - Rebuild: Restore from the dump into a fresh MySQL instance
- Verify: Run
inno verifyandinno auditon the new data directory
Related Commands
inno verify— structural integrity verificationinno recover— page-level recoverability assessmentinno checksum— checksum validationinno repair— fix corrupt checksums
Backup Analysis
The inno backup subcommands help you verify backup integrity and understand what changed between backups. Use backup diff to compare page-level changes and backup chain to validate XtraBackup checkpoint continuity.
When to Use
- Validating that incremental backups are complete before a restore
- Identifying which pages changed between a full backup and the current state
- Verifying XtraBackup chain integrity after a backup rotation
- Cross-referencing tablespace LSNs against backup checkpoint metadata
Quick Start
Compare a backup against the current tablespace:
inno backup diff --base /backups/full/mydb/users.ibd \
--current /var/lib/mysql/mydb/users.ibd
Validate an XtraBackup chain:
inno backup chain -d /backups/mysql
Cross-reference with inno verify:
inno verify -f users.ibd --backup-meta /backups/full/xtrabackup_checkpoints
Incremental Backup Comparison
The backup diff command compares page LSNs between two snapshots of the same tablespace:
inno backup diff --base backup/users.ibd --current live/users.ibd --json
{
"base_file": "backup/users.ibd",
"current_file": "live/users.ibd",
"summary": {
"unchanged": 120,
"modified": 8,
"added": 2,
"removed": 0,
"regressed": 0
},
"modified_page_types": {
"INDEX": 7,
"UNDO_LOG": 1
}
}
Regressed pages (current LSN < base LSN) are unusual and may indicate a partial restore or tablespace corruption.
XtraBackup Chain Validation
The backup chain command reads xtrabackup_checkpoints files from backup subdirectories:
inno backup chain -d /backups/mysql -v
Expected directory layout:
/backups/mysql/
full_2026-03-01/
xtrabackup_checkpoints
incr_2026-03-02/
xtrabackup_checkpoints
incr_2026-03-03/
xtrabackup_checkpoints
The validator checks:
- At least one full backup exists
- LSN ranges are contiguous (no gaps)
- Overlapping ranges are flagged but don't break the chain
Backup Metadata Verification
The inno verify --backup-meta flag cross-references a tablespace's page LSNs against an XtraBackup checkpoint file:
inno verify -f /var/lib/mysql/mydb/users.ibd \
--backup-meta /backups/full/xtrabackup_checkpoints
This checks that all page LSNs fall within the checkpoint's from_lsn..to_lsn window. Pages outside the window indicate either:
- The tablespace has been modified since the backup (pages after window)
- The tablespace contains pages from before the backup's coverage (pages before window)
Related Commands
inno verify— structural integrity verificationinno checksum— page-level checksum validationinno diff— byte-level comparison between two tablespace files
Bloat Scoring
The inno health --bloat flag computes a bloat score and letter grade (A-F) for each index in a tablespace. Use it to identify tables that need OPTIMIZE TABLE or ALTER TABLE ... FORCE.
When to Use
- Scheduling table maintenance: identify which tables benefit most from optimization
- Investigating slow queries: high bloat correlates with suboptimal scan performance
- Monitoring index health over time with
inno audit --health --bloat --prometheus - Setting alerts on bloat grade thresholds with
inno audit --health --max-bloat-grade
Quick Start
# Single tablespace
inno health -f users.ibd --bloat
# Directory-wide audit
inno audit -d /var/lib/mysql --health --bloat
# Filter to worst offenders (grade C or worse)
inno audit -d /var/lib/mysql --health --max-bloat-grade C
Formula
The bloat score is a weighted average of four components:
| Component | Weight | Description |
|---|---|---|
| Fill factor deficit | 30% | 1 - avg_fill_factor — lower fill means more wasted space |
| Garbage ratio | 25% | Average garbage bytes / usable page space |
| Fragmentation | 25% | Ratio of non-sequential leaf page transitions |
| Delete-mark ratio | 20% | Delete-marked records / total walked records |
The final score ranges from 0.0 (no bloat) to 1.0 (maximum bloat).
Grade Thresholds
| Grade | Score Range | Interpretation |
|---|---|---|
| A | < 0.10 | Healthy — no action needed |
| B | 0.10 - 0.19 | Minor bloat — monitor but no immediate action |
| C | 0.20 - 0.34 | Moderate — consider OPTIMIZE TABLE during maintenance window |
| D | 0.35 - 0.49 | Significant — schedule optimization soon |
| F | >= 0.50 | Critical — OPTIMIZE TABLE or ALTER TABLE ... FORCE recommended |
JSON Output
inno health -f users.ibd --bloat --json
Each index in the JSON output includes a bloat object:
{
"bloat": {
"score": 0.23,
"grade": "C",
"components": {
"fill_factor_deficit": 0.15,
"garbage_ratio": 0.30,
"fragmentation": 0.20,
"delete_mark_ratio": 0.10
},
"recommendation": "Consider OPTIMIZE TABLE during next maintenance window"
}
}
Directory-Wide Alerts
Use inno audit with bloat to scan all tablespaces:
# Show only tables with grade C or worse
inno audit -d /var/lib/mysql --health --max-bloat-grade C --json
The --max-bloat-grade flag implies --bloat — you don't need both flags.
Cardinality Estimation
The --cardinality flag estimates distinct values for the leading primary key column:
inno health -f users.ibd --cardinality --sample-size 200
This uses deterministic sampling (every k-th leaf page) without any random number generation.
Related Commands
inno health— per-index B+Tree health metricsinno audit --health— directory-wide health scanninginno pages --deleted— view delete-marked records per page
Undo Log Structure
InnoDB undo logs store previous versions of modified records, enabling MVCC (multi-version concurrency control) and transaction rollback. Understanding the on-disk undo format is essential for forensic analysis and data recovery.
Tablespace Layout
Since MySQL 8.0, undo logs live in dedicated undo tablespaces (.ibu files) rather than the system tablespace. Each undo tablespace begins with an RSEG Array page (page 0) that holds an array of rollback segment page numbers.
Undo tablespace (.ibu)
+-----------------------------+
| Page 0: RSEG Array | Array of rollback segment page numbers
+-----------------------------+
| Page N: Rollback Segment | Up to 128 RSEG headers per tablespace
| Page M: Rollback Segment |
+-----------------------------+
| Page X: Undo Log pages | Actual undo records (type FIL_PAGE_UNDO_LOG)
+-----------------------------+
Rollback Segments
Each rollback segment header page contains:
| Offset | Size | Field | Description |
|---|---|---|---|
| 38+0 | 4 | max_size | Maximum number of undo pages this segment can use |
| 38+4 | 4 | history_size | Number of committed transactions in the history list |
| 38+24 | 4096 | slots[1024] | Array of 1024 undo segment page numbers (FIL_NULL = empty) |
Each non-empty slot points to the first page of an undo segment -- the page where undo records for a single transaction are written.
Undo Page Structure
Every FIL_PAGE_UNDO_LOG page (type 2) has three headers stacked at the start of the page body:
+-------------------------------+ byte 0
| FIL Header (38 bytes) |
+-------------------------------+ byte 38
| Undo Page Header (18 bytes) | page type (INSERT/UPDATE), start/free offsets
+-------------------------------+ byte 56
| Undo Segment Header (30 bytes)| state, last log offset (first page of segment only)
+-------------------------------+ byte 86
| Undo Log Header (34 bytes) | trx_id, trx_no, del_marks, table_id
+-------------------------------+ byte 120+
| Undo Records | variable-length records
+-------------------------------+
Undo Page Header
| Offset | Size | Field | Description |
|---|---|---|---|
| 38+0 | 2 | page_type | 1 = INSERT, 2 = UPDATE |
| 38+2 | 2 | start | Offset of the first undo record |
| 38+4 | 2 | free | Offset of the first free byte |
INSERT undo logs contain only insert undo records and can be discarded immediately after transaction commit. UPDATE undo logs contain update and delete undo records and must be retained until the purge system processes them.
Undo Segment Header
Present only on the first page of each undo segment:
| Offset | Size | Field | Description |
|---|---|---|---|
| 56+0 | 2 | state | Segment state (see below) |
| 56+2 | 2 | last_log | Offset of the most recent undo log header |
Segment states:
| Value | State | Meaning |
|---|---|---|
| 1 | ACTIVE | Transaction is still running |
| 2 | CACHED | Segment is cached for reuse |
| 3 | TO_FREE | Insert undo, safe to free after purge |
| 4 | TO_PURGE | Update undo, retained for MVCC reads |
| 5 | PREPARED | Two-phase commit, prepared but not yet committed |
Undo Log Header
Each undo log within a segment starts with a 34-byte header:
| Offset | Size | Field | Description |
|---|---|---|---|
| 0 | 8 | trx_id | Transaction ID that created this undo log |
| 8 | 8 | trx_no | Transaction serial number (commit order) |
| 16 | 2 | del_marks | Non-zero if delete-mark records exist |
| 18 | 2 | log_start | Offset of the first undo record |
| 20 | 1 | xid_exists | Non-zero if XID info follows (distributed transactions) |
| 21 | 1 | dict_trans | Non-zero if this is a DDL transaction |
| 22 | 8 | table_id | Table ID (for insert undo logs) |
| 30 | 2 | next_log | Offset of the next undo log header (0 if last) |
| 32 | 2 | prev_log | Offset of the previous undo log header (0 if first) |
Undo Record Types
Each undo record begins with a type byte that encodes the operation:
| Type | Name | Description |
|---|---|---|
| 11 | INSERT_REC | Undo for an INSERT -- stores the inserted PK for rollback deletion |
| 12 | UPD_EXIST_REC | Undo for an in-place UPDATE -- stores old column values |
| 13 | UPD_DEL_REC | Undo for an UPDATE that was implemented as delete + insert |
| 14 | DEL_MARK_REC | Undo for a DELETE -- stores the delete-marked record's old values |
The type byte also encodes a "compiled" flag (bit 4) and an "extern" flag (bit 5) indicating whether external LOB references are present.
Field Order Gotcha
The field layout within an undo record depends on the record type, and getting this wrong is a common source of parsing bugs.
INSERT_REC records have a straightforward layout:
[type_cmpl | table_id (compressed) | PK fields...]
Modify-type records (UPD_EXIST_REC, UPD_DEL_REC, DEL_MARK_REC) place transaction metadata before the primary key:
[type_cmpl | table_id (compressed) | trx_id (6 bytes, fixed BE) | roll_ptr (7 bytes) | update_vector... | PK fields...]
Key details:
trx_idis a fixed 6-byte big-endian value (written bymach_write_to_6in MySQL source), not InnoDB compressed formatroll_ptris always 7 bytes- The update vector (changed column count + old values) comes before the PK fields
table_iduses InnoDB's variable-length compressed integer encoding
This ordering matches the MySQL source in trx0rec.cc.
Undo Chain Traversal
Undo records within a page are linked via 2-byte offsets. Each record stores a pointer to the next record's offset. Walking the chain starts at the log_start offset from the undo log header and follows next pointers until reaching offset 0 or the free boundary.
For multi-page undo logs, the undo page header contains a FLST_NODE linking pages in a doubly-linked list owned by the undo segment.
Source Reference
- Undo page parsing:
src/innodb/undo.rs--UndoPageHeader,UndoSegmentHeader,UndoLogHeader,RollbackSegmentHeader - Undo CLI:
src/cli/undo.rs--inno undosubcommand - Detailed undo record parsing for undelete:
src/innodb/undelete.rs--DetailedUndoRecord,parse_undo_records() - MySQL source:
storage/innobase/trx/trx0undo.cc,trx0rec.cc
LOB/BLOB Chain Formats
When a column value exceeds the space available on an INDEX page, InnoDB stores the overflow data on dedicated BLOB or LOB pages. Understanding these formats is necessary for inspecting externally stored columns and tracing data chains across pages.
When Does InnoDB Use External Storage?
InnoDB stores column data externally when:
- A row's total size exceeds roughly half the page size (keeping at least two rows per page for B+Tree efficiency)
- The column is a large
BLOB,TEXT,JSON, orLONGBLOBvalue - The row format is DYNAMIC or COMPRESSED (MySQL 5.7+), which stores only a 20-byte external pointer on the INDEX page
The INDEX page record contains a 20-byte BLOB reference (also called an "extern field ref") pointing to the first overflow page:
BLOB Reference (20 bytes on INDEX page):
+----------+---------+----------+--------+
| space_id | page_no | offset | length |
| 4 bytes | 4 bytes | 4 bytes | 8 bytes|
+----------+---------+----------+--------+
Old-Style BLOB Pages (Pre-8.0)
Three page types handle old-style externally stored data:
| Type | Value | Description |
|---|---|---|
| BLOB | 10 | Uncompressed overflow data |
| ZBLOB | 11 | First page of a compressed BLOB chain |
| ZBLOB2 | 12 | Subsequent pages of a compressed BLOB chain |
BLOB Page Header (8 bytes)
Each old-style overflow page has a simple header at FIL_PAGE_DATA (byte 38):
| Offset | Size | Field | Description |
|---|---|---|---|
| 0 | 4 | part_len | Number of data bytes stored on this page |
| 4 | 4 | next_page_no | Next overflow page number (FIL_NULL = last page) |
The data immediately follows the 8-byte header. With the default 16K page size, each BLOB page carries up to 16,338 bytes of payload (16384 - 38 header - 8 BLOB header).
Chain Traversal
Old-style BLOB chains are singly linked lists:
INDEX page BLOB page 5 BLOB page 12 BLOB page 20
+------------+ +------------+ +------------+ +------------+
| extern ref |----->| part_len | | part_len | | part_len |
| page_no=5 | | next=12 |----->| next=20 |----->| next=NULL |
+------------+ | data... | | data... | | data... |
+------------+ +------------+ +------------+
Walk the chain by reading next_page_no until it equals FIL_NULL (0xFFFFFFFF).
New-Style LOB Pages (MySQL 8.0+)
MySQL 8.0 introduced a richer LOB format with MVCC support at the LOB level. This format uses four new page types:
| Type | Value | Description |
|---|---|---|
| LOB_FIRST | 22 | First page of an uncompressed LOB |
| LOB_DATA | 23 | Data page in an uncompressed LOB |
| LOB_INDEX | 24 | Index page linking LOB data pages |
| ZLOB_FIRST | 25 | First page of a compressed LOB |
| ZLOB_DATA | 26 | Data page in a compressed LOB |
| ZLOB_FRAG | 27 | Fragment page for small compressed LOBs |
| ZLOB_FRAG_ENTRY | 28 | Fragment entry index page |
| ZLOB_INDEX | 29 | Index page for compressed LOB |
LOB First Page Header (12 bytes)
The first page of a new-style LOB contains metadata at FIL_PAGE_DATA:
| Offset | Size | Field | Description |
|---|---|---|---|
| 0 | 1 | version | LOB format version |
| 1 | 1 | flags | LOB flags |
| 2 | 4 | data_len | Total uncompressed data length |
| 6 | 6 | trx_id | Transaction ID that created the LOB |
After the header, the first page contains LOB index entries that describe where the data chunks reside.
LOB Index Entries (60 bytes each)
LOB index entries form a doubly-linked list and describe individual data chunks:
| Offset | Size | Field | Description |
|---|---|---|---|
| 0 | 6 | prev_node | Previous entry (page_no + offset) |
| 6 | 6 | next_node | Next entry (page_no + offset) |
| 12 | 1 | versions | Number of versions of this entry |
| 14 | 6 | trx_id | Transaction that created this chunk |
| 20 | 4 | trx_undo_no | Undo record number |
| 24 | 4 | page_no | Page containing the data for this chunk |
| 28 | 4 | data_len | Length of data on the referenced page |
| 32 | 4 | lob_version | LOB version number |
This structure enables MVCC for LOBs -- different transactions can see different versions of the same LOB by following different index entry chains.
LOB Data Page Header (11 bytes)
Each LOB_DATA page has a minimal header:
| Offset | Size | Field | Description |
|---|---|---|---|
| 0 | 1 | version | LOB format version |
| 1 | 4 | data_len | Bytes of data stored on this page |
| 5 | 6 | trx_id | Transaction that wrote this page |
Compressed LOBs
Compressed LOB pages (ZLOB_FIRST, ZLOB_DATA) use the same header layout as their uncompressed counterparts. The data_len field in the ZLOB first page header represents the total uncompressed length, while the data stored on individual ZLOB_DATA pages is zlib-compressed.
ZLOB_FRAG pages handle small compressed LOBs that fit within a single page fragment, avoiding the overhead of a full LOB chain.
Inspecting LOB Chains with inno
# Show all LOB pages in a tablespace
inno pages -f table.ibd -t blob
# Inspect LOB chain starting from a specific page
inno pages -f table.ibd --lob-chain -p 5
# JSON output for programmatic analysis
inno pages -f table.ibd --lob-chain -p 5 --json
The web UI Pages tab includes a LOB Chain panel that visualizes the chain when you select a LOB start page (BLOB, ZBLOB, ZBLOB2, LOB_FIRST, or ZLOB_FIRST).
Source Reference
- LOB page parsing:
src/innodb/lob.rs--BlobPageHeader,LobFirstPageHeader,LobDataPageHeader,LobIndexEntry - LOB chain traversal:
src/innodb/lob.rs--walk_blob_chain(),walk_lob_chain() - WASM LOB chain:
src/wasm.rs--analyze_lob_chain() - MySQL source:
storage/innobase/lob/lob0lob.cc,lob0first.cc
Binary Log Format
MySQL binary logs record all data-modifying events for replication and point-in-time recovery. Unlike InnoDB tablespace files which use big-endian byte order, all integer fields in binary logs are little-endian.
File Structure
Every binlog file begins with a 4-byte magic number followed by a sequence of events:
+-------------------+ byte 0
| Magic: 0xFE 'bin' | 4 bytes
+-------------------+ byte 4
| FORMAT_DESCRIPTION | First event (always)
+-------------------+
| Event 2 |
+-------------------+
| Event 3 |
+-------------------+
| ... |
+-------------------+
| ROTATE / STOP | Last event (usually)
+-------------------+
The magic bytes \xfebin ([0xfe, 0x62, 0x69, 0x6e]) identify the file as a MySQL binary log.
Common Event Header (19 bytes)
Every event starts with a 19-byte header. All fields are little-endian:
| Offset | Size | Field | Description |
|---|---|---|---|
| 0 | 4 | timestamp | Seconds since Unix epoch when the event was created |
| 4 | 1 | type_code | Event type identifier (see table below) |
| 5 | 4 | server_id | Originating server's server_id value |
| 9 | 4 | event_length | Total event size including header, payload, and checksum |
| 13 | 4 | next_position | Absolute file offset of the next event |
| 17 | 2 | flags | Event flags (bit 0 = binlog in use / not cleanly closed) |
The next_position field enables sequential scanning: read the header, then seek to next_position for the next event.
Event Types
The type code byte identifies the event kind. Key types for analysis:
| Code | Name | Description |
|---|---|---|
| 2 | QUERY_EVENT | SQL statement execution |
| 4 | ROTATE_EVENT | Points to the next binlog file |
| 15 | FORMAT_DESCRIPTION_EVENT | Binlog metadata (always first real event) |
| 16 | XID_EVENT | Transaction commit (XA transaction ID) |
| 19 | TABLE_MAP_EVENT | Maps table ID to schema/table name |
| 30 | WRITE_ROWS_EVENT | Row insert (row-based replication) |
| 31 | UPDATE_ROWS_EVENT | Row update (row-based replication) |
| 32 | DELETE_ROWS_EVENT | Row delete (row-based replication) |
| 33 | GTID_LOG_EVENT | Global Transaction ID |
| 39 | PARTIAL_UPDATE_ROWS_EVENT | Partial JSON update (MySQL 8.0+) |
| 40 | TRANSACTION_PAYLOAD_EVENT | Compressed transaction (MySQL 8.0.20+) |
A complete list of all 41 named event types is defined in src/binlog/constants.rs, derived from MySQL's binlog_event.h.
FORMAT_DESCRIPTION_EVENT
The first event after the magic bytes is always a FORMAT_DESCRIPTION_EVENT (FDE). It describes the binlog format version and the server that created the file:
| Offset | Size | Field | Description |
|---|---|---|---|
| 0 | 2 | binlog_version | Format version (4 for all modern MySQL) |
| 2 | 50 | server_version | Null-padded ASCII server version string |
| 52 | 4 | create_timestamp | Timestamp when the binlog was created |
| 56 | 1 | header_length | Common header length (always 19 for v4) |
| 57 | N | post_header_lengths | Array of per-event-type post-header sizes |
The FDE also implicitly tells the parser whether CRC-32C checksums are present. Since binlog_checksum=CRC32 is the default from MySQL 5.6.6 onward, the parser must handle both cases. inno binlog auto-detects checksum presence by attempting to parse the FDE with and without the trailing 4-byte CRC.
ROTATE_EVENT
A ROTATE_EVENT appears at the end of a binlog file (or during live rotation) and points to the next file in the sequence:
| Offset | Size | Field | Description |
|---|---|---|---|
| 0 | 8 | position | Start position in the next binlog file |
| 8 | N | filename | Name of the next binlog file (variable length) |
Row-Based Replication Events
Row-based replication (the default since MySQL 5.7.7) records actual row data rather than SQL statements.
TABLE_MAP_EVENT
Before any row events, a TABLE_MAP_EVENT maps a numeric table ID to a database and table name. It includes the column count and column type descriptors, enabling the row events that follow to be decoded.
Row Events (WRITE/UPDATE/DELETE)
Row events reference the table ID from the preceding TABLE_MAP_EVENT and contain:
- A bitmap of columns present in the row image
- For UPDATE events: a "before" bitmap and an "after" bitmap
- The actual row data encoded according to InnoDB's column type rules
Row events use v2 format (types 30-32) in MySQL 5.6+, which supports extra data fields for partial row images.
Event Checksums
Since MySQL 5.6.6, each event ends with a 4-byte CRC-32C checksum (when binlog_checksum=CRC32, which is the default):
[common header: 19 bytes][payload: N bytes][CRC-32C: 4 bytes]
The checksum covers all bytes from the start of the event through the end of the payload (everything except the checksum itself). The event_length field in the header includes the 4-byte checksum.
Byte Order Difference
This is worth emphasizing: binary logs use little-endian byte order for all integer fields. This is the opposite of InnoDB tablespace files, which use big-endian. The src/binlog/ module uses byteorder::LittleEndian throughout, while src/innodb/ uses byteorder::BigEndian.
Inspecting Binary Logs with inno
# Parse a binary log file
inno binlog -f mysql-bin.000001
# Verbose output with event details
inno binlog -f mysql-bin.000001 -v
# JSON output
inno binlog -f mysql-bin.000001 --json
Source Reference
- Constants and event codes:
src/binlog/constants.rs - Event types and common header:
src/binlog/event.rs--BinlogEventType,CommonEventHeader - FDE and ROTATE parsing:
src/binlog/header.rs--FormatDescriptionEvent,RotateEvent - Checksum validation:
src/binlog/checksum.rs - File reader and iteration:
src/binlog/file.rs--BinlogFile - MySQL source:
libbinlogevents/include/binlog_event.h,sql/binlog/event/event_reader.cpp
R-Tree Spatial Indexes
InnoDB uses R-Tree indexes to support spatial data types (GEOMETRY, POINT, LINESTRING, POLYGON, etc.). R-Tree pages share the same on-disk INDEX page structure as B+Tree pages but use Minimum Bounding Rectangles (MBRs) as keys instead of column values.
How R-Tree Indexes Work
A B+Tree orders records by comparing scalar key values. An R-Tree instead organizes records by spatial containment: each internal node stores an MBR that encloses all the MBRs in its child subtree. Queries find all records whose MBR overlaps a search rectangle.
Level 2 (root): [MBR covering entire dataset]
/ \
Level 1: [MBR region A] [MBR region B]
/ | \ / \
Level 0: [MBR] [MBR] [MBR] [MBR] [MBR]
(leaf) row row row row row
Leaf-level records contain the MBR of the actual geometry plus a pointer to the row in the clustered index. Non-leaf records contain an MBR plus a child page number.
MBR Structure (32 bytes)
Each MBR is stored as four IEEE 754 double-precision (8-byte) values in big-endian byte order:
| Offset | Size | Field | Description |
|---|---|---|---|
| 0 | 8 | min_x | Minimum X coordinate (longitude) |
| 8 | 8 | min_y | Minimum Y coordinate (latitude) |
| 16 | 8 | max_x | Maximum X coordinate |
| 24 | 8 | max_y | Maximum Y coordinate |
For a POINT type, min_x == max_x and min_y == max_y -- the bounding rectangle degenerates to a single point.
R-Tree Page Layout
R-Tree pages reuse the standard INDEX page header:
+-------------------------------+ byte 0
| FIL Header (38 bytes) |
+-------------------------------+ byte 38
| INDEX Header (36 bytes) | level, n_recs, heap_top, etc.
+-------------------------------+ byte 74
| FSEG Headers (20 bytes) |
+-------------------------------+ byte 94
| Infimum + Supremum (26 bytes) |
+-------------------------------+ byte 120
| Record area | R-tree records with MBR keys
+-------------------------------+
The level field in the INDEX header indicates tree depth (0 = leaf). The n_recs field gives the number of records on the page.
Each record in the record area consists of:
| Component | Size | Description |
|---|---|---|
| Record header | 5 bytes | Compact format record header |
| MBR key | 32 bytes | Minimum Bounding Rectangle |
| Child page / row pointer | 4+ bytes | Child page number (non-leaf) or clustered index key (leaf) |
Spatial Index Algorithm
InnoDB's R-Tree uses R*-Tree insertion heuristics:
- Search: Traverse from root, following nodes whose MBR overlaps the query rectangle
- Insert: Choose the subtree requiring the least MBR enlargement; reinsert overflowing entries rather than splitting immediately
- Split: When a node overflows and reinsertion does not help, split using a strategy that minimizes overlap between sibling MBRs
The SPATIAL index keyword in DDL creates an R-Tree:
CREATE SPATIAL INDEX idx_location ON stores(location);
Only NOT NULL columns can have spatial indexes. The SRID attribute (MySQL 8.0+) constrains the spatial reference system.
Inspecting R-Tree Pages with inno
R-Tree pages appear as INDEX pages with index_type=RTREE in SDI metadata:
# List all pages and identify R-Tree pages via fill factor and level
inno pages -f table.ibd
# View SDI to find RTREE index definitions
inno sdi -f table.ibd --pretty
The web UI Spatial tab renders the R-Tree structure and visualizes MBR containment relationships.
Source Reference
- MBR parsing:
src/innodb/rtree.rs--MinimumBoundingRectangle,RtreePageInfo,parse_rtree_page() - Web UI visualization:
web/src/components/spatial.js - MySQL source:
storage/innobase/gis/gis0sea.cc,gis0rtree.cc
Full-Text Search Auxiliary Tables
InnoDB implements full-text search (FTS) using a set of auxiliary tables that are stored as separate .ibd tablespace files. These tables maintain an inverted index mapping tokens to document IDs.
Architecture Overview
When you create a FULLTEXT index on an InnoDB table, MySQL creates several auxiliary tablespace files alongside the main table's .ibd file:
schema/
articles.ibd # Main table
FTS_0000000000000437_CONFIG.ibd # FTS configuration
FTS_0000000000000437_DELETED.ibd # Deleted document IDs
FTS_0000000000000437_DELETED_CACHE.ibd # Deleted IDs (in-memory cache)
FTS_0000000000000437_BEING_DELETED.ibd # Documents being purged
FTS_0000000000000437_BEING_DELETED_CACHE.ibd # Being-purged cache
FTS_0000000000000437_00000000000004a2_INDEX_0.ibd # Index shard 0
FTS_0000000000000437_00000000000004a2_INDEX_1.ibd # Index shard 1
...
FTS_0000000000000437_00000000000004a2_INDEX_5.ibd # Index shard 5
The filename encodes the parent table ID and (for index shards) the index ID, both as 16-digit zero-padded hexadecimal strings.
Auxiliary Table Types
CONFIG
The CONFIG table stores FTS configuration parameters as key-value pairs:
| Key | Description |
|---|---|
FTS_SYNCED_DOC_ID | Highest document ID that has been synced to disk |
FTS_TOTAL_DELETED_COUNT | Number of documents marked for deletion |
FTS_TOTAL_WORD_COUNT | Total number of indexed tokens |
FTS_LAST_OPTIMIZED_WORD | Last word processed by OPTIMIZE TABLE |
Index Shards (INDEX_0 through INDEX_5)
The inverted index is partitioned into 6 shards based on the first character of each token. Each shard is a B+Tree table with columns:
| Column | Type | Description |
|---|---|---|
| word | VARCHAR | The indexed token |
| first_doc_id | BIGINT | First document ID containing this token |
| last_doc_id | BIGINT | Last document ID containing this token |
| doc_count | INT | Number of documents containing this token |
| ilist | BLOB | Encoded list of (document_id, position) pairs |
The ilist column is a compressed binary format encoding document IDs and word positions within each document. This is what enables MATCH() AGAINST() queries with relevance ranking.
Deletion Tracking
FTS uses a lazy deletion strategy:
- When a row is deleted from the main table, its document ID is added to DELETED / DELETED_CACHE
- When a row is updated, the old document ID goes to DELETED and the new version is indexed
- The BEING_DELETED tables track documents currently being purged by the background FTS optimize thread
OPTIMIZE TABLEtriggers a full merge and purge of deleted entries
Filename Parsing
IDB Utils can identify and parse FTS auxiliary filenames to extract the table ID, index ID, and file type:
FTS_<table_id_hex>_CONFIG.ibd
FTS_<table_id_hex>_DELETED.ibd
FTS_<table_id_hex>_DELETED_CACHE.ibd
FTS_<table_id_hex>_BEING_DELETED.ibd
FTS_<table_id_hex>_BEING_DELETED_CACHE.ibd
FTS_<table_id_hex>_<index_id_hex>_INDEX_<N>.ibd (N = 0-5)
The is_fts_auxiliary() function tests whether a filename matches this pattern, and parse_fts_filename() extracts the structured metadata.
FTS Behavior Notes
- FTS indexes are only available on InnoDB tables (MyISAM has a separate FTS implementation)
- The auxiliary tables are not visible through
SHOW TABLESbut exist as physical.ibdfiles DROP INDEXon a fulltext index removes all associated auxiliary files- FTS uses its own document ID counter (
FTS_DOC_ID), which is either an explicit column or an implicit hidden column innodb_ft_cache_sizecontrols how much memory is used before flushing tokens to the index shards
Inspecting FTS Tables with inno
FTS auxiliary tables are standard InnoDB tablespaces and can be inspected with any inno subcommand:
# Parse the FTS config table
inno pages -f FTS_0000000000000437_CONFIG.ibd
# Check integrity of an index shard
inno checksum -f FTS_0000000000000437_00000000000004a2_INDEX_0.ibd
# The audit command identifies FTS auxiliary files in directory scans
inno audit -d /var/lib/mysql/mydb/
Source Reference
- FTS filename parsing:
src/innodb/fts.rs--FtsFileType,FtsFileInfo,parse_fts_filename(),is_fts_auxiliary() - MySQL source:
storage/innobase/fts/fts0fts.cc,fts0opt.cc,fts0que.cc
Page Types
Complete table of all InnoDB page types recognized by inno:
| Name | Value | Description | Usage |
|---|---|---|---|
ALLOCATED | 0 | Freshly allocated | Page type field not yet initialized; appears in tablespaces with preallocated but unused extents |
UNDO_LOG | 2 | Undo log | Stores previous values of modified records for MVCC and rollback |
INODE | 3 | File segment inode | Bookkeeping for file segments (collections of extents belonging to an index) |
IBUF_FREE_LIST | 4 | Insert buffer free list | Insert buffer free space management (system tablespace only) |
IBUF_BITMAP | 5 | Insert buffer bitmap | Tracks which pages have buffered writes pending (page 1 of every tablespace) |
SYS | 6 | System internal | Various system tablespace purposes (data dictionary, doublewrite buffer) |
TRX_SYS | 7 | Transaction system header | Transaction system bookkeeping including rollback segment pointers (system tablespace only) |
FSP_HDR | 8 | File space header | Page 0 of each tablespace; contains space ID, size, flags, and extent descriptors |
XDES | 9 | Extent descriptor | Extent descriptor page for each group of 16,384 pages beyond the first |
BLOB | 10 | Uncompressed BLOB | Externally stored column data for columns exceeding the inline limit |
ZBLOB | 11 | First compressed BLOB | First page of a compressed externally stored column |
ZBLOB2 | 12 | Subsequent compressed BLOB | Continuation pages of a compressed externally stored column |
COMPRESSED | 14 | Compressed page | Page stored in compressed format (requires KEY_BLOCK_SIZE) |
ENCRYPTED | 15 | Encrypted page | Page encrypted with tablespace-level encryption (MySQL) |
COMPRESSED_ENCRYPTED | 16 | Compressed + encrypted | Page that is both compressed and encrypted |
ENCRYPTED_RTREE | 17 | Encrypted R-tree | Encrypted spatial index page |
INSTANT / SDI_BLOB | 18 | Vendor-dependent | MariaDB: instant ALTER TABLE metadata; MySQL: SDI overflow data |
LOB_INDEX | 20 | LOB index | Large object index page for new LOB format (MySQL 8.0+) |
LOB_DATA | 21 | LOB data | Large object data page for new LOB format (MySQL 8.0+) |
LOB_FIRST | 22 | LOB first page | First page of a large object in new LOB format (MySQL 8.0+) |
RSEG_ARRAY | 23 | Rollback segment array | Array of rollback segment header page numbers (MySQL 8.0+) |
SDI | 17853 | SDI | Serialized Dictionary Information containing table/index metadata (MySQL 8.0+) |
SDI_BLOB | 17854 | SDI BLOB | SDI overflow data for large metadata records (MySQL 8.0+) |
INDEX | 17855 | B+Tree index | Primary key and secondary index data; the most common page type in data tablespaces |
RTREE | 17856 | R-tree index | Spatial index data for geometry columns |
PAGE_COMPRESSED | 34354 | MariaDB page compression | Page-level compression using zlib, LZ4, LZO, LZMA, bzip2, or Snappy (MariaDB only) |
PAGE_COMPRESSED_ENCRYPTED | 37401 | MariaDB compressed + encrypted | Page-level compression combined with per-page encryption (MariaDB only) |
Notes
Value 18 Ambiguity
Page type value 18 has different meanings depending on the vendor:
- MySQL 8.0+:
SDI_BLOB-- overflow pages for SDI records that exceed a single page - MariaDB:
INSTANT-- metadata for columns added with instant ALTER TABLE
inno resolves this automatically based on detected vendor. If vendor detection is ambiguous, the output will note the dual interpretation.
Page Type Distribution
A typical InnoDB tablespace has the following page type distribution:
- INDEX pages dominate (often 90%+ of all pages) -- these hold the actual table data and index entries
- FSP_HDR appears exactly once (page 0)
- IBUF_BITMAP appears once per tablespace (page 1)
- INODE appears once or twice (page 2, sometimes more for large tablespaces)
- ALLOCATED pages indicate preallocated but unused space
- SDI pages appear in MySQL 8.0+ tablespaces (typically pages 3-4)
Use inno parse to see the page type summary for any tablespace file.
Checksum Algorithms
IDB Utils supports three checksum algorithms used by different InnoDB implementations and versions.
CRC-32C (MySQL 5.7.7+ default)
The default algorithm since MySQL 5.7.7. It computes the XOR of two independent CRC-32C values over non-overlapping byte ranges:
- Range 1: bytes
[4..26)-- covers page number, prev/next pointers, LSN, and page type - Range 2: bytes
[38..page_size-8)-- covers the entire page body
[checksum][--- Range 1 ---][flush_lsn+space_id][------- Range 2 -------][trailer]
0 3 4 25 26 37 38 PS-8 PS
The two CRC-32C values are computed independently (not chained) and XORed together. The result is stored in bytes 0-3 of the page (the FIL header checksum field).
CRC-32C is hardware-accelerated on modern x86 (SSE 4.2) and ARM (CRC extension) processors, making validation fast even for large tablespaces.
Legacy InnoDB (MySQL < 5.7.7)
The original InnoDB checksum algorithm, used as the default before MySQL 5.7.7. It uses the internal ut_fold_ulint_pair function with wrapping u32 arithmetic.
The algorithm processes bytes one at a time (not as u32 words) over the same two byte ranges as CRC-32C:
- Range 1: bytes
[4..26) - Range 2: bytes
[38..page_size-8)
The fold function accumulates a running hash by combining each byte with the previous hash value using wrapping multiplication, addition, and XOR operations. The final result is the sum of the fold values from both ranges.
The legacy checksum is stored in the same location as CRC-32C: bytes 0-3 of the page.
Mixed Mode
MySQL 5.7.x supports innodb_checksum_algorithm values of crc32, innodb, none, and strict_* variants. During migration from legacy to CRC-32C, a tablespace may contain pages with either algorithm. inno checksum validates against both algorithms and reports a page as valid if either checksum matches.
MariaDB full_crc32 (MariaDB 10.5+)
MariaDB 10.5 introduced a simplified checksum format called full_crc32:
- Single CRC-32C computed over bytes
[0..page_size-4)-- covers nearly the entire page - Checksum location: stored in the last 4 bytes of the page, not in the FIL header
[------------- CRC-32C input ---------------][checksum]
0 PS-4 PS
This differs from the MySQL format in two important ways:
- The checksum covers bytes 0-3 (which are skipped in the MySQL CRC-32C algorithm), providing stronger integrity protection
- The checksum is stored at the end of the page rather than the beginning
Detection
MariaDB full_crc32 is detected via FSP flags bit 4 on page 0. When this bit is set, inno automatically uses the full_crc32 algorithm for all pages in the tablespace.
Validation Behavior
inno checksum validates each page using the appropriate algorithm:
- Check FSP flags on page 0 for MariaDB
full_crc32-- if detected, use that for all pages - Otherwise, compute both CRC-32C and Legacy checksums
- A page is valid if its stored checksum matches either computed value
- Pages with all-zero content (empty/allocated pages) are reported separately and not counted as invalid
- LSN consistency is checked independently: the low 32 bits of the header LSN must match the trailer value
Use inno checksum -v for per-page details or inno checksum --json for structured output.
Redo Log Format
InnoDB redo logs record all modifications to tablespace pages, enabling crash recovery. The logs are organized as a sequence of 512-byte blocks.
File Layout
| Block | Purpose |
|---|---|
| 0 | File header |
| 1 | Checkpoint 1 |
| 2 | Reserved |
| 3 | Checkpoint 2 |
| 4+ | Data blocks containing log records |
File Header (Block 0)
| Offset | Size | Field | Description |
|---|---|---|---|
| 0 | 4 | Group ID | Redo log group identifier |
| 4 | 8 | Start LSN | LSN of the first log record in this file |
| 12 | 4 | File number | Sequence number within the redo log group |
| 16 | 32 | Creator string | Server version and vendor (e.g., "MySQL 8.0.32", "Percona XtraDB 8.0.35") |
The creator string is particularly useful for vendor detection -- it identifies whether the redo log was created by MySQL, Percona XtraDB, or MariaDB.
Checkpoint Blocks (Blocks 1 and 3)
Two checkpoint blocks provide redundancy. InnoDB alternates between them, so at least one is always valid after a crash.
| Offset | Size | Field | Description |
|---|---|---|---|
| 0 | 8 | Checkpoint number | Monotonically increasing checkpoint counter |
| 8 | 8 | LSN | LSN up to which all changes are flushed to tablespace files |
| 16 | 4 | Byte offset | Offset within the log file for this checkpoint |
| 20 | 4 | Log buffer size | Size of the log buffer in bytes |
| 24 | 8 | Archived LSN | LSN up to which logs have been archived (0 if archiving disabled) |
The checkpoint with the higher checkpoint number is the most recent. During crash recovery, InnoDB replays all log records from the checkpoint LSN forward.
Data Block Header (14 bytes)
Each data block (block 4 and beyond) starts with a 14-byte header:
| Offset | Size | Field | Description |
|---|---|---|---|
| 0 | 4 | Block number | Sequential block number; bit 31 is the flush flag (1 = first block in a flush batch) |
| 4 | 2 | Data length | Number of bytes of log data in this block (max 494) |
| 6 | 2 | First record group offset | Offset to the first complete log record group starting in this block |
| 8 | 4 | Checkpoint number | Checkpoint number when this block was written |
| 12 | 2 | Padding | Reserved bytes |
Data Block Checksum
Each block ends with a 4-byte CRC-32C checksum:
- Computed over bytes
[0..508)of the block - Stored at bytes
[508..512)
inno log validates block checksums and reports any corruption.
Block Capacity
Each 512-byte block carries at most 494 bytes of log record data:
[header: 14 bytes][log data: up to 494 bytes][checksum: 4 bytes]
0 13 14 507 508 511
Log records can span multiple blocks. The data length field indicates how many bytes of the 494-byte payload contain actual log data (the rest is padding).
File Formats
Legacy Format (MySQL < 8.0.30)
- Files:
ib_logfile0,ib_logfile1(or more, controlled byinnodb_log_files_in_group) - Fixed-size files, pre-allocated at server startup
- Location: MySQL data directory root
New Format (MySQL 8.0.30+)
- Files:
#innodb_redo/#ib_redo*(numbered sequentially) - Dynamic file creation and removal
- Location:
#innodb_redo/subdirectory within the data directory inno logsupports both formats
MLOG Record Types
Redo log records use type codes to identify the operation. Common types include:
| Type | Value | Description |
|---|---|---|
MLOG_1BYTE | 1 | Write 1 byte to a page |
MLOG_2BYTES | 2 | Write 2 bytes to a page |
MLOG_4BYTES | 4 | Write 4 bytes to a page |
MLOG_8BYTES | 8 | Write 8 bytes to a page |
MLOG_REC_INSERT | 9 | Insert a record (non-compact format) |
MLOG_REC_UPDATE_IN_PLACE | 13 | Update a record in place |
MLOG_REC_DELETE | 14 | Delete a record |
MLOG_PAGE_CREATE | 16 | Create a page |
MLOG_UNDO_INSERT | 20 | Insert an undo log record |
MLOG_INIT_FILE_PAGE | 24 | Initialize a file page |
MLOG_COMP_REC_INSERT | 38 | Insert a record (compact format) |
MLOG_COMP_REC_UPDATE_IN_PLACE | 42 | Update a record in place (compact format) |
MLOG_COMP_REC_DELETE | 43 | Delete a record (compact format) |
MLOG_COMP_PAGE_CREATE | 44 | Create a page (compact format) |
The MLOG_COMP_* variants are used by the compact row format (MySQL 5.0+), while the non-compact versions correspond to the older redundant row format.
Usage with inno
# Parse a legacy redo log
inno log -f /var/lib/mysql/ib_logfile0
# Parse a new-format redo log
inno log -f /var/lib/mysql/#innodb_redo/#ib_redo10
# Show specific blocks
inno log -f ib_logfile0 -b 0-10
# Skip empty blocks
inno log -f ib_logfile0 --no-empty
# Verbose output with block checksums
inno log -f ib_logfile0 -v
# JSON output
inno log -f ib_logfile0 --json