Skip to content

Guide

MySQL schema change history without DDL triggers

MySQL / schema changes / binlog / schema drift / auditing

MySQL has no DDL triggers. How to find who changed a table and when: binary log, general log, audit plugins, information_schema and schema snapshots.

People coming from SQL Server or PostgreSQL usually search for "DDL trigger MySQL" first, and the answer is short: there is no such thing. MySQL triggers fire on INSERT, UPDATE and DELETE against a table, never on CREATE, ALTER or DROP. So the history of schema changes has to come from somewhere outside the trigger system, and each source answers a slightly different question.

The binary log already has most of it

If binary logging is on, which it is by default in MySQL 8 and on any server with replicas or point in time recovery, every DDL statement is already recorded. DDL is always written as a statement, even when binlog_format = ROW, so it is readable:

mysqlbinlog --base64-output=DECODE-ROWS --verbose binlog.000412 \
  | grep -iE "^(create|alter|drop|rename) " -B 6

The lines just above each statement carry the timestamp and the thread id. The binlog does not record the user name, so connecting a change to a person means matching the thread id against something else, such as the general log or an audit plugin. Retention follows binlog_expire_logs_seconds, which defaults to 30 days in MySQL 8.0, and on Amazon RDS it is set separately through mysql.rds_set_configuration.

The general query log

general_log = ON records every statement from every connection with the user and host. It answers "who" directly and it is far too heavy to leave on for a busy production server. It is useful for a short investigation window, not as a history.

Audit plugins

The audit plugin route gives a structured record of who ran which DDL and when, with filters so only schema changes are kept. The options depend on the distribution: MySQL Enterprise Audit on MySQL Enterprise Edition, the audit log plugin on Percona Server, server_audit on MariaDB, and the provider's audit option on managed services. This is what an auditor will ask for, and it is the right tool when the question is accountability.

information_schema timestamps

information_schema.TABLES has CREATE_TIME and UPDATE_TIME, and it is tempting to read them as a change log. They do not work that way. UPDATE_TIME tracks data changes rather than schema changes, and CREATE_TIME changes on an ALTER TABLE that rebuilds the table but not on one that runs in place. They can hint that something happened recently and cannot say what.

Schema snapshots

A snapshot records state rather than events: read the catalog on a schedule, keep the description of every table, column, index, view, routine and trigger, and compare it with the previous one. The homemade version is mysqldump --no-data --routines --triggers into a file every night and a diff against yesterday, which works for one schema and grows noisy as the number of databases rises, because AUTO_INCREMENT values and definer clauses change the dump without changing the schema.

Snapshots cannot say who made a change, and they miss changes made and reverted between two scans. They also need no SUPER, no plugin and no extra logging on the server, and they behave the same on self-hosted MySQL, RDS and Aurora.

How Taavik keeps MySQL schema history

Taavik's schema change detection uses snapshots. An agent inside the network reads information_schema with a read-only login every 6, 12 or 24 hours or weekly, with daily as the most frequent option on the free plan, and the workspace compares each scan with the previous one object by object, so AUTO_INCREMENT counters and dump formatting never show up as changes.

Renamed tables are matched by their columns and reported as renames. Each change gets a severity: a dropped column, a changed type or a nullability flip is likely breaking, a modified view, routine or trigger needs attention, a new object or a comment change is informational. Alerts send the changes that match a rule to Slack, email or a webhook, and the generated documentation keeps a revision with a diff for each one.

MySQL does not expose index and trigger definitions as DDL, so for those objects the agent rebuilds an equivalent statement from the catalog. The comparison is exact, and the displayed SQL is a reconstruction rather than the original text.

The combination that covers both questions is the binlog or an audit plugin for who ran the statement, and snapshots for a history that outlives log retention.

The same question on other engines: PostgreSQL, SQL Server and Azure SQL Database.