← Back to blog

WordPress database repair: what repair.php actually does, and what it cannot touch

The SandyWP team 6 min read

WordPress has a built-in database repair page at /wp-admin/maint/repair.php, and you open it by adding define( 'WP_ALLOW_REPAIR', true ); to wp-config.php. Two things about it are worth knowing before you do: the page has no login check at all, and the REPAIR TABLE statement it runs is not supported on InnoDB, which is the engine your tables almost certainly use.

That does not make the page useless. It makes it a narrow tool, and knowing its edges tells you when to stop clicking it and go after the real problem.

What the page does, line by line

wp-admin/maint/repair.php is 192 lines. The working part is a loop:

$tables = $wpdb->tables();
$tables = array_merge( $tables, (array) apply_filters( 'tables_to_repair', array() ) );

foreach ( $tables as $table ) {
	$check = $wpdb->get_row( $wpdb->prepare( 'CHECK TABLE %i', $table ) );
	// ...if Msg_text is not 'OK':
	$repair = $wpdb->get_row( $wpdb->prepare( 'REPAIR TABLE %i', $table ) );
}

It runs CHECK TABLE on each table, and only if the result is not OK does it run REPAIR TABLE. Choosing "Repair and Optimize" adds an ANALYZE TABLE, and then an OPTIMIZE TABLE if the analyze did not report the table already up to date.

That is the entire mechanism. Three standard SQL maintenance statements, run against a fixed list of tables.

The constant is the only lock on the page

There is no is_user_logged_in() in that file, no capability check, and no nonce. The file defines WP_REPAIRING before loading WordPress, specifically so it can run when the normal admin cannot.

This is deliberate: the page exists for when your database is broken enough that you cannot log in. It is also why core itself tells you, in the success message, to take the constant back out:

"Repairs complete. Please remove the following line from wp-config.php to prevent this page from being used by unauthorized users."

Guides disagree about whether the page is protected. It is not.

While that constant is defined, anyone who knows the URL can trigger a full CHECK, REPAIR and OPTIMIZE pass over your tables, and OPTIMIZE on a large table is slow enough to be a usable denial of service.

Add the constant, use the page, delete the line. Not tomorrow.

Why the repair usually reports nothing to do

REPAIR TABLE only works on a few storage engines. The MySQL manual is blunt about it: the statement "works for MyISAM, ARCHIVE, and CSV tables."

InnoDB has been the default engine since MySQL 5.5, released in 2010, and it is the default in MariaDB too.

So on any WordPress site built this decade, every core table is InnoDB and REPAIR TABLE has nothing to offer them.

What you get back is a row saying the storage engine does not support repair, which the page renders as a failure for that table.

The repair did not fail because your data is beyond saving. It failed because the statement does not apply.

This matters because it changes what a bad result means. A page full of engine-not-supported messages tells you nothing about your data.

To find out which engines you actually have:

SELECT table_name, engine FROM information_schema.tables
WHERE table_schema = DATABASE();

If anything comes back as MyISAM, usually an old table a plugin created years ago, then the repair page has real work to do on it.

What "Optimize" does on InnoDB, and why it locks

The optimize option is the one worth being careful with. On InnoDB, OPTIMIZE TABLE is not a tidy-up: the MySQL manual says it "is mapped to ALTER TABLE ... FORCE, which rebuilds the table to update index statistics and free unused space in the clustered index."

A full table rebuild. Core's own wording on the button acknowledges it: "the database will be locked while optimizing."

Modern InnoDB does this online for regular tables, taking an exclusive lock only briefly at each end.

The exception in the manual is tables with FULLTEXT indexes, which fall back to the copy method, and wp_posts has a FULLTEXT index on some setups.

If your site is down and you are trying to get it back, press "Repair Database" and not the optimize button. Optimizing is a performance job for a quiet moment, not an outage fix.

What the page will not touch

$wpdb->tables() is the list of core tables. Posts, postmeta, options, users, usermeta, terms, comments and the rest.

Your WooCommerce lookup tables, your form plugin's entries table, your analytics table: none of them are in that list. A plugin has to hook the tables_to_repair filter to be included, and most do not.

add_filter( 'tables_to_repair', function ( $tables ) {
	global $wpdb;
	$tables[] = $wpdb->prefix . 'my_plugin_data';
	return $tables;
} );

So "Repairs complete" means the core tables passed. It is not a clean bill of health for the database.

There is also a detail in the optimize branch worth knowing if you read the output closely. The $optimize variable holds the mode flag at the start, then gets reassigned to the query result row inside the same loop, so the flag it is tested against is not the same value after the first table that needs optimizing.

Treat the optimize output as advisory rather than a complete record.

The commands to reach for instead

If you have shell access, skip the page. WP-CLI wraps the same statements and tells you more:

wp db check
wp db repair
wp db optimize

wp db check runs CHECK TABLE across every table in the database, not just the core list, which is the main reason to prefer it.

For a corrupt InnoDB table specifically, the rebuild that actually does something is:

ALTER TABLE wp_options ENGINE=InnoDB;

That rewrites the table in place. It is what OPTIMIZE TABLE runs under the hood, without the repair page wrapped around it.

And if InnoDB itself will not start, no SQL statement helps. That is an innodb_force_recovery job on the server, and on managed hosting it is a support ticket.

When corruption is not the problem

Most "my database is corrupted" cases are not corruption.

"Error establishing a database connection" is nearly always wrong credentials in wp-config.php, a database server that is down, or a connection limit. The repair page cannot help, because it loads WordPress too and will hit the same wall.

A site that is slow is usually autoloaded options, orphaned postmeta or an oversized options table, none of which REPAIR TABLE addresses. Transients left behind by plugins are a common cause and a different fix entirely.

One missing table after a failed migration or import is a restore job, not a repair job. REPAIR TABLE cannot create a table that is not there.

Before you repair anything, take a copy of the database. A repair pass is a write operation on data you have already decided is damaged.

Practise this on a copy, not on the site that is broken

Every step above writes to your database while you are least sure what is wrong with it, and the useful ones need a constant in wp-config.php that opens an unauthenticated page.

That is a good argument for trying the sequence somewhere disposable first. Clone the site into a sandbox, set the constant there, and see what the repair page actually reports against your table list and your plugins before you touch production.

Sandboxes here run MariaDB 11, so the engine behaviour above is the same one you will see on your host. Shell and SFTP access give you wp db check and wp db query directly, which is the quickest way to get the engine list and the table sizes out.

If the sandbox comes back clean and production does not, you have learned something specific. If both come back the same, you have learned the repair page was never the answer, and you have learned it without a locked table on a live site.

That is the same reason upgrade testing belongs on a copy.