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 inno command 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 idb crate 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 .ibd files 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.

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:

PlatformArchitecture
Linuxx86_64
Linuxaarch64
macOSx86_64
macOSaarch64

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

CommandDescription
inno parseParse .ibd file, display page headers and type summary
inno pagesDetailed page structure analysis (INDEX, UNDO, LOB, SDI)
inno dumpHex dump of raw page bytes
inno sdiExtract SDI metadata from MySQL 8.0+ tablespaces
inno schemaExtract schema and reconstruct DDL from tablespace metadata
inno exportExport record data as CSV, JSON, or hex dump
inno infoInspect ibdata1, compare LSNs, query MySQL

Validation & Health

CommandDescription
inno checksumValidate page checksums (CRC-32C, legacy, MariaDB full_crc32)
inno healthPer-index B+Tree health metrics (fill factor, fragmentation, bloat)
inno verifyVerify structural integrity of a tablespace
inno validateValidate tablespace against live MySQL
inno compatCheck upgrade compatibility between MySQL versions
inno auditAudit data directory for integrity, health, or checksum mismatches

Comparison & Monitoring

CommandDescription
inno diffCompare two tablespace files page-by-page
inno watchMonitor a tablespace for page-level changes in real time
inno findSearch a data directory for pages by number
inno tsidList or find tablespace IDs

Recovery & Repair

CommandDescription
inno recoverAssess page-level recoverability and count salvageable records
inno repairRecalculate and fix corrupt page checksums
inno undeleteRecover deleted records from tablespace
inno corruptIntentionally corrupt pages for testing
inno defragDefragment tablespace, reorder INDEX pages
inno transplantCopy specific pages from a donor into a target tablespace
inno simulateSimulate InnoDB crash recovery levels 1-6

Log & Transaction Analysis

CommandDescription
inno logAnalyze InnoDB redo log files
inno undoAnalyze undo tablespace structure
inno binlogAnalyze MySQL binary log files
inno timelineUnified modification timeline from redo, undo, and binary logs

Backup Analysis

CommandDescription
inno backup diffCompare page LSNs between backup and current tablespace
inno backup chainValidate XtraBackup backup chain LSN continuity

Utilities

CommandDescription
inno completionsGenerate shell completions for bash, zsh, fish, powershell

Global Flags

These flags are available on every subcommand:

FlagDefaultDescription
--color <auto|always|never>autoControl colored terminal output. auto enables color when stdout is a terminal.
--format <text|json|csv>textOutput format. Overrides per-subcommand --json flag. CSV support varies by subcommand.
-o, --output <file>stdoutWrite output to a file instead of printing to stdout.
--audit-log <path>noneAppend 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):

FlagDescription
--jsonEmit structured JSON output instead of human-readable text. All subcommands support this.
-v, --verboseShow 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

FlagShortRequiredDefaultDescription
--file <path>-fYes--Path to the InnoDB data file (.ibd).
--page <number>-pNoAll pagesDisplay a specific page number only.
--verbose-vNoOffShow checksum validation and LSN consistency per page.
--no-empty-eNoOffSkip empty/allocated pages (zero checksum, type Allocated).
--json--NoOffOutput in JSON format.
--page-size <size>--NoAuto-detectOverride 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

FlagShortRequiredDefaultDescription
--file <path>-fYes--Path to the InnoDB data file (.ibd).
--page <number>-pNoAll pagesDisplay a specific page number only.
--verbose-vNoOffShow checksum status, FSEG internals, and additional detail.
--show-empty-eNoOffInclude empty/allocated pages in the output.
--list-lNoOffCompact one-line-per-page listing mode.
--type <filter>-tNoAll typesFilter output to pages matching this type name.
--json--NoOffOutput in JSON format.
--page-size <size>--NoAuto-detectOverride 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):

FilterMatches
INDEXINDEX pages (B+Tree nodes)
UNDOUndo log pages
BLOBBLOB, ZBLOB, and ZBLOB2 pages
LOBLOB_INDEX, LOB_DATA, and LOB_FIRST pages
SDISDI and SDI_BLOB pages
COMPRESSED or COMPAll compressed page types
ENCRYPTED or ENCAll encrypted page types
INSTANTINSTANT 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

FlagShortRequiredDefaultDescription
--file <path>-fYes--Path to the InnoDB data file.
--page <number>-pNo0Page number to dump (page mode).
--offset <byte>--No--Absolute byte offset to start dumping (offset mode). Bypasses page mode.
--length <bytes>-lNoPage size (page mode) or 256 (offset mode)Number of bytes to dump.
--raw--NoOffOutput raw binary bytes instead of formatted hex dump.
--page-size <size>--NoAuto-detectOverride page size.
--keyring <path>--No--Path to MySQL keyring file for decrypting encrypted tablespaces.
--decrypt--NoOffDecrypt 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_pair with 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

FlagShortRequiredDefaultDescription
--file <path>-fYes--Path to the InnoDB data file (.ibd).
--verbose-vNoOffShow per-page checksum details (algorithm, stored/calculated values).
--json--NoOffOutput in JSON format.
--page-size <size>--NoAuto-detectOverride 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:

  1. Quick comparison (default): Performs full-page byte equality checks and reports a summary of identical, modified, and file-only page counts.

  2. 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.

  3. 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

FlagShortRequiredDefaultDescription
<file1>--Yes--First InnoDB data file (.ibd). Positional argument.
<file2>--Yes--Second InnoDB data file (.ibd). Positional argument.
--verbose-vNoOffShow per-page header field diffs for modified pages.
--byte-ranges-bNoOffShow exact byte-range diffs for changed pages. Requires -v.
--page <number>-pNoAll pagesCompare a single page only.
--json--NoOffOutput in JSON format.
--page-size <size>--NoAuto-detectOverride 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

FlagShortRequiredDefaultDescription
--file <path>-fYes--Path to the InnoDB data file (.ibd).
--interval <ms>-iNo1000Polling interval in milliseconds.
--verbose-vNoOffShow per-field diffs for changed pages (old LSN, new LSN, delta).
--json--NoOffOutput in NDJSON streaming format (one JSON object per line).
--page-size <size>--NoAuto-detectOverride 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 --verify is 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

FlagShortRequiredDefaultDescription
--file <path>-fYes--Path to the InnoDB data file (.ibd).
--page <number>-pNoRandomPage number to corrupt. If omitted, a random page is chosen.
--bytes <count>-bNo1Number of random bytes to write.
--header-kNoOffTarget the FIL header area (first 38 bytes of the page).
--records-rNoOffTarget the record data area (after page header, before trailer).
--offset <byte>--No--Absolute byte offset to corrupt. Bypasses page calculation.
--verify--NoOffShow before/after checksum comparison to confirm corruption.
--json--NoOffOutput in JSON format.
--page-size <size>--NoAuto-detectOverride 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

FlagShortRequiredDefaultDescription
--file <path>-fYes--Path to the InnoDB data file (.ibd).
--page <number>-pNoAll pagesAnalyze a single page instead of a full scan.
--verbose-vNoOffShow per-page details (type, status, LSN, record count).
--json--NoOffOutput in JSON format.
--force--NoOffExtract records from corrupt pages with valid headers.
--page-size <size>--NoAuto-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

FlagShortRequiredDefaultDescription
--datadir <path>-dYes--MySQL data directory path to search.
--page <number>-pYes--Page number to search for across all tablespace files.
--checksum <value>-cNo--Only match pages with this stored checksum value.
--space-id <id>-sNo--Only match pages in this tablespace (by space ID).
--first--NoOffStop after the first match for faster lookups.
--json--NoOffOutput in JSON format.
--page-size <size>--NoAuto-detectOverride 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 .ibd file on disk.

If neither -l nor -t is specified, all discovered tablespaces are listed (same behavior as -l).

Flags

FlagShortRequiredDefaultDescription
--datadir <path>-dYes--MySQL data directory path to scan.
--list-lNoOffList all tablespace IDs found in the data directory.
--tsid <id>-tNo--Find the tablespace file with this specific space ID.
--json--NoOffOutput in JSON format.
--page-size <size>--NoAuto-detectOverride 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

FlagShortRequiredDefaultDescription
--file <path>-fYes--Path to the InnoDB data file (.ibd).
--pretty-pNoOffPretty-print the extracted JSON with indentation.
--page-size <size>--NoAuto-detectOverride 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_logfile0 and ib_logfile1 files 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

FlagShortRequiredDefaultDescription
--file <path>-fYes--Path to redo log file (ib_logfile0, ib_logfile1, or #ib_redo*).
--blocks <count>-bNoAll data blocksLimit output to the first N data blocks.
--no-empty--NoOffSkip blocks that contain no redo log data.
--verbose-vNoOffDecode and display MLOG record types within each data block.
--json--NoOffOutput 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

FlagShortRequiredDefaultDescription
--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>-dNo/var/lib/mysqlMySQL data directory path. Used by --ibdata and --lsn-check.
--database <name>-DNo--Database name for MySQL query mode.
--table <name>-tNo--Table name for MySQL query mode.
--host <host>--NolocalhostMySQL host for live queries.
--port <port>--No3306MySQL port for live queries.
--user <user>--NorootMySQL user for live queries.
--password <pass>--No--MySQL password for live queries.
--defaults-file <path>--NoAuto-detect .my.cnfPath to a MySQL defaults file.
--json--NoOffOutput in JSON format.
--page-size <size>--NoAuto-detectOverride 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

OptionDescription
-f, --filePath to InnoDB data file
--batchRepair all .ibd files under a directory
-p, --pageRepair only a specific page number
-a, --algorithmChecksum algorithm: auto, crc32c, innodb, full_crc32 (default: auto)
--no-backupSkip creating a .bak backup
--dry-runPreview repairs without modifying files
-v, --verboseShow per-page repair details
--jsonOutput in JSON format
--page-sizeOverride page size
--keyringPath to MySQL keyring file

Behavior

  • Auto-detects the checksum algorithm from page 0 unless --algorithm is specified
  • Creates a .bak backup before modifying the file (unless --no-backup)
  • Only rewrites pages with invalid checksums
  • Batch mode processes files in parallel using rayon
  • Compatible with --audit-log for 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

OptionDescription
-f, --filePath to source InnoDB data file
-o, --outputPath to output file (required)
-v, --verboseShow per-page details
--jsonOutput in JSON format
--page-sizeOverride page size
--keyringPath 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

OptionDescription
donorPath to donor tablespace file (source of pages)
targetPath to target tablespace file (destination)
-p, --pagesPage numbers to transplant (comma-separated)
--no-backupSkip creating a backup of the target
--forceAllow space ID mismatch, corrupt donor pages, and page 0 transplant
--dry-runPreview without modifying the target
-v, --verboseShow per-page details
--jsonOutput in JSON format
--page-sizeOverride page size
--keyringPath 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

OptionDescription
-f, --filePath to InnoDB data file
-v, --verboseShow structured schema breakdown above the DDL
--jsonOutput in JSON format
--page-sizeOverride page size
--keyringPath 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 TABLE DDL 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

OptionDescription
-f, --filePath to InnoDB data file
-p, --pageExport records from a specific page only
--formatOutput format: csv, json, or hex (default: csv)
--where-delete-markInclude only delete-marked records
--system-columnsInclude DB_TRX_ID and DB_ROLL_PTR columns
-v, --verboseShow additional details
--page-sizeOverride page size
--keyringPath 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

OptionDescription
-f, --filePath to InnoDB data file
-v, --verboseShow additional detail (records, empty leaves)
--jsonOutput in JSON format
--prometheusOutput in Prometheus exposition format
--page-sizeOverride page size
--keyringPath to MySQL keyring file
--bloatCompute bloat scores (grade A-F) per index
--cardinalityEstimate cardinality of leading primary key columns
--sample-sizeNumber of leaf pages to sample per index for cardinality (default: 100)

Metrics

MetricDescription
Fill factorAverage, min, and max page utilization (0-100%)
Garbage ratioPercentage of space occupied by deleted records
FragmentationHow out-of-order pages are relative to sequential layout
Tree depthB+Tree depth per index
Page countsTotal, 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

OptionDescription
-d, --datadirMySQL data directory path
--healthShow per-tablespace health metrics
--checksum-mismatchList only pages with checksum mismatches
-v, --verboseShow additional details
--jsonOutput in JSON format
--prometheusOutput in Prometheus exposition format
--page-sizeOverride page size
--keyringPath to MySQL keyring file
--min-fill-factorFilter: show tables with fill factor below threshold (0-100)
--max-fragmentationFilter: show tables with fragmentation above threshold (0-100)
--bloatEnable bloat scoring in health mode
--max-bloat-gradeFilter: show tables with worst bloat grade at or worse than threshold (A-F)
--depthMaximum 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

OptionDescription
-f, --filePath to InnoDB data file (mutually exclusive with --scan)
-s, --scanScan a data directory (mutually exclusive with --file)
-t, --targetTarget MySQL version (e.g., "8.4.0", "9.0.0")
-v, --verboseShow detailed check information
--jsonOutput in JSON format
--page-sizeOverride page size
--keyringPath to MySQL keyring file
--depthMaximum 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

OptionDescription
-f, --filePath to InnoDB data file
-v, --verboseShow per-page findings
--jsonOutput in JSON format
--page-sizeOverride page size
--keyringPath to MySQL keyring file
--redoPath to redo log file for LSN continuity check
--chainVerify backup chain (accepts multiple files)
--backup-metaPath to XtraBackup checkpoint file for LSN cross-reference

Structural Checks

CheckDescription
PageNumberSequencePage numbers match expected file positions
SpaceIdConsistencyAll pages have consistent space IDs
LsnMonotonicityLSNs are non-decreasing
BTreeLevelConsistencyB+Tree levels are valid
PageChainBoundsprev/next pointers within bounds
TrailerLsnMatchTrailer 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

OptionDescription
-d, --datadirPath to MySQL data directory
-D, --databaseDatabase name to filter
-t, --tableDeep-validate a specific table (format: db.table)
--hostMySQL server hostname
--portMySQL server port
-u, --userMySQL username
-p, --passwordMySQL password
--defaults-filePath to MySQL defaults file
-v, --verboseShow detailed output
--jsonOutput in JSON format
--page-sizeOverride page size
--depthMaximum directory recursion depth

Note: MySQL cross-validation requires the mysql feature: 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

OptionDescription
-f, --filePath to InnoDB undo tablespace file (.ibu or .ibd)
-p, --pageShow a specific undo page only
-v, --verboseShow additional detail including undo records
--jsonOutput in JSON format
--page-sizeOverride page size (default: auto-detect)
--keyringPath to MySQL keyring file for encrypted tablespaces
--mmapUse memory-mapped I/O (faster for large files)

Output

Text Mode

Text output shows three sections:

  1. RSEG Array — rollback segment slots from the RSEG array header page, including which slots are active vs empty.

  2. Segment Summary — per-segment state (Active, Cached, ToPurge, ToFree, Prepared), transaction count, and undo log type.

  3. 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

StateDescription
ActiveCurrently in use by an active transaction
CachedAvailable for reuse, contains committed history
ToPurgeMarked for purge by the background purge thread
ToFreeMarked for segment deallocation
PreparedPart 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

OptionDescription
-f, --filePath to MySQL binary log file
-l, --limitMaximum number of events to display
--filter-typeFilter events by type name (e.g. TABLE_MAP, WRITE_ROWS)
-v, --verboseShow additional detail (column types for TABLE_MAP events)
--jsonOutput in JSON format

Output

Text Mode

Text output shows:

  1. Format Description — server version, binlog version, creation timestamp, checksum algorithm.

  2. Event Type Distribution — counts per event type, sorted by frequency.

  3. Table Maps — databases and tables referenced in TABLE_MAP events with column counts.

  4. 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 CodeNameDescription
2QUERY_EVENTSQL statement (DDL or statement-based DML)
15FORMAT_DESCRIPTION_EVENTBinlog file header metadata
16XID_EVENTTransaction commit marker
19TABLE_MAP_EVENTTable schema mapping for row events
30WRITE_ROWS_EVENT_V2Row-based INSERT data
31UPDATE_ROWS_EVENT_V2Row-based UPDATE data (before/after)
32DELETE_ROWS_EVENT_V2Row-based DELETE data
33GTID_LOG_EVENTGlobal 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

OptionDescription
-f, --filePath to the InnoDB tablespace file (.ibd)
--undo-filePath to an undo tablespace (ibdata1 or .ibu) for undo log scanning
--tableFilter by table name
--min-trx-idMinimum transaction ID to include
--confidenceMinimum confidence threshold (0.0–1.0, default: 0.0)
--formatRecord output format: csv, json, sql, hex (default: csv)
--jsonOutput full metadata JSON envelope (overrides --format)
-v, --verboseShow additional recovery detail
-p, --pageRecover from a specific page only
--page-sizeOverride page size (default: auto-detect)
--keyringPath to MySQL keyring file for encrypted tablespaces
--mmapUse 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

OptionDescription
-f, --filePath to InnoDB system tablespace (ibdata1)
-d, --datadirMySQL data directory path
--levelForce recovery level to simulate (1-6, default: simulate all)
-v, --verboseShow per-page details
--jsonOutput in JSON format
--page-sizeOverride page size
--keyringPath to MySQL keyring file

Recovery Levels

LevelNameEffect
1SRV_FORCE_IGNORE_CORRUPTSkip corrupt pages during recovery
2SRV_FORCE_NO_BACKGROUNDPrevent background operations (purge, insert buffer merge)
3SRV_FORCE_NO_TRX_UNDOSkip transaction rollbacks after recovery
4SRV_FORCE_NO_IBUF_MERGESkip insert buffer merge
5SRV_FORCE_NO_UNDO_LOG_SCANSkip undo log scanning
6SRV_FORCE_NO_LOG_REDOSkip 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

OptionDescription
--redo-logPath to InnoDB redo log file
--undo-filePath to undo tablespace file
--binlogPath to MySQL binary log file
-d, --datadirMySQL data directory (resolves table names to space IDs for binlog entries)
-s, --space-idFilter entries by tablespace ID
-p, --pageFilter entries by page number
--tableFilter binlog entries by table name (case-insensitive substring match)
--limitMaximum number of entries to display
-v, --verboseShow additional detail per entry
--jsonOutput in JSON format
--page-sizeOverride page size
--keyringPath 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, or binlog
  • 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 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

OptionDescription
--basePath to base (backup) tablespace file
--currentPath to current tablespace file
-v, --verboseShow per-page change details
--jsonOutput in JSON format
--page-sizeOverride page size
--keyringPath to MySQL keyring file

Page Change States

StatusDescription
UnchangedSame LSN in both files
ModifiedCurrent LSN > base LSN
AddedPage only exists in current file
RemovedPage only exists in base file
RegressedCurrent 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

OptionDescription
-d, --dirDirectory containing backup subdirectories
-v, --verboseShow detailed checkpoint info
--jsonOutput 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_lsn of one backup and from_lsn of 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

ShellDescription
bashBash completions (source in .bashrc or /etc/bash_completion.d/)
zshZsh completions (place in $fpath directory)
fishFish completions (place in ~/.config/fish/completions/)
powershellPowerShell 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

FeatureDefaultDescription
cliYesCLI-specific modules (clap, colored, indicatif, etc.). Disable with default-features = false for library-only usage.
mysqlNoLive MySQL queries via mysql_async + tokio. Implies cli.

Key Entry Points

Type / FunctionModulePurpose
Tablespaceidb::innodb::tablespaceOpen .ibd files, read pages, iterate over all pages
FilHeaderidb::innodb::pageParse the 38-byte FIL header from any InnoDB page
FilTraileridb::innodb::pageParse the 8-byte FIL trailer from any InnoDB page
FspHeaderidb::innodb::pageParse the FSP header from page 0 (space ID, size, flags)
PageTypeidb::innodb::page_typesMap page type codes to names, descriptions, and usage notes
validate_checksumidb::innodb::checksumCRC-32C, legacy InnoDB, and MariaDB full_crc32 validation
validate_lsnidb::innodb::checksumLSN consistency check between header and trailer
find_sdi_pagesidb::innodb::sdiLocate SDI pages in a tablespace
extract_sdi_from_pagesidb::innodb::sdiExtract and decompress SDI metadata records
LogFileidb::innodb::logOpen and parse redo log files
VendorInfoidb::innodb::vendorVendor detection (MySQL, Percona, MariaDB)

Module Overview

The library is organized under idb::innodb:

ModulePurpose
tablespaceFile I/O abstraction, page size auto-detection, page iteration
pageFIL header (38 bytes), FIL trailer (8 bytes), FSP header parsing
page_typesPage type enum mapping u16 codes to names and descriptions
checksumCRC-32C and legacy InnoDB checksum validation
indexINDEX page internals -- B+Tree header, FSEG, system records
recordRow-level record parsing -- compact format, variable-length fields
sdiSDI metadata extraction from MySQL 8.0+ tablespaces
logRedo log file header, checkpoints, and data block parsing
undoUNDO log page header and segment header parsing
lobLarge object page headers (old-style BLOB and MySQL 8.0+ LOB)
compressionCompression algorithm detection and decompression (zlib, LZ4)
encryptionEncryption detection from FSP flags, encryption info parsing
keyringMySQL keyring_file plugin format reader
decryptionAES-256-CBC page decryption using tablespace keys
vendorVendor detection (MySQL, Percona, MariaDB) and format variants
constantsInnoDB 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

MethodReturn TypeDescription
page_size()u32Detected or configured page size in bytes (4096, 8192, 16384, 32768, or 65536)
page_count()u64Total number of pages in the file (file_size / page_size)
file_size()u64File size in bytes
fsp_header()Option<&FspHeader>FSP header from page 0, if parseable
vendor_info()&VendorInfoDetected vendor (MySQL, Percona, or MariaDB with format variant)
encryption_info()Option<&EncryptionInfo>Encryption info from page 0, if present
is_encrypted()boolWhether 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, where page_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 ssize formula). 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

FieldTypeDescription
checksumu32Stored checksum (bytes 0-3)
page_numberu32Page number within the tablespace (bytes 4-7)
prev_pageu32Previous page pointer, 0xFFFFFFFF if unused (bytes 8-11)
next_pageu32Next page pointer, 0xFFFFFFFF if unused (bytes 12-15)
lsnu64LSN of newest modification (bytes 16-23)
page_typePageTypePage type enum (bytes 24-25)
flush_lsnu64Flush LSN, only valid on page 0 of system tablespace (bytes 26-33)
space_idu32Space 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

FieldTypeDescription
checksumu32Old-style checksum (bytes 0-3 of trailer)
lsn_low32u32Low 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

FieldTypeDescription
space_idu32Tablespace space ID
sizeu32Tablespace size in pages
free_limitu32Minimum page number not yet initialized
flagsu32Space flags (encodes page size, compression, encryption info)
frag_n_usedu32Number 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

VariantValueDescription
Allocated0Freshly allocated, not yet initialized
UndoLog2Undo log page
Inode3File segment inode
FspHdr8File space header (page 0)
Xdes9Extent descriptor
Blob10Uncompressed BLOB data
Sdi17853SDI metadata (MySQL 8.0+)
SdiBlob17854SDI BLOB overflow (MySQL 8.0+)
Index17855B+Tree index page
Rtree17856R-tree spatial index
LobIndex20LOB index (MySQL 8.0+)
LobData21LOB data (MySQL 8.0+)
LobFirst22LOB first page (MySQL 8.0+)
Encrypted15Encrypted page
PageCompressed34354MariaDB page-level compression
Instant18MariaDB 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:

  1. All-zeros page: If the entire page is zeroed, it is considered valid with algorithm None.
  2. No-checksum magic: If the stored checksum is 0xDEADBEEF (BUF_NO_CHECKSUM_MAGIC), the page was written with innodb_checksum_algorithm=none and is considered valid.
  3. MariaDB full_crc32: When vendor_info indicates MariaDB full_crc32 format, the full_crc32 algorithm is tried. If it does not match, failure is reported immediately (no fallback to other algorithms).
  4. CRC-32C (tried first for MySQL/Percona): XOR of two independent CRC-32C values over the page data.
  5. 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

FieldTypeDescription
algorithmChecksumAlgorithmThe algorithm that was detected or attempted
validboolWhether the stored checksum matches the calculated value
stored_checksumu32The checksum value stored in the page
calculated_checksumu32The checksum value calculated from the page data

ChecksumAlgorithm

VariantDescription
Crc32cCRC-32C (hardware accelerated, MySQL 5.7.7+ default)
InnoDBLegacy InnoDB checksum (buf_calc_page_new_checksum equivalent)
MariaDbFullCrc32MariaDB full_crc32 (single CRC-32C over entire page minus last 4 bytes)
NoneNo 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:

  1. 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.
  2. 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:

  1. Reads each SDI page and verifies it has page type SDI (17853)
  2. Parses the INDEX page header to confirm it is a leaf page with records
  3. Walks compact-format records on the page
  4. For each ordinary record, extracts the SDI header fields (type, ID, lengths)
  5. If compressed data fits within the page, decompresses it directly
  6. If compressed data spans multiple pages, follows the next-page chain to collect all compressed bytes before decompression

SdiRecord

FieldTypeDescription
sdi_typeu32SDI type: 1 = Table, 2 = Tablespace
sdi_idu64SDI object ID
uncompressed_lenu32Expected uncompressed data length in bytes
compressed_lenu32Compressed data length in bytes
dataStringDecompressed 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

FieldTypeDescription
group_idu32Log group ID
start_lsnu64Start LSN of this log file
file_nou32File number within the log group
created_byStringMySQL 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

FieldTypeDescription
numberu64Checkpoint sequence number
lsnu64LSN at the time of this checkpoint
offsetu32Byte offset of the checkpoint within the log file
buf_sizeu32Log buffer size at checkpoint time
archived_lsnu64LSN 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

FieldTypeDescription
block_nou32Block number (with flush bit masked out)
flush_flagboolWhether this block was the first in a flush batch (bit 31 of raw block number)
data_lenu16Number of bytes of log data in this block (including the 14-byte header)
first_rec_groupu16Offset of the first log record group starting in this block
checkpoint_nou32Checkpoint 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

ValueVariantName
1Mlog1ByteMLOG_1BYTE
2Mlog2BytesMLOG_2BYTES
4Mlog4BytesMLOG_4BYTES
8Mlog8BytesMLOG_8BYTES
9MlogRecInsertMLOG_REC_INSERT
13MlogRecUpdateMLOG_REC_UPDATE_IN_PLACE
14MlogRecDeleteMLOG_REC_DELETE
19MlogPageCreateMLOG_PAGE_CREATE
22MlogUndoInitMLOG_UNDO_INIT
30MlogInitFilePageMLOG_INIT_FILE_PAGE
31MlogWriteStringMLOG_WRITE_STRING
32MlogMultiRecEndMLOG_MULTI_REC_END
36MlogCompRecInsertMLOG_COMP_REC_INSERT
44MlogCompPageReorganizeMLOG_COMP_PAGE_REORGANIZE
52MlogFileCreateMLOG_FILE_CREATE

Unknown type codes are represented as MlogRecordType::Unknown(value).

Block Count Methods

MethodDescription
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:

ConstantValueDescription
LOG_BLOCK_SIZE512Size of a redo log block in bytes
LOG_BLOCK_HDR_SIZE14Size of the block header
LOG_BLOCK_TRL_SIZE4Size of the block trailer
LOG_BLOCK_FLUSH_BIT_MASK0x80000000Bitmask for the flush flag in block number
LOG_BLOCK_CHECKSUM_OFFSET508Byte offset of CRC-32C checksum within a block
LOG_FILE_HDR_BLOCKS4Number 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-pack to WebAssembly
  • Frontend: Vite + Tailwind CSS single-page application
  • Source: web/ directory in the repository
  • Bindings: src/wasm.rs provides thin wrappers over the library API, returning JSON strings via wasm-bindgen

Limitations

  • No filesystem access — files must be loaded via drag-and-drop or file picker
  • No encryption support — the --keyring option is CLI-only
  • Large files may be slow to process depending on browser memory limits
  • No watch mode — 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 files
  • ib_logfile* / #ib_redo* — InnoDB redo log files
  • ibdata1 — 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+ .ibd file 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

TypeValueDescription
ALLOCATED0Freshly allocated, type field not yet initialized
UNDO_LOG2Stores previous values of modified records
INODE3File segment inode bookkeeping
TRX_SYS7Transaction system header (system tablespace only)
FSP_HDR8File space header, always page 0
XDES9Extent descriptor for 16,384-page blocks
BLOB10Externally stored column data
SDI17853Serialized Dictionary Information (MySQL 8.0+)
INDEX17855B+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_pair hash 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:

BlockPurpose
0File header (group_id, start_lsn, file_no, creator string)
1Checkpoint 1
2Reserved
3Checkpoint 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

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:

  1. Master key -- stored in the keyring file, identified by a key ID embedded in the tablespace
  2. Per-tablespace key and IV -- stored on page 0 of the tablespace, encrypted with the master key
  3. 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 jq works without interference.
  • The watch subcommand with --json emits newline-delimited JSON (NDJSON), one object per poll cycle. Use jq -c for compact processing.
  • Optional fields are omitted from JSON output rather than set to null. Use jq's // operator to provide defaults: .field // "default".
  • Combine --json with -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

FeatureMySQLPercona XtraDBMariaDB
DetectionDefaultRedo log created_byFSP flags bit 4/16
ChecksumsCRC-32C, LegacySame as MySQLfull_crc32 (10.5+), CRC-32C
Page TypesAll standardSame as MySQL+PageCompressed, +Instant
Compressionzlib, LZ4Same as MySQLzlib, LZ4, LZO*, LZMA*, bzip2*, Snappy*
EncryptionTablespace-levelSame as MySQLPer-page only
SDIYes (8.0+)Yes (8.0+)N/A
Redo LogsFull parsingFull parsingHeader + checkpoints only

*detection only, not decompressed

Vendor Detection Logic

inno auto-detects the vendor from on-disk metadata. The detection priority is:

  1. FSP flags bit 4 set -- MariaDB full_crc32 format (10.5+). This is unambiguous.
  2. FSP flags bit 16 set, bits 11-14 zero -- MariaDB original format.
  3. Redo log created_by string -- contains "Percona" for Percona XtraDB, otherwise MySQL.
  4. 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

VersionTablespace FilesRedo Log FilesSDI
MySQL 5.7.ibdib_logfile0, ib_logfile1N/A
MySQL 8.0.ibdib_logfile0, ib_logfile1Yes
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

CheckDescriptionSeverity
page_sizeNon-default page sizes (4K/8K/32K/64K) require MySQL 5.7.6+Error
sdiSDI metadata is required for MySQL 8.0+ but absent in pre-8.0 filesError
encryptionTablespace-level encryption requires MySQL 5.7.11+Error
vendorMariaDB tablespaces are incompatible with MySQL (divergent formats)Error
row_formatCOMPRESSED is deprecated in MySQL 8.4+, REDUNDANT in 9.0+Warning
compressionPage 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=COMPRESSED is 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=REDUNDANT is deprecated in 9.0+.
  • ROW_FORMAT=COMPRESSED deprecation 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:

CheckDescription
PageNumberSequenceThe page number stored at offset 4 matches its expected position in the file
SpaceIdConsistencyAll pages have the same space ID as page 0
LsnMonotonicityLSNs are non-decreasing across pages (within tolerance)
BTreeLevelConsistencyINDEX leaf pages have level 0, internal pages have level > 0
PageChainBoundsprev/next pointers are within file bounds; first page has prev = FIL_NULL
TrailerLsnMatchThe 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 checksum handles).
  • Monitoring: Include in periodic health checks alongside inno audit and inno 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 mysql feature. 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 .ibd file 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

FindingDescription
Orphan files.ibd files on disk that have no matching entry in MySQL's tablespace registry
Missing filesTablespaces registered in MySQL but with no corresponding .ibd file on disk
Space ID mismatchesFiles where the on-disk space ID differs from MySQL's recorded space ID
Invalid index rootsRoot 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

OptionDescription
--hostMySQL server hostname or IP
--portMySQL server port (default: 3306)
--user / -uMySQL username
--password / -pMySQL password
--defaults-filePath 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

TypeDecodingNotes
TINYINT, SMALLINT, MEDIUMINT, INT, BIGINTFullSigned and unsigned; high-bit XOR for InnoDB ordering
FLOATFullIEEE 754 single-precision
DOUBLEFullIEEE 754 double-precision
DECIMALFullInnoDB packed BCD encoding
DATEFull3-byte packed format (day + month32 + year16*32)
DATETIME / DATETIME2Full5-byte packed bit-field + FSP bytes
TIMESTAMP / TIMESTAMP2Full4-byte UTC epoch + FSP bytes
TIME / TIME2Full3-byte offset encoding + FSP bytes
YEARFull1-byte, offset from 1900
CHAR, VARCHARFullLength-prefixed with character set
ENUMFull1-2 byte index into element list from SDI
SETFull1-8 byte bitmask into element list from SDI
TEXT, BLOBPartialInline data as hex; off-page references noted
JSONPartialInline data as hex; off-page references noted
GEOMETRYPartialRaw WKB bytes as hex
OtherHex fallbackUnknown 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 0x800000 to 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:

SubcommandEvents Logged
inno repairpage_write per repaired page, backup_created
inno repair --batchSame as above, per file in the batch
inno corruptpage_write per corrupted page
inno defragfile_write for the output file
inno transplantpage_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

EventDescriptionFields
session_startBeginning of a write sessionsession_id, args
page_writeA page was modifiedsession_id, file, page, algorithm
file_writeA new file was createdsession_id, path
backup_createdA backup file was createdsession_id, path
session_endEnd of a write sessionsession_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

Feature5.65.78.08.49.0
Default page size16K16K16K16K16K
Non-default page sizes (4K/8K/32K/64K)-5.7.6+YesYesYes
CRC-32C checksums-5.7.7+YesYesYes
Legacy InnoDB checksumsYesYesYesYesYes
SDI metadata--YesYesYes
Tablespace-level encryption-5.7.11+YesYesYes
ROW_FORMAT=COMPRESSEDYesYesYesDeprecatedDeprecated
ROW_FORMAT=REDUNDANTYesYesYesYesDeprecated
ROW_FORMAT=DYNAMICYesDefaultDefaultDefaultDefault
Instant ADD COLUMN--8.0.12+YesYes
Instant ADD COLUMN v2--8.0.29+YesYes
Redo log formatPre-8.0Pre-8.0Pre-8.0.30 / 8.0.30+8.0.30+8.0.30+

Checksum Algorithms by Version

MySQL

Version RangeAlgorithmDetection
< 5.7.7Legacy InnoDBut_fold_ulint_pair byte-by-byte
5.7.7+CRC-32CXOR of two CRC32c ranges
All versionsPage 0May use either algorithm

MariaDB

Version RangeAlgorithmDetection
< 10.5Legacy InnoDB or CRC-32CSame as MySQL
10.5+full_crc32Single 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

VersionFormatFiles
< 8.0.30Legacyib_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 schema falls back to index structure inference
  • inno export outputs hex-only data
  • inno compat reports 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:

FieldDescription
Chain typeold_blob, lob, or zlob
First pagePage number of the chain start
Total data lengthSum of data across all pages in the chain
Page countNumber of pages in the chain
PagesList 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-type flag 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 PatternPurpose
FTS_<table_id>_CONFIG.ibdFTS configuration and state
FTS_<table_id>_<index_id>_INDEX_<N>.ibdInverted index partitions (0-5)
FTS_<table_id>_DELETE.ibdDeleted document IDs
FTS_<table_id>_BEING_DELETED.ibdDocuments being removed from index
FTS_<table_id>_DELETE_CACHE.ibdCached deletes pending merge
FTS_<table_id>_BEING_DELETED_CACHE.ibdCached 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:

  1. Level 1 — Try first. Skips corrupt pages but preserves all recoverable data. Safe for most single-page corruption.

  2. Level 2 — If level 1 hangs on background operations. Prevents purge and insert buffer merge.

  3. Level 3 — If crash recovery itself fails. Skips transaction rollback, which means uncommitted transactions remain visible.

  4. Level 4 — If insert buffer corruption is suspected. Skips merge operations entirely.

  5. Level 5 — If undo log is corrupted. Skips undo scanning, meaning the undo tablespace is treated as empty.

  6. 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

  1. Assess: Run inno simulate to understand the damage
  2. Back up: Copy all data files before attempting recovery
  3. Recover: Set innodb_force_recovery to the lowest effective level
  4. Export: Use mysqldump to export all data
  5. Rebuild: Restore from the dump into a fresh MySQL instance
  6. Verify: Run inno verify and inno audit on the new data directory
  • inno verify — structural integrity verification
  • inno recover — page-level recoverability assessment
  • inno checksum — checksum validation
  • inno 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)
  • inno verify — structural integrity verification
  • inno checksum — page-level checksum validation
  • inno 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:

ComponentWeightDescription
Fill factor deficit30%1 - avg_fill_factor — lower fill means more wasted space
Garbage ratio25%Average garbage bytes / usable page space
Fragmentation25%Ratio of non-sequential leaf page transitions
Delete-mark ratio20%Delete-marked records / total walked records

The final score ranges from 0.0 (no bloat) to 1.0 (maximum bloat).

Grade Thresholds

GradeScore RangeInterpretation
A< 0.10Healthy — no action needed
B0.10 - 0.19Minor bloat — monitor but no immediate action
C0.20 - 0.34Moderate — consider OPTIMIZE TABLE during maintenance window
D0.35 - 0.49Significant — schedule optimization soon
F>= 0.50Critical — 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.

  • inno health — per-index B+Tree health metrics
  • inno audit --health — directory-wide health scanning
  • inno 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:

OffsetSizeFieldDescription
38+04max_sizeMaximum number of undo pages this segment can use
38+44history_sizeNumber of committed transactions in the history list
38+244096slots[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

OffsetSizeFieldDescription
38+02page_type1 = INSERT, 2 = UPDATE
38+22startOffset of the first undo record
38+42freeOffset 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:

OffsetSizeFieldDescription
56+02stateSegment state (see below)
56+22last_logOffset of the most recent undo log header

Segment states:

ValueStateMeaning
1ACTIVETransaction is still running
2CACHEDSegment is cached for reuse
3TO_FREEInsert undo, safe to free after purge
4TO_PURGEUpdate undo, retained for MVCC reads
5PREPAREDTwo-phase commit, prepared but not yet committed

Undo Log Header

Each undo log within a segment starts with a 34-byte header:

OffsetSizeFieldDescription
08trx_idTransaction ID that created this undo log
88trx_noTransaction serial number (commit order)
162del_marksNon-zero if delete-mark records exist
182log_startOffset of the first undo record
201xid_existsNon-zero if XID info follows (distributed transactions)
211dict_transNon-zero if this is a DDL transaction
228table_idTable ID (for insert undo logs)
302next_logOffset of the next undo log header (0 if last)
322prev_logOffset of the previous undo log header (0 if first)

Undo Record Types

Each undo record begins with a type byte that encodes the operation:

TypeNameDescription
11INSERT_RECUndo for an INSERT -- stores the inserted PK for rollback deletion
12UPD_EXIST_RECUndo for an in-place UPDATE -- stores old column values
13UPD_DEL_RECUndo for an UPDATE that was implemented as delete + insert
14DEL_MARK_RECUndo 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_id is a fixed 6-byte big-endian value (written by mach_write_to_6 in MySQL source), not InnoDB compressed format
  • roll_ptr is always 7 bytes
  • The update vector (changed column count + old values) comes before the PK fields
  • table_id uses 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 undo subcommand
  • 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, or LONGBLOB value
  • 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:

TypeValueDescription
BLOB10Uncompressed overflow data
ZBLOB11First page of a compressed BLOB chain
ZBLOB212Subsequent 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):

OffsetSizeFieldDescription
04part_lenNumber of data bytes stored on this page
44next_page_noNext 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:

TypeValueDescription
LOB_FIRST22First page of an uncompressed LOB
LOB_DATA23Data page in an uncompressed LOB
LOB_INDEX24Index page linking LOB data pages
ZLOB_FIRST25First page of a compressed LOB
ZLOB_DATA26Data page in a compressed LOB
ZLOB_FRAG27Fragment page for small compressed LOBs
ZLOB_FRAG_ENTRY28Fragment entry index page
ZLOB_INDEX29Index page for compressed LOB

LOB First Page Header (12 bytes)

The first page of a new-style LOB contains metadata at FIL_PAGE_DATA:

OffsetSizeFieldDescription
01versionLOB format version
11flagsLOB flags
24data_lenTotal uncompressed data length
66trx_idTransaction 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:

OffsetSizeFieldDescription
06prev_nodePrevious entry (page_no + offset)
66next_nodeNext entry (page_no + offset)
121versionsNumber of versions of this entry
146trx_idTransaction that created this chunk
204trx_undo_noUndo record number
244page_noPage containing the data for this chunk
284data_lenLength of data on the referenced page
324lob_versionLOB 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:

OffsetSizeFieldDescription
01versionLOB format version
14data_lenBytes of data stored on this page
56trx_idTransaction 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:

OffsetSizeFieldDescription
04timestampSeconds since Unix epoch when the event was created
41type_codeEvent type identifier (see table below)
54server_idOriginating server's server_id value
94event_lengthTotal event size including header, payload, and checksum
134next_positionAbsolute file offset of the next event
172flagsEvent 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:

CodeNameDescription
2QUERY_EVENTSQL statement execution
4ROTATE_EVENTPoints to the next binlog file
15FORMAT_DESCRIPTION_EVENTBinlog metadata (always first real event)
16XID_EVENTTransaction commit (XA transaction ID)
19TABLE_MAP_EVENTMaps table ID to schema/table name
30WRITE_ROWS_EVENTRow insert (row-based replication)
31UPDATE_ROWS_EVENTRow update (row-based replication)
32DELETE_ROWS_EVENTRow delete (row-based replication)
33GTID_LOG_EVENTGlobal Transaction ID
39PARTIAL_UPDATE_ROWS_EVENTPartial JSON update (MySQL 8.0+)
40TRANSACTION_PAYLOAD_EVENTCompressed 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:

OffsetSizeFieldDescription
02binlog_versionFormat version (4 for all modern MySQL)
250server_versionNull-padded ASCII server version string
524create_timestampTimestamp when the binlog was created
561header_lengthCommon header length (always 19 for v4)
57Npost_header_lengthsArray 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:

OffsetSizeFieldDescription
08positionStart position in the next binlog file
8NfilenameName 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:

OffsetSizeFieldDescription
08min_xMinimum X coordinate (longitude)
88min_yMinimum Y coordinate (latitude)
168max_xMaximum X coordinate
248max_yMaximum 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:

ComponentSizeDescription
Record header5 bytesCompact format record header
MBR key32 bytesMinimum Bounding Rectangle
Child page / row pointer4+ bytesChild page number (non-leaf) or clustered index key (leaf)

Spatial Index Algorithm

InnoDB's R-Tree uses R*-Tree insertion heuristics:

  1. Search: Traverse from root, following nodes whose MBR overlaps the query rectangle
  2. Insert: Choose the subtree requiring the least MBR enlargement; reinsert overflowing entries rather than splitting immediately
  3. 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:

KeyDescription
FTS_SYNCED_DOC_IDHighest document ID that has been synced to disk
FTS_TOTAL_DELETED_COUNTNumber of documents marked for deletion
FTS_TOTAL_WORD_COUNTTotal number of indexed tokens
FTS_LAST_OPTIMIZED_WORDLast 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:

ColumnTypeDescription
wordVARCHARThe indexed token
first_doc_idBIGINTFirst document ID containing this token
last_doc_idBIGINTLast document ID containing this token
doc_countINTNumber of documents containing this token
ilistBLOBEncoded 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:

  1. When a row is deleted from the main table, its document ID is added to DELETED / DELETED_CACHE
  2. When a row is updated, the old document ID goes to DELETED and the new version is indexed
  3. The BEING_DELETED tables track documents currently being purged by the background FTS optimize thread
  4. OPTIMIZE TABLE triggers 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 TABLES but exist as physical .ibd files
  • DROP INDEX on 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_size controls 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:

NameValueDescriptionUsage
ALLOCATED0Freshly allocatedPage type field not yet initialized; appears in tablespaces with preallocated but unused extents
UNDO_LOG2Undo logStores previous values of modified records for MVCC and rollback
INODE3File segment inodeBookkeeping for file segments (collections of extents belonging to an index)
IBUF_FREE_LIST4Insert buffer free listInsert buffer free space management (system tablespace only)
IBUF_BITMAP5Insert buffer bitmapTracks which pages have buffered writes pending (page 1 of every tablespace)
SYS6System internalVarious system tablespace purposes (data dictionary, doublewrite buffer)
TRX_SYS7Transaction system headerTransaction system bookkeeping including rollback segment pointers (system tablespace only)
FSP_HDR8File space headerPage 0 of each tablespace; contains space ID, size, flags, and extent descriptors
XDES9Extent descriptorExtent descriptor page for each group of 16,384 pages beyond the first
BLOB10Uncompressed BLOBExternally stored column data for columns exceeding the inline limit
ZBLOB11First compressed BLOBFirst page of a compressed externally stored column
ZBLOB212Subsequent compressed BLOBContinuation pages of a compressed externally stored column
COMPRESSED14Compressed pagePage stored in compressed format (requires KEY_BLOCK_SIZE)
ENCRYPTED15Encrypted pagePage encrypted with tablespace-level encryption (MySQL)
COMPRESSED_ENCRYPTED16Compressed + encryptedPage that is both compressed and encrypted
ENCRYPTED_RTREE17Encrypted R-treeEncrypted spatial index page
INSTANT / SDI_BLOB18Vendor-dependentMariaDB: instant ALTER TABLE metadata; MySQL: SDI overflow data
LOB_INDEX20LOB indexLarge object index page for new LOB format (MySQL 8.0+)
LOB_DATA21LOB dataLarge object data page for new LOB format (MySQL 8.0+)
LOB_FIRST22LOB first pageFirst page of a large object in new LOB format (MySQL 8.0+)
RSEG_ARRAY23Rollback segment arrayArray of rollback segment header page numbers (MySQL 8.0+)
SDI17853SDISerialized Dictionary Information containing table/index metadata (MySQL 8.0+)
SDI_BLOB17854SDI BLOBSDI overflow data for large metadata records (MySQL 8.0+)
INDEX17855B+Tree indexPrimary key and secondary index data; the most common page type in data tablespaces
RTREE17856R-tree indexSpatial index data for geometry columns
PAGE_COMPRESSED34354MariaDB page compressionPage-level compression using zlib, LZ4, LZO, LZMA, bzip2, or Snappy (MariaDB only)
PAGE_COMPRESSED_ENCRYPTED37401MariaDB compressed + encryptedPage-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:

  1. The checksum covers bytes 0-3 (which are skipped in the MySQL CRC-32C algorithm), providing stronger integrity protection
  2. 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:

  1. Check FSP flags on page 0 for MariaDB full_crc32 -- if detected, use that for all pages
  2. Otherwise, compute both CRC-32C and Legacy checksums
  3. A page is valid if its stored checksum matches either computed value
  4. Pages with all-zero content (empty/allocated pages) are reported separately and not counted as invalid
  5. 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

BlockPurpose
0File header
1Checkpoint 1
2Reserved
3Checkpoint 2
4+Data blocks containing log records

File Header (Block 0)

OffsetSizeFieldDescription
04Group IDRedo log group identifier
48Start LSNLSN of the first log record in this file
124File numberSequence number within the redo log group
1632Creator stringServer 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.

OffsetSizeFieldDescription
08Checkpoint numberMonotonically increasing checkpoint counter
88LSNLSN up to which all changes are flushed to tablespace files
164Byte offsetOffset within the log file for this checkpoint
204Log buffer sizeSize of the log buffer in bytes
248Archived LSNLSN 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:

OffsetSizeFieldDescription
04Block numberSequential block number; bit 31 is the flush flag (1 = first block in a flush batch)
42Data lengthNumber of bytes of log data in this block (max 494)
62First record group offsetOffset to the first complete log record group starting in this block
84Checkpoint numberCheckpoint number when this block was written
122PaddingReserved 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 by innodb_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 log supports both formats

MLOG Record Types

Redo log records use type codes to identify the operation. Common types include:

TypeValueDescription
MLOG_1BYTE1Write 1 byte to a page
MLOG_2BYTES2Write 2 bytes to a page
MLOG_4BYTES4Write 4 bytes to a page
MLOG_8BYTES8Write 8 bytes to a page
MLOG_REC_INSERT9Insert a record (non-compact format)
MLOG_REC_UPDATE_IN_PLACE13Update a record in place
MLOG_REC_DELETE14Delete a record
MLOG_PAGE_CREATE16Create a page
MLOG_UNDO_INSERT20Insert an undo log record
MLOG_INIT_FILE_PAGE24Initialize a file page
MLOG_COMP_REC_INSERT38Insert a record (compact format)
MLOG_COMP_REC_UPDATE_IN_PLACE42Update a record in place (compact format)
MLOG_COMP_REC_DELETE43Delete a record (compact format)
MLOG_COMP_PAGE_CREATE44Create 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