A method of syncronising database changes from development to live using Toad for MySQL.
Requirements: advanced Drupal, database administration, experts only

One of the recurring problems faced by Drupal developers is database change management. If you don't encounter this problem yourself, then just imagine a scenario where you have a live site that needs to be running 24/7, while all your changes are made on a development server.
The big problem comes in migrating all the database changes from the dev server to the live one, without overwriting any data. Some people simply fiddle with dev until they’ve got it right, then duplicate all their work step-by-step on live, until they’re the same. You can imagine how perilous this process is!
I’ve been needing a solution to this problem for a while now, so I’ve pieced together this guide in the hope of receiving feedback from those who are wiser than me! I have not thoroughly tested this method on a real server configuration, only on my localhost, so I don’t know if it’s foolproof yet. But please take a look!
Issue #1: New primary keys need to be different on dev and live
If you’ve tried data syncronisation before, you may have run into this snag: Imagine dev and live both start with 3 nodes, primary key node-ids of 1, 2, 3. What happens when you add a node on dev, and a user adds a node on live?
Well, they both get a primary key id of 4, and this is irreconcilable by any data syncronisation application, as it views node 4 as having changed, rather than 2 new nodes being created.
I discovered a solution to this problem in this thread on France24’s Drupal staging solution. They force the dev server to create only even numbered ids, and the live server only odd numbered ids. That way, new content on one server can never create a primary key collision with new content on the other server.
This does involve a couple of lines hack on Drupal core. What, you don’t want to hack Drupal core? But sir, it is just one wafer-thin hack!
You could always do what the France24 guys do, and install PHP Runkit which will allow you to replace entire functions without modifying the original, but that seems like a bit of fiasco for a small change, and infeasible for shared hosting.
In Drupal 5, ids are managed in two different ways. The first is by using the sequences table, which stores the last created id for a number of different entities, such as users and nodes:

In order to force Drupal to use only odd or even ids, the function that needs to change is db_next_id(..), located in database.mysql.inc or database.mysqli.inc depending on which configuration you use (I would imagine mysqli for MySQL 5):
<?php
function db_next_id($name) {
$name = db_prefix_tables($name);
db_query('LOCK TABLES {sequences} WRITE');
$id = db_result(db_query("SELECT id FROM {sequences} WHERE name = '%s'", $name)) + 1;
db_query("REPLACE INTO {sequences} VALUES ('%s', %d)", $name, $id);
db_query('UNLOCK TABLES');
return $id;
}
?>The following lines say “if the id is even, then increment it by one so it becomes odd”
if ($id % 2 == 0) {
$id++;
}And to force even ids:
if ($id % 2 == 1) {
$id++;
}Here’s where it goes:
<?php
function db_next_id($name) {
$name = db_prefix_tables($name);
db_query('LOCK TABLES {sequences} WRITE');
$id = db_result(db_query("SELECT id FROM {sequences} WHERE name = '%s'", $name)) + 1;
if ($id % 2 == 1) {
$id++;
}
db_query("REPLACE INTO {sequences} VALUES ('%s', %d)", $name, $id);
db_query('UNLOCK TABLES');
return $id;
}
?>Naturally, you’ll want to make sure to force evens on dev, and odds on live, or vice versa.
MySQL System Variables
Then you have other tables, for example watchdog and probably a lot of contrib, do not use the sequences table. They rely on the database to auto-increment their primary key ids, so when they insert a row there is no call to db_next_id(..).
To manage these, we can alter MySQL system variables that were introduced in MySQL 5.0.2. Their primary function is to facilitate master-master replication, but it serves us the same purpose. They let us specify how MySQL generates it's auto incremented values.
The variables are auto_increment_increment and auto_increment_offset. It works as follows:
Starting from the value auto_increment_offset, create a sequence that increases each time by N x auto_increment_increment, where N is the position in the sequence.
Not really that complicated. If we give it the following:
auto_increment_increment = 2;
auto_increment_offset = 1;MySQL will generate a sequence of ids as follows:
1 3 5 7 9 11 13 …Perfect!
One method of setting these variables in by adding them to your my.cnf configuration file, and restarting the MySQL servers. Obviously this is not possible for those of us without dedicated hosts. Luckily, there’s another per-session method which also works, but unluckily it requires another hack to Drupal core… :(
Here’s the function, db_connect(..), which is located once again either in database.mysql.inc or database.mysqli.inc:
<?php
function db_connect($url) {
// Check if MySQLi support is present in PHP
if (!function_exists('mysqli_init') && !extension_loaded('mysqli')) {
_db_error_page('Unable to use the MySQLi database because the MySQLi extension for PHP is not installed. Check your <code>php.ini</code> to see how you can enable it.');
}
$url = parse_url($url);
// Decode url-encoded information in the db connection string
$url['user'] = urldecode($url['user']);
// Test if database url has a password.
$url['pass'] = isset($url['pass']) ? urldecode($url['pass']) : '';
$url['host'] = urldecode($url['host']);
$url['path'] = urldecode($url['path']);
if (!isset($url['port'])) {
$url['port'] = NULL;
}
$connection = mysqli_init();
@mysqli_real_connect($connection, $url['host'], $url['user'], $url['pass'], substr($url['path'], 1), $url['port'], NULL, MYSQLI_CLIENT_FOUND_ROWS);
if (mysqli_connect_errno() > 0) {
_db_error_page(mysqli_connect_error());
}
// Force UTF-8.
mysqli_query($connection, 'SET NAMES "utf8"');
return $connection;
}
?>Pretty simple change really. What we need to do is set the auto_increment variables here, where we initiate the connection. These lines, inserted immediately before
return $connection;, do it for odd numbers:
mysqli_query($connection,'SET @@auto_increment_increment=2');
mysqli_query($connection,'SET @@auto_increment_offset=1');And this is for even numbers:
mysqli_query($connection,'SET @@auto_increment_increment=2');
mysqli_query($connection,'SET @@auto_increment_offset=2’);And now for...
Drupal 6
The awesome thing about Drupal 6, is that there is no more sequences table. All ids are generated by the database, so the first hack to db_next_id(..) is not necessary. In fact, the function is gone now, so you’d struggle a bit to hack it anyway… :) You only need the changes that affect the auto increment variables.
Great! So now we’ve covered our bases. There are still two caveats though. What if a table doesn’t use a primary key? And what if a contributed module uses its own sequences table (for some bizarre reason)? I’d be interested to know why, firstly, but of course the answer is that you’ll just have to look out for these situations. This is made easier using the tool introduced in the next part.
Issue #2: Syncronising the data
I stumbled across a neat little tool called Toad for MySQL, a freeware application brought to you by the same guys that produce Toad for Oracle, an industry standard in Oracle database administration.
It’s free, but it’s Windows only, so fire up those virtual machines all you Mac and Linux guys!
In this example, I work by taking a copy of live’s database and importing it to dev, doing my changes on dev, then exporting the changes back to live.
Here's an example situation. I'm not going to step through exactly how to perform these steps, because they're all core functions of Drupal:
- development and live databases begin syncronised
- you, the developer, add a content type called "dog" to your dev site
- you create a new "dog", with some info
- you change the site name to "World of Pets"
- a user creates a story node on live, while you're busy with dev
- a user comments on a story on live
- a user edits a pre-existing story node, changes some text
- a new user signs up on live
Nightmare, right? ;)
Not necessarily. If you have your odd/even database ids working, this is easily reconcilable. Basically, all we need to do is migrate changes on dev up to live, then copy the entire database from live back to dev in order to be synced up again.
Step 1: Toad and its connections
Start by downloading and installing Toad. Once you're up and running, choose View->Connection Manager to bring up the following window (that is already populated with my connections in the screenshot):

Click the "add connection" button to add a connection to both your live and dev MySQL servers. It's a simple form to fill out for each:

Of course, your live host's MySQL server could potentially be configured to only allow connections from its localhost (i.e. PHP scripts running on itself), in which case connecting from your local machine will not work. GoDaddy.com imposes this restriction. Some hosts do not, and they let you to change the allowed hosts (for example using CPanel), so you can temporarily grant yourself remote access to your database.

Step 2: Comparing the data
Connect to one of your databases through Toad, then choose Advanced -> Data Compare from the main menu.

Up pops a wizard! Just select the source and target databases from the dialog:

In this case, I have two installations of Drupal on my localhost, to make things easier. One is called drupal-6-live, and the other drupal-6-local, 'local' meaning 'dev' in the context of this article.
Click "Next" and wait while it does its magic. You should then be presented with another step of the wizard:

Here you are telling Toad which tables to compare. Clicking the "Find Target(s)" button will automatically pair tables with the same name, which is definitely what we want. You can also see in this step if you have unpaired tables, for example if you created a new table on dev. Toad will not syncronise these, but exporting a complete table from dev to live is really easy, even using a tool like phpMyAdmin, so I won’t worry about that here.
Click "Next" and "Next" again, watch it do some more magic, then finish the wizard after reviewing the summary.
Step 3: Syncronising the correct changes
Here's the fun part. You'll be presented with this screen, which is a list of the findings of the database compare:

Step 4: Stare at it for a long, long, time until everything becomes clear or your brain tries to escape out of your ears...
No, not really. Maximise the window and shuffle the columns around so you can see the information clearly. You'll see in the 'status' column, that an equals sign indicates the table is the same on dev and live. A red/green arrow pair indicates that there are changes, and you should take a closer look!
Some knowledge of Drupal's database structure is absolutely critical. You should know that you don't need to syncronise the 'cache' or 'sessions' tables, for example. But the first one on the alphabetical list of changed tables that should catch your eye would be the comments:

Aha! The red arrow indicates the row was found on the live table, but not on our dev table. So a user has made a new comment. However, we don’t need to syncronise this back to dev, because we’re only looking for changes to move up to live, not the other way around. A green arrow is what we’re after.

It’s difficult to get a good screenshot, but here’s an example from the node table. You’ll see the red arrow indicating a new node on live with the id of 2, and the green arrow with a vid (and nid) of 3! So the odd/even numbering has protected us from collisions in this case. We select this row for syncronisation. Also notice the green/red arrows together, which means there’s a difference.
Continue through all the tables, checking for the green arrows, and ticking them off as necessary. Note that you need a sync tick next to the table, in order for any ticks next to the rows to be synced. A little user interface quirk that’s fine once you ‘get it’!
Now you can click the sync button, select the direction and prepare the SQL script, which can either be run immediately or in the Toad editor:


There’s no doubt that this is a finicky process that requires knowledge of the tables. However, I would argue that it’s not as laborious or error-prone as trying to replicate the changes using Drupal’s front end. Up for discussion, and definitely depends on your particular setup and the changes you are performing.
Conclusion
That’s it really! I’m not in a position to properly use this yet, as some of my sites don’t allow external MySQL connections, and the ones that do are still on PHP 4. So although I’ve looked into it as a possible solution, I am really hoping for input from the community on whether this is a good idea and if there’s perhaps a simpler way.
The hack that is used on Drupal 6 to force odd/even ids could possibly be submitted as a patch for inclusion into 7, if it presented an option to switch on/off in an advanced menu. Although that may be a hard sell...
You should also take your site onto offline mode while performing the sync itself… that’s quite important.
And don’t get me started on backups… ;)












MySQL connections
Not sure if this would work out, but as a solution for your database connection problem: if you can use SSH at your hoster, it's possible to forward the remote MySQL port to your local system. Eg: ssh -L 3307:localhost:3306 your.server.name
You can reach the MySQL on localhost port 3307 that way. (This is also possible on Windows with Putty, check the options in that case)
Thanks for the tip. My hosts
Thanks for the tip. My hosts don't allow SSH, but I'm sure someone else will benefit.
You need migrations
Several people have hacked the Ruby on Rails database migrations to work on other platforms.
It's an attempt to work through this problem from the development side of things by generating migration files for each change that can be run or rolled back on each change.
Interesting, do you have any
Interesting, do you have any specific links? I would like to check it out.
primary keys and code hacks
I've found that relying on database keys to do this sort of thing is troublesome, but better than anything else I've come up with. I wrote about my approach, http://www.dave-cohen.com/node/1779, and share my code in my drupal.org sandbox.
You'll find that many of drupal's tables do not have unique ids, or at least not numerical ones. As you wrote, you have to tweak these tables and I consider that another kind of core hack. Personally, I'm not afraid to hack away. Here's a list of tables I've discovered in D5.
blocks, filters, profile_values, permission, term_relation, term_synonym, view_argument, view_exposed_filter, view_filter, view_sort, view_tablefield, actions_registry, actions_assignments, workflow_actions, workflow_type_map, profile_values.
I've had trouble with all those, because I use my own database dump script (I'm not about to start a virtual machine for Toad) and it relies on unique keys to prevent duplicate entries when promoting changes to the live server.
Thanks for the links and
Thanks for the links and table list. I didn't realise there were so many in Drupal 5 without primary keys. I took a peek at a Drupal 6 database and it seems that there's been a concerted effort to give everything a primary key. That must have been something to do with the schema API? Anyway it's good news for the future!
Just want to confirm that
Just want to confirm that Toad does warn about tables without suitable keys when doing a data compare. There are about 6 tables in Drupal 5 core like this.
In Drupal 6, every table has a key so it works nicely.
It's at least comforting to know that you'll get a warning if contrib modules don't provide tables with primary keys!
i made a little module for
i made a little module for exporting/importing from the database. I never released it because I never cleaned it up but I would be interested in releasing it if someone wants to take over it.
what it does (very developer like), generates an admin page for exporting tables and importing tables.
it will display a form with checkboxes for every table and there were a few other options like only export the table structures not the data, and something else.. i can't remember.
it will create a folder in your files/ directory with dates and everything and each table will have a tablename.sql file with whatever you wanted to export.
and to import this, you can copy that folder it creates, move it over to the "production" box, have the same module installed on that box, go to the import page, and select which version you want to import (version is a select box that just reads the /files/export/ folder and shows all the dates available).
this helped to speed up doing big structure changes and etc..
its part of the problem as a developer.. you get so busy and use to some type of flow and you dont work on your own process as much as you should.
it would be much better to see a much more automated system but this worked when i needed something fairly fast.
figuring out the drupal table structure and which tables contain content and which contain data... fun times... oh and all those damn contrib module tables.. :S
Was there any reason for not
Was there any reason for not using phpMyAdmin to do this? Just curious.
Thanks for such a comprehensive post
This is a great thing to put out there and brings together a bunch of stuff I've read and have been thinking about - haven't tried the actual steps you've outlined but at a glance they seem to be the most comprehensive documentation on something that actually works.
It's worth noting three Drupal projects that have been recently active with trying to solve the data migration problem(s):
http://drupal.org/project/deploy (this one seems particularly active)
http://drupal.org/project/backup_migrate
http://drupal.org/project/dbscripts
(fyi, I don't include Autopilot in the list because it has not been active since September 2007, in terms of anything publicly released.)
Oooh, great
I had no idea about Toad for MySQL, and I'll definately have to have a look at it.
Toad for MySQL is crap
I love Toad for Oracle. However, Toad for MySQL is pure crap. It is so buggy and slow that you can't use it after a few hours.
I like SQLyog's Structure Sync and DB Sync tools.
Post new comment