auto_increment flag repair on primary keys of a WordPress MySQL database

Here’s some code that I used to restore the auto_increment flag on the primary keys of my WordPress MySQL database after a somewhat faulty export/import cycle.

The auto_increment problem and its symptoms

When you export the WordPress database and then import it again, either via phpmyadmin or via mysqldump and the mysql CLI, all sorts of things can (and often will) go wrong. This can be understandably very stressful, especially on a live system. Please take a deep breath and read on.

I did a dump of my (fortunately development) environment and then imported it again. At first glance, all was working perfectly.

Then, all of a sudden I noticed that the Administrator user had no rights to create posts or pages. The usual Publish button was replaced with Submit for review.

Naïvely my first thought was to install a plugin to fix roles and capabilities. Sure enough, the admin user had no right to create posts or pages. When I tried to give those rights, I saw the following in my logs:

PHP message: WordPress database error Duplicate entry '0' for key 'PRIMARY' for query INSERT INTO `wp_usermeta` (`user_id`, `meta_key`, `meta_value`) VALUES (1, 'wp_capabilities', 'a:1:{s:13:\"administrator\";b:1;}') made by require_once('wp-admin/admin.php'), do_action('users_page_users-user-role-editor'), User_Role_Editor->edit_roles, Ure_Lib->editor, Ure_Lib->process_user_request, Ure_Lib->permissions_object_update, Ure_Lib->update_user, WP_User->add_role, update_user_meta, update_metadata, add_metadata

(I have debug logs enabled in my development environment. If you’re in a production environment you might not see this.)

Why would a primary key be set to 0 you ask? A quick glance at the structure of the wp_usermeta table via phpmyadmin reveals that the primary key column had no auto_increment flag.

SQL INSERT statements from various plugins were inserting rows in various tables with the primary key being undefined (and therefore set to a default of 0). Since primary keys have a unique constraint, attempting to do a second insert to the same table fails, causing all sorts of havoc.

For some reason the auto_increment flag had not been preserved when I re-imported the SQL dump. Everything else seemed to be in order though. I did not investigate why this happened but decided to simply fix this.

Coding is the solution

By now I could see the front-end but was not able to login to the admin interface any more. Any INSERT query to the database, including those that store session information upon login, were failing. As I had quite a lot of tables, I decided not to do this manually, but to write a generic script.

As a side-note, the script needs to connect with the NO_ZERO_DATE SQL mode. WordPress uses a lot of DATETIME fields with a default value of 0000-00-00 00:00:00 and this script will be very unhappy if this mode is not set.

Pseudocode

for all tables in database
    get the primary key column's name and type
    get the next available primary key value
    change the row with zero primary key so it has the next available primary key
    set the auto_increment flag to the primary key column

Note: The above assumes that all the primary keys are numeric. YMMV.

What to do:

Next is a PHP listing of the above solution. Here’s what to do:

  1. Check to see that your issue is actually one of missing auto_increment flags. This script will only repair this particular error.
  2. Check to see that all your primary keys are numeric. There shouldn’t be an issue if some aren’t but you might have to hack the code manually or go update the structure of those tables via phpmyadmin.
  3. Change the host, dbname, username and password in the code to those that match your system.
  4. Backup your database (I guess you already have a backup and that’s what caused the issue, but still, you want to be able to go back if something goes wrong when running this.) You are solely responsible for any damages including data loss from running this script. Don’t blame me for corrupting your data please. Read and understand the code first!

The PHP script

Save this in a file, take another deep breath, and run it via the PHP CLI. I hope it solves your MySQL woes. Good luck buddy.

<?php

// change these settings
$servername = 'localhost';
$username = 'dbuser';
$password = 'password';
$db = 'database';

// connect
$conn = new mysqli($servername, $username, $password);
try {
    $conn = new PDO("mysql:host=$servername;dbname=$db", $username, $password, array(PDO::MYSQL_ATTR_INIT_COMMAND => 'SET sql_mode="NO_ZERO_DATE"') );
    $conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION );
    echo "Connected successfully";
} catch(PDOException $e) {
    exit( "Connection failed: " . $e->getMessage() );
}

// get all table names
$stm = $conn->prepare('SHOW TABLES');
$stm->execute();
$table_names = array();
foreach ( $stm->fetchAll() as $row ) {
    $table_names[] = $row[0];
}

// for all tables
foreach ( $table_names as $table_name ) {
    echo "\nRepairing table $table_name...\n";

    // get the primary key name
    $stm = $conn->prepare( "show keys from $table_name where Key_name = 'PRIMARY'" );
    $stm->execute();
    $key_name = $stm->fetch()['Column_name'];

    // get the primary key type
    $stm = $conn->prepare( "show fields from $table_name where Field = '$key_name'" );
    $stm->execute();
    $key_type = $stm->fetch()['Type'];

    // if there is a primary key
    if ($key_name) {
        echo "Primary key is $key_name\n";

        try {
            // if auto_increment was missing there might be a row with key=0 . compute the next available primary key
            $sql = "select (ifnull( max($key_name), 0)+1) as next_id from $table_name";
            $stm = $conn->prepare( $sql );
            echo "$sql\n";
            $stm->execute();
            $next_id = $stm->fetch()['next_id'];

            // give a sane primary key to a row that has key = 0 if it exists
            $sql = "update $table_name set $key_name = $next_id where $key_name = 0";
            echo "$sql\n";
            $stm = $conn->prepare( $sql );
            $stm->execute();

            // set auto_increment to the primary key
            $sql = "alter table $table_name modify column $key_name $key_type auto_increment";
            echo "$sql\n";
            $stm = $conn->prepare( $sql );
            $stm->execute();

        } catch (PDOException $e) {
            echo "\n\nQuery: $sql\nError:" . $e->getMessage() . "\n\n";
        }
    } else {
        echo "No primary key found for table $table_name.\n";
    }
}
echo "\n\nFinished\n";
$conn = null;

4 thoughts on “auto_increment flag repair on primary keys of a WordPress MySQL database

Leave a Reply

Your email address will not be published. Required fields are marked *