Migrations

MySQL ALTER TABLE Without Downtime: INSTANT, INPLACE, gh-ost or pt-osc

Adding a column to a 2 TB table can take a fraction of a second or freeze the application for minutes. Which one you get depends on the operation, the algorithm and one long-running transaction you did not know about.

· 17 min read · AG Data Team

Running a MySQL ALTER TABLE without downtime on a large table is mostly a question of picking the right method before you press Enter. Some changes are pure metadata and finish instantly even on terabytes. Others rebuild the whole table, need as much free disk as the table itself, and leave every replica behind for hours. And any of them, even the instant ones, can freeze the application if a single forgotten transaction holds the table open. Here is how to tell which case you are in and what to do about it.

Three algorithms, very different costs

InnoDB has three ways to run an ALTER TABLE. The online DDL operations table in the MySQL manual says which ones each change supports:

Always state the algorithm and lock level explicitly, for example ALGORITHM=INSTANT or ALGORITHM=INPLACE, LOCK=NONE. If the change cannot be done that way, MySQL returns an error instead of quietly choosing something heavier. An error on a test server is far better than an unexpected table copy on production.

What is instant, and what is not

The good news is that the most common changes are cheap. According to the manual, these support INSTANT:

ALTER TABLE orders ADD COLUMN source VARCHAR(32) NULL, ALGORITHM=INSTANT;

-- how many instant row versions has the table used? (limit: 64 in 8.4)
SELECT NAME, TOTAL_ROW_VERSIONS
FROM INFORMATION_SCHEMA.INNODB_TABLES
WHERE NAME = 'shop/orders';

Two limits matter on long-lived tables. Each instant add or drop creates a new row version, and MySQL 8.4 allows at most 64 (255 as of 9.1). After that you get ERROR 4092 (HY000): Maximum row versions reached and the next change has to rebuild the table. A rebuild with INPLACE, COPY or OPTIMIZE TABLE resets the counter. Instant column addition also does not work on tables with ROW_FORMAT=COMPRESSED, tables with a FULLTEXT index or temporary tables.

These are the operations that hurt on a large table:

With an explicit algorithm, a change that cannot be done in place fails right away. The manual's example, for a single-byte character set going from VARCHAR(255) to VARCHAR(256):

ALTER TABLE t ALGORITHM=INPLACE, CHANGE COLUMN c1 c1 VARCHAR(256);
ERROR 0A000: ALGORITHM=INPLACE is not supported. Reason: Cannot change
column type INPLACE. Try ALGORITHM=COPY.

The metadata lock queue: how an "online" ALTER freezes production

This is one of the most common schema-change incidents, and it happens even with instant changes. Every ALTER TABLE needs an exclusive metadata lock on the table, at least briefly. The manual describes the sequence exactly:

  1. Some transaction has read from the table and has not committed yet. It holds a shared metadata lock. It might be a report, a batch job, or a connection left idle in a transaction by the application.
  2. Your ALTER TABLE asks for the exclusive lock and waits for that transaction.
  3. Every new query on the table now waits behind the pending ALTER, including plain SELECTs.

From the outside it looks like the table is down. Connections pile up, the pool runs out, and the application starts failing, all because of one transaction that was doing nothing. SHOW FULL PROCESSLIST shows the waiting sessions.

The default lock_wait_timeout is 31536000 seconds, which is one year. So by default the ALTER will wait, and keep everyone else waiting, practically forever. Before any DDL on a busy table, lower it in your own session and check for long transactions first:

-- give up after 5 seconds instead of queueing everyone behind us
SET SESSION lock_wait_timeout = 5;
ALTER TABLE orders ADD INDEX idx_customer_created (customer_id, created_at),
  ALGORITHM=INPLACE, LOCK=NONE;

If the lock is not granted in 5 seconds, the ALTER fails, the queue clears and you can try again later. A failed attempt costs nothing, while a blocked table costs an incident. This metadata lock wait is different from InnoDB row lock waits, which we cover in our lock wait timeout article.

Large tables: disk, logs and replicas

On a table of a few gigabytes, an in-place rebuild is a coffee break. On hundreds of gigabytes, check these before you start.

You can follow a long ALTER through Performance Schema stage events. WORK_ESTIMATED may change as it runs, so treat it as a rough indicator:

UPDATE performance_schema.setup_instruments SET ENABLED = 'YES'
 WHERE NAME LIKE 'stage/innodb/alter%';
UPDATE performance_schema.setup_consumers SET ENABLED = 'YES'
 WHERE NAME LIKE '%stages%';

SELECT EVENT_NAME, WORK_COMPLETED, WORK_ESTIMATED
FROM performance_schema.events_stages_current;

gh-ost and pt-online-schema-change

When a change needs a rebuild or a copy and the table is large and busy, online schema change tools are usually the better choice. Both create a new table with the new structure, copy rows in small chunks, keep it in sync with ongoing writes and finally swap the tables. The difference is how they keep it in sync.

pt-online-schema-change from Percona Toolkit uses triggers on the original table and switches the tables with an atomic RENAME TABLE. Things to know:

gh-ost from GitHub does not use triggers. It reads changes from the binary log instead, so writes to the original table do not fire extra triggers. Its requirements:

Its strength on large tables is control: it throttles on replica lag with --max-lag-millis, on load with --max-load, bails out on --critical-load, and can postpone the final cut-over until you remove a flag file, so the swap happens when you choose:

gh-ost --host=replica-1 --database=shop --table=orders \
  --alter="MODIFY amount DECIMAL(14,2) NOT NULL" \
  --max-lag-millis=1500 --max-load=Threads_running=30 \
  --critical-load=Threads_running=80 \
  --postpone-cut-over-flag-file=/tmp/ghost.postpone \
  --execute

Both tools copy the table, so they need free space for a full second copy of it. The copy consists of ordinary writes, so it also goes into the binary log and to every replica. It is slower than a native rebuild, but it is throttled, pausable and keeps replicas close to current, which is usually what you want on production.

Our rule of thumb: if the change is INSTANT, run it natively with ALGORITHM=INSTANT and a short lock_wait_timeout. If it is INPLACE without a rebuild and the table is moderate, run it natively in a quiet period. If it rebuilds or copies a large table, or replicas must not lag, use gh-ost or pt-online-schema-change. And whatever the method, check for long-running transactions first.

A checklist before ALTER on a large table

  1. Find out the algorithm: run the statement with an explicit ALGORITHM on a copy with the same schema and see whether MySQL accepts it.
  2. For instant changes, check TOTAL_ROW_VERSIONS.
  3. For rebuilds, check free space in the data directory and in tmpdir/innodb_tmpdir.
  4. Check for long-running and idle transactions on the table, and set a short lock_wait_timeout in your session.
  5. Estimate the duration and replica lag on a full-size copy, and decide whether native DDL or a tool fits.
  6. Have a fresh, verified backup and decide in advance how to stop or roll back.

Key takeaways

MySQL Online DDL gh-ost pt-online-schema-change
Work with us

Need a schema change on a big MySQL table?

We plan and run schema changes on large production MySQL tables: choosing the method, checking space and replicas, throttling, and carrying out the cut-over in a safe window.

Get in touch More from the blog