DBTrail

Recovery

Undo a wrong DELETE, UPDATE or INSERT on specific rows. DBTrail writes the SQL; you review it and apply it.

Row-level undo: a handful of rows is wrong, you put those back. Whole tables or the whole database at a moment: Restore tables to a moment.

Undo flips each change. A DELETE is undone with an INSERT from the before image, an UPDATE with an UPDATE back to the before image, an INSERT with a DELETE matching the after image. DBTrail writes the SQL newest change first, wrapped in BEGIN and COMMIT, and you review and apply it; DBTrail never runs it against your database.

Three ways in

Three doors to the same engine: the Restore view in the web interface (recommended), Claude in plain English through the recover tool, and the bintrail recover CLI for many keys, key ranges and GTID transactions.
The Restore view after Undo on one change: a banner naming it (an UPDATE on shop.orders, pk 26), the schema, table and PK filters, the row-at-a-point-in-time section, a drawing of the row now and after the undo (status goes from delivered back to paid), and the generated reversal.sql with Copy and Download.
  • Web interface → Restore. Filter by schema, table, key and time; preview before and after; copy or download the script. With a backup configured it also shows the row at that moment and its history. Hours rotated out with no archive are flagged; an incomplete undo is never shown as complete.
  • Claude. The recover tool, same filters. Setup: Connect Claude.
  • CLI. bintrail recover adds multiple keys, per-key limits, column matching and whole transactions by GTID. Reference. Runbooks: POST /api/recover with the access token.
# every change to orders 1000-1999 since 09:00, as a script to review
bintrail recover --index-dsn "$IDX" --schema shop --table orders \
  --pk-min 1000 --pk-max 1999 --since "2026-09-14 09:00:00" --dry-run

Key ranges (--pk-min, --pk-max, either alone): one integer key column only. Composite, DECIMAL, string or binary keys, and PostgreSQL-sourced indexes, are refused. Only plain decimal spelling matches (007 never does). It scans the partitions your time filters keep, so pair it with --since and --until.

A row at a moment

With a backup configured, Restore shows one row as it was at any instant, with its history above the undo form. Three possible answers: the row's state, deleted as of that time, or no baseline row for that PK. A gap between the backup and that instant is an error, never a wrong answer.

Foreign keys

  • Undo runs newest first, so parents come back before their children, as long as every related delete is inside your filters.
  • A parent deleted outside the window makes the INSERTs fail on the foreign key. Widen the window or recover the parent first.

MySQL 8.x CASCADE limitation

ON DELETE CASCADE before MySQL 9.6. The binlog sees only DELETE customer 5; its orders are deleted inside InnoDB and never logged, so a plain undo brings back only the customer. The Restore view inserts the customer and rebuilds orders 11 and 12 from the foreign keys, listing what it restored in a CASCADE banner.

Before MySQL 9.6, cascaded child changes are not in the binlog

The Events view and a plain undo cannot see them. Restore's cascade repair and bintrail recover-cascade rebuild them from the foreign keys, for ON DELETE CASCADE and ON DELETE SET NULL. + Add server warns about CASCADE constraints with the ALTER TABLE to copy. When an undo holds a delete or key update on a table with such children, the MCP recover tool warns and points at recover_cascade, and a schema-wide undo in the web interface warns and says to undo the parent table on its own. Workaround: ON DELETE RESTRICT and delete children explicitly. MySQL 9.6+ logs cascades.

Before you apply

  1. Read the SQL. The data may have changed since the original event.
  2. Narrow the filters: table, key, time.
  3. Large batches: run in staging first.
  4. Roll back if unsure. The script is wrapped in BEGIN/COMMIT.

Access control

The Access profiles settings page: a drawing across the top (the pii flag on shop.customers.email, the marketing profile that denies pii, so a query sees email blank), then a Flags panel with that flag, a Profiles panel with the marketing profile and its one rule, and a Rules panel where marketing may not see pii, each with the form that adds another entry.
  • Settings → Access profiles: flag a table or column (pii), define a profile, add an allow or deny rule. Query and recovery output withholds or redacts what the profile denies. CLI: bintrail flag, bintrail profile, bintrail access, then --profile.
  • Names are trimmed; a spelling that differs from an existing one only by case or accents is refused.
  • The page is refused while a profile is active on your session.

On this page