Archive for the ‘Databases’ Category

What to do if you get ERROR 1005/errno: 150

Friday, September 3rd, 2010

Sometimes when you try to add a foreign key in MySQL, you get this error:

ERROR 1005 (HY000): Can't create table 'my_table.#sql-a74_233' (errno: 150)

Every time I’ve come across this problem it’s been because my int types didn’t match. For example, I had an int(11) trying to reference a bigint(20) on a different table. If you change the int types to match, the problem goes away. Keep in mind that you may have to delete all the rows in your tables and/or remove existing foreign keys in order to be able to make the change.

Transactions with Doctrine

Friday, September 3rd, 2010

If you’re going to make multiple related updates to your database, you should use a transaction. Unfortunately Doctrine’s documentation on transactions, like most of the Doctrine documentation I’ve seen, kind of sucks. Maybe their whole “Unit of Work” class without error handling makes sense somehow but I don’t get it. Here’s what they have:

<?php
$conn = Doctrine_Manager::connection();
 
try {
  $conn->beginTransaction();
 
  $this->performCreatesOrUpdates($conn);
  $this->performDeletes($conn);
 
  $conn->commit();
} catch(Doctrine_Exception $e) {
  $conn->rollback();
}

This is the right idea but it has the unforgivable flaw that it fails silently. If your transaction rolls back instead of commits, you won’t necessarily know about it. That’s bad. Here’s what I would have put:

<?php
$conn = Doctrine_Manager::connection();
 
try
{
  $conn->beginTransaction();
 
  $myObject->save();
  $myOtherObject->save();
 
  $conn->commit();
}
catch(Exception $e)
{
  throw new Exception($e->getMessage());
  $conn->rollback();
}

The code is mostly the same but that one crucial change—throwing an exception—makes a world of difference.

Inheritance with symfony and Doctrine ORM

Monday, February 1st, 2010

The Problem

Doctrine ORM claims to support some kind of inheritance but I have yet to see a good example. The inheritance documentation on the Doctrine ORM site could be worse but it certainly has a lot of room for improvement. How about an example with some actual data? The symfony documentation for Doctrine inheritance lays things out a little more clearly than the Doctrine docs but it still wants for good examples.

The Solution

Here I intend to perform a hearty exploration of Doctrine inheritance with the hope that I can show, by example, what it can and can’t do. The inheritance model I will use will be simple: the base class will be called FarmAnimal and the three inheriting classes will be Cow, Dog and Chicken. All FarmAnimals will have at least a name, a sound and a number of legs. Each type of FarmAnimal will have the following unique properties: Cow will have a use (beef or dairy), a Dog will have a breed and a Chicken will have an egg color.

If you’d like to follow along, I’ve put together some setup instructions for this project.

Doctrine offers three styles of inheritance: Simple, Concrete and Column Aggregation. The symfony docs give the following definitions:

Name Description
Concrete Each child class has a separate table has all the columns of its parents
Simple Each child class shares the same table and columns as its parents
Column Aggregation All columns must be defined in the parent and each child class is determined by a type column

Simple Inheritance

The Doctrine documentation shows an example where we have a User class and a Group class. Both of these classes inherit from Entity and neither one has any unique properties. When the example schema is evaluated, Doctrine only spits out one table: entity. If that’s all that “simple inheritance” is good for, I don’t really see how that’s inheritance. Any object you might instantiate, whether it be a User, Group or plain Entity, is an Entity, nothing more and nothing less. What’s gained there?

Let’s try something similar to their example but go a little further with it.

config/doctrine/schema.yml

FarmAnimal:
  columns:
    name: string(20)
    sound: string(20)
    leg_count: integer
    created_at: timestamp
    updated_at: timestamp
 
Cow:
  inheritance:
    extends: FarmAnimal
    type: simple
  columns:
    purpose:
      type: enum
      values: [beef, dairy]
 
Dog:
  inheritance:
    extends: FarmAnimal
    type: simple
  columns:
    breed: string(20)
 
Chicken:
  inheritance:
    extends: FarmAnimal
    type: simple
  columns:
    egg_color:
      type: enum
      values: [brown, white]

Since we’re using enums, we’ll have to update config/databases.yml and add the use_native_enum directive:

all:
  doctrine:
    class: sfDoctrineDatabase
    param:
      dsn: 'mysql:host=localhost;dbname=inheritance'
      username: inheritance
      password: pass123
      attributes:
        use_native_enum: true

Now let’s build the model, build the SQL and insert the SQL:

$ symfony doctrine:build-model
$ symfony doctrine:build-sql
$ symfony doctrine:insert-sql

What does this give us in the database?

mysql> SHOW TABLES;
+-----------------------+
| Tables_in_inheritance |
+-----------------------+
| farm_animal           |
+-----------------------+
1 row IN SET (0.00 sec)
 
mysql> DESCRIBE farm_animal;
+------------+-----------------------+------+-----+---------+----------------+
| FIELD      | Type                  | NULL | KEY | DEFAULT | Extra          |
+------------+-----------------------+------+-----+---------+----------------+
| id         | bigint(20)            | NO   | PRI | NULL    | AUTO_INCREMENT |
| name       | varchar(20)           | YES  |     | NULL    |                |
| sound      | varchar(20)           | YES  |     | NULL    |                |
| leg_count  | bigint(20)            | YES  |     | NULL    |                |
| created_at | datetime              | YES  |     | NULL    |                |
| updated_at | datetime              | YES  |     | NULL    |                |
| purpose    | enum('beef','dairy')  | YES  |     | NULL    |                |
| breed      | varchar(20)           | YES  |     | NULL    |                |
| egg_color  | enum('brown','white') | YES  |     | NULL    |                |
+------------+-----------------------+------+-----+---------+----------------+
9 rows IN SET (0.01 sec)

As you can clearly see, all the inheriting classes’ properties have been rolled into one table along with the base class’s properties. This is exactly what the Doctrine doc tells us, but it doesn’t totally spell it out with an example.

The table above is a bad model, both from a purist and pragmatic perspective. A purist might say, “We have three distinctly different types of things but only one data structure! Are we expected to deduce, based on which values are NULL and which are not, which type of FarmAnimal we’re dealing with? What if we add the idea of a Cat with a breed? How do we tell it apart from a Dog?” And a pragmatist might say, “What if we have 20 different types of FarmAnimals, each having 5 different properties? That’s at least 100 columns in the FarmAnimal table!” I would agree with both the pragmatist and the purist. Imagine looking at a table with 100 columns, most of the rows having NULL in most of the columns most of the time. That’s an extreme example, but the principle is the same on any scale.

If you look at the PHP classes created by symfony, they’re meaningless. Take a look at lib/model/doctrine/base/BaseChicken.class.php, for example:

/**
 * BaseChicken
 *
 * This class has been auto-generated by the Doctrine ORM Framework
 *
 *
 * @package    INHERITANCE
 * @subpackage model
 * @author     Your name here
 * @version    SVN: $Id: Builder.php 6820 2009-11-30 17:27:49Z jwage $
 */
abstract class BaseChicken extends FarmAnimal
{
    public function setUp()
    {
        parent::setUp();
 
    }
}

There’s nothing unique to Chicken there. All the properties of all the FarmAnimals are kept in lib/model/doctrine/base/BaseFarmAnimal.class.php:

abstract class BaseFarmAnimal extends sfDoctrineRecord
{
    public function setTableDefinition()
    {
        $this->setTableName('farm_animal');
        $this->hasColumn('name', 'string', 20, array(
             'type' => 'string',
             'length' => '20',
             ));
        $this->hasColumn('sound', 'string', 20, array(
             'type' => 'string',
             'length' => '20',
             ));
        $this->hasColumn('leg_count', 'integer', null, array(
             'type' => 'integer',
             ));
        $this->hasColumn('created_at', 'timestamp', null, array(
             'type' => 'timestamp',
             ));
        $this->hasColumn('updated_at', 'timestamp', null, array(
             'type' => 'timestamp',
             ));
        $this->hasColumn('purpose', 'enum', null, array(
             'type' => 'enum',
             'values' =>
             array(
              0 => 'beef',
              1 => 'dairy',
             ),
             ));
        $this->hasColumn('breed', 'string', 20, array(
             'type' => 'string',
             'length' => '20',
             ));
        $this->hasColumn('egg_color', 'enum', null, array(
             'type' => 'enum',
             'values' =>
             array(
              0 => 'brown',
              1 => 'white',
             ),
             ));
    }
 
    public function setUp()
    {
        parent::setUp();
 
    }
}

I’ve seen enough of “simple inheritance” to know that I would never use it. Let’s see what “concrete inheritance” is all about.

Concrete Inheritance

Apparently all that’s needed to switch from simple inheritance to concrete inheritance is to replace all instances of type: simple with type: concrete:

FarmAnimal:
  columns:
    name: string(20)
    sound: string(20)
    leg_count: integer
    created_at: timestamp
    updated_at: timestamp
 
Cow:
  inheritance:
    extends: FarmAnimal
    type: concrete
  columns:
    purpose:
      type: enum
      values: [beef, dairy]
 
Dog:
  inheritance:
    extends: FarmAnimal
    type: concrete
  columns:
    breed: string(20)
 
Chicken:
  inheritance:
    extends: FarmAnimal
    type: concrete
  columns:
    egg_color:
      type: enum
      values: [brown, white]

Let’s run our symfony commands again:

$ symfony doctrine:build-model
$ symfony doctrine:build-sql
$ symfony doctrine:insert-sql

What’s in the database now?

mysql> SHOW TABLES;
+-----------------------+
| Tables_in_inheritance |
+-----------------------+
| chicken               |
| cow                   |
| dog                   |
| farm_animal           |
+-----------------------+
4 rows IN SET (0.00 sec)

That’s encouraging: each class has its own separate table.

mysql> DESCRIBE farm_animal;
+------------+-------------+------+-----+---------+----------------+
| FIELD      | Type        | NULL | KEY | DEFAULT | Extra          |
+------------+-------------+------+-----+---------+----------------+
| id         | bigint(20)  | NO   | PRI | NULL    | AUTO_INCREMENT |
| name       | varchar(20) | YES  |     | NULL    |                |
| sound      | varchar(20) | YES  |     | NULL    |                |
| leg_count  | bigint(20)  | YES  |     | NULL    |                |
| created_at | datetime    | YES  |     | NULL    |                |
| updated_at | datetime    | YES  |     | NULL    |                |
+------------+-------------+------+-----+---------+----------------+
6 rows IN SET (0.00 sec)
 
mysql> DESCRIBE cow;
+------------+----------------------+------+-----+---------+----------------+
| FIELD      | Type                 | NULL | KEY | DEFAULT | Extra          |
+------------+----------------------+------+-----+---------+----------------+
| id         | bigint(20)           | NO   | PRI | NULL    | AUTO_INCREMENT |
| name       | varchar(20)          | YES  |     | NULL    |                |
| sound      | varchar(20)          | YES  |     | NULL    |                |
| leg_count  | bigint(20)           | YES  |     | NULL    |                |
| created_at | datetime             | YES  |     | NULL    |                |
| updated_at | datetime             | YES  |     | NULL    |                |
| purpose    | enum('beef','dairy') | YES  |     | NULL    |                |
+------------+----------------------+------+-----+---------+----------------+
7 rows IN SET (0.00 sec)
 
mysql> DESCRIBE dog;
+------------+-------------+------+-----+---------+----------------+
| FIELD      | Type        | NULL | KEY | DEFAULT | Extra          |
+------------+-------------+------+-----+---------+----------------+
| id         | bigint(20)  | NO   | PRI | NULL    | AUTO_INCREMENT |
| name       | varchar(20) | YES  |     | NULL    |                |
| sound      | varchar(20) | YES  |     | NULL    |                |
| leg_count  | bigint(20)  | YES  |     | NULL    |                |
| created_at | datetime    | YES  |     | NULL    |                |
| updated_at | datetime    | YES  |     | NULL    |                |
| breed      | varchar(20) | YES  |     | NULL    |                |
+------------+-------------+------+-----+---------+----------------+
7 rows IN SET (0.01 sec)
 
mysql> DESCRIBE chicken;
+------------+-----------------------+------+-----+---------+----------------+
| FIELD      | Type                  | NULL | KEY | DEFAULT | Extra          |
+------------+-----------------------+------+-----+---------+----------------+
| id         | bigint(20)            | NO   | PRI | NULL    | AUTO_INCREMENT |
| name       | varchar(20)           | YES  |     | NULL    |                |
| sound      | varchar(20)           | YES  |     | NULL    |                |
| leg_count  | bigint(20)            | YES  |     | NULL    |                |
| created_at | datetime              | YES  |     | NULL    |                |
| updated_at | datetime              | YES  |     | NULL    |                |
| egg_color  | enum('brown','white') | YES  |     | NULL    |                |
+------------+-----------------------+------+-----+---------+----------------+
7 rows IN SET (0.00 sec)

That looks like what I want. Each entity is represented by a table that has a column for each of its properties, nothing more and nothing less. The only way this might be improved, in my opinion, is if we could tell Doctrine that FarmAnimal is an abstract class and we don’t want a table for it.

If we look at the symfony-generated classes, they look how we’d expect:

abstract class BaseChicken extends FarmAnimal
{
    public function setTableDefinition()
    {
        parent::setTableDefinition();
        $this->setTableName('chicken');
        $this->hasColumn('egg_color', 'enum', null, array(
             'type' => 'enum',
             'values' =>
             array(
              0 => 'brown',
              1 => 'white',
             ),
             ));
    }
 
    public function setUp()
    {
        parent::setUp();
 
    }
}
abstract class BaseFarmAnimal extends sfDoctrineRecord
{
    public function setTableDefinition()
    {
        $this->setTableName('farm_animal');
        $this->hasColumn('name', 'string', 20, array(
             'type' => 'string',
             'length' => '20',
             ));
        $this->hasColumn('sound', 'string', 20, array(
             'type' => 'string',
             'length' => '20',
             ));
        $this->hasColumn('leg_count', 'integer', null, array(
             'type' => 'integer',
             ));
        $this->hasColumn('created_at', 'timestamp', null, array(
             'type' => 'timestamp',
             ));
        $this->hasColumn('updated_at', 'timestamp', null, array(
             'type' => 'timestamp',
             ));
    }   
 
    public function setUp()
    {
        parent::setUp();
 
    }
}

Doctrine’s “concrete inheritance” looks pretty solid. Let’s take a look at the third and final style, “column aggregation.”

Column Aggregation

To be honest, I’m pretty baffled as to what this one is supposed to do for us. Hopefully this example will make things clearer.

FarmAnimal:
  columns:
    name: string(20)
    sound: string(20)
    leg_count: integer
    created_at: timestamp
    updated_at: timestamp
 
Cow:
  inheritance:
    extends: FarmAnimal
    type: column_aggregation
    keyField: type
    keyValue: 1
  columns:
    purpose:
      type: enum
      values: [beef, dairy]
 
Dog:
  inheritance:
    extends: FarmAnimal
    type: column_aggregation
    keyField: type
    keyValue: 2
  columns:
    breed: string(20)
 
Chicken:
  inheritance:
    extends: FarmAnimal
    type: column_aggregation
    keyField: type
    keyValue: 3
  columns:
    egg_color:
      type: enum
      values: [brown, white]
$ symfony doctrine:build-model
$ symfony doctrine:build-sql
$ symfony doctrine:insert-sql
mysql> SHOW TABLES;
+-----------------------+
| Tables_in_inheritance |
+-----------------------+
| farm_animal           |
+-----------------------+
1 row IN SET (0.00 sec)
 
mysql> DESCRIBE farm_animal;
+------------+-----------------------+------+-----+---------+----------------+
| FIELD      | Type                  | NULL | KEY | DEFAULT | Extra          |
+------------+-----------------------+------+-----+---------+----------------+
| id         | bigint(20)            | NO   | PRI | NULL    | AUTO_INCREMENT |
| name       | varchar(20)           | YES  |     | NULL    |                |
| sound      | varchar(20)           | YES  |     | NULL    |                |
| leg_count  | bigint(20)            | YES  |     | NULL    |                |
| created_at | datetime              | YES  |     | NULL    |                |
| updated_at | datetime              | YES  |     | NULL    |                |
| type       | varchar(255)          | YES  |     | NULL    |                |
| purpose    | enum('beef','dairy')  | YES  |     | NULL    |                |
| breed      | varchar(20)           | YES  |     | NULL    |                |
| egg_color  | enum('brown','white') | YES  |     | NULL    |                |
+------------+-----------------------+------+-----+---------+----------------+
10 rows IN SET (0.01 sec)

One table again, just like simple inheritance. Since I already rejected this structure, I see no reason to continue with column aggregation.

Conclusion

In my opinion, Doctrine’s simple inheritance and column aggregation are invalid and concrete is the only way to go. I hope these examples cleared up some confusion for anyone who had as much trouble with these concepts as I did.

Inheritance with symfony and Doctrine ORM: Project Setup

Monday, February 1st, 2010

This post goes along with another post, Inheritance with symfony and Doctrine ORM. If you’ve found yourself here, you might want to start there.

First, set up the symfony project:

$ mkdir inheritance
$ cd inheritance/
$ mkdir -p lib/vendor
$ cd lib/vendor/
$ wget http://www.symfony-project.org/get/symfony-1.4.1.zip
$ mv symfony-1.4.1 symfony
$ rm symfony
$ rm symfony-1.4.1.zip
$ cd ../../
$ php lib/vendor/symfony/data/bin/symfony -V
symfony version 1.4.1 (/var/www/inheritance/lib/vendor/symfony/lib)

$ php lib/vendor/symfony/data/bin/symfony generate:project INHERITANCE

Then create the database:

mysql> create database inheritance;
Query OK, 1 row affected (0.06 sec)

Following the principle of least privilege, create a user that only has access to the inheritance database and nothing else:

mysql> create user 'inheritance'@'localhost' identified by 'pass123';
Query OK, 0 rows affected (0.24 sec)

mysql> grant all on inheritance.* to 'inheritance'@'localhost';
Query OK, 0 rows affected (0.00 sec)

Now tell symfony about the database:

$ php symfony configure:database "mysql:host=localhost;dbname=inheritance" inheritance pass123

And create the frontend app:

$ symfony generate:app frontend
$ chmod 777 cache/ log/

In order for your stylesheets to work, you’ll have to add a directive to your Apache config file similar to the following. My project lives in /var/www/inheritance; yours might be different. (Don’t forget to restart Apache after you add this.)

Alias /inheritance/web/sf /var/www/inheritance/lib/vendor/symfony/data/web/sf

Lastly, you might need to modify web/frontend_dev.php. My dev server is not the same machine that I use to look at what I’m developing, so I can’t restrict access to 127.0.0.1. For this temporary little project, it’s safe enough for me just to allow access from anywhere.

// this check prevents access to debug front controllers that are deployed by accident to production servers.
// feel free to remove this, extend it or make something more sophisticated.
/*
if (!in_array(@$_SERVER['REMOTE_ADDR'], array('127.0.0.1', '::1')))
{
die('You are not allowed to access this file. Check '.basename(__FILE__).' for more information.');
}
*/

require_once(dirname(__FILE__).'/../config/ProjectConfiguration.class.php');

$configuration = ProjectConfiguration::getApplicationConfiguration('frontend', 'dev', true);
sfContext::createInstance($configuration)->dispatch();

That’s all.

Why You Should Use Database Transactions

Tuesday, December 29th, 2009

Database transactions are an important part of keeping your database consistent. It seems that a lot of developers haven’t been exposed to transactions or choose not to use them, which is unfortunate because it’s easier for your data to get corrupted than you might think.

Take this, for example: you have a database with two tables, owner and pet, where each owner has zero or more pets. Below is an implementation in MySQL. If you have MySQL installed, you should be able to log into MySQL by doing the following:

mysql -u youruser -p

You will be prompted for a password and then you’ll be in the MySQL CLI.

First, let’s create the database.

mysql> create database example;
Query OK, 1 row affected (0.00 sec)

Then tell MySQL you want to be using the database called example.

mysql> use example
Database changed

Create the owner table.

mysql> create table owner (
owner_id int unsigned auto_increment not null,
primary key (owner_id),
name varchar(30) not null
) engine=innodb;
Query OK, 0 rows affected (0.11 sec)

Create the pet table.

mysql> create table pet (
pet_id int unsigned auto_increment not null,
primary key (pet_id),
owner_id int unsigned not null,
foreign key (owner_id) references owner (owner_id),
name varchar(30) not null
) engine=innodb;
Query OK, 0 rows affected (0.02 sec)
mysql> show tables;
+-------------------+
| Tables_in_example |
+-------------------+
| owner             |
| pet               |
+-------------------+
2 rows in set (0.00 sec)

Now you can see that the two tables have been successfully added. Let’s add some data to the database:

mysql> insert into owner (name) values ('Alice'), ('Bob');
Query OK, 2 rows affected (0.00 sec)
Records: 2  Duplicates: 0  Warnings: 0
mysql> select * from owner;
+----------+-------+
| owner_id | name  |
+----------+-------+
|        1 | Alice |
|        2 | Bob   |
+----------+-------+
2 rows in set (0.00 sec)
mysql> insert into pet (owner_id, name) values
(1, 'Fido'), (2, 'Scruffy'), (2, 'Hoppy the Frog');
Query OK, 3 rows affected (0.00 sec)
Records: 3  Duplicates: 0  Warnings: 0

If we select owners and pets from our database, we should see that Alice has a pet named Fido and that Bob has two pets: Hoppy the Frog and Scruffy.

mysql> select o.name owner_name, p.name pet_name
from owner o
join pet p on o.owner_id = p.owner_id
order by o.name;
+------------+----------------+
| owner_name | pet_name       |
+------------+----------------+
| Alice      | Fido           |
| Bob        | Scruffy        |
| Bob        | Hoppy the Frog |
+------------+----------------+
3 rows in set (0.00 sec)

There they are. Below, we’ll be going through some examples that delete records. After each example, we could just manually re-insert the records we deleted, but that would get old fast. Instead, let’s back up our database using mysqldump. Log out of the MySQL CLI (or open a new terminal) and type this:

mysqldump example -u youruser -p > example_backup.sql

We’ll be able to use example_backup.sql at anytime in the future to restore our database.

Now let’s say Alice is no longer a customer of ours and we want to remove her from our database. This will be a two-part operation:

  1. Delete Alice’s pet(s) from the pet table
  2. Delete Alice from the owner table

Note carefully that the order of these two operations matters. We can’t delete Alice from the owner table before we delete her pets because pet.owner_id has a foreign key constraint that points to owner.owner_id. If you try to do it that way, you’ll get an error.

Let’s delete Alice’s pets and then delete Alice:

mysql> delete from pet where owner_id = 1;
Query OK, 1 row affected (0.00 sec)

mysql> delete from owner where owner_id = 1;
Query OK, 1 row affected (0.01 sec)

If we repeat our query from earlier, we should see that Alice is gone and Bob is still there.

mysql> select o.name owner_name,
    -> p.name pet_name
    -> from owner o
    -> join pet p on o.owner_id = p.owner_id
    -> order by o.name;
+------------+----------------+
| owner_name | pet_name       |
+------------+----------------+
| Bob        | Scruffy        |
| Bob        | Hoppy the Frog |
+------------+----------------+
2 rows in set (0.00 sec)

That looks right, but let’s query each table individually to make sure:

mysql> select * from owner;
+----------+------+
| owner_id | name |
+----------+------+
|        2 | Bob  |
+----------+------+
1 row in set (0.00 sec)

mysql> select * from pet;
+--------+----------+----------------+
| pet_id | owner_id | name           |
+--------+----------+----------------+
|      2 |        2 | Scruffy        |
|      3 |        2 | Hoppy the Frog |
+--------+----------+----------------+
2 rows in set (0.00 sec)

Good, that’s what we expected. So if everything worked just fine without using any transactions, what’s the problem? Well, usually there is no problem, but it’s all too easy for a condition to arise where there will be a problem. I’ll explain in a moment, but first let’s restore our database:

mysql example -u youruser -p < example_backup.sql

Now that we have our original database back, let's imagine that your company's DBA decides that the owner table is poorly named and it should really be called user:

mysql> alter table owner rename user;
Query OK, 0 rows affected (0.00 sec)

mysql> show tables;
+-------------------+
| Tables_in_example |
+-------------------+
| pet               |
| user              |
+-------------------+
2 rows in set (0.00 sec)

The DBA informs the developers of this, but one of the developers at your company is lazy and doesn't like to read e-mails, so he never pays attention to the change, so he never updates his code. (Actually, this DBA could be you, and the person who forgets to update his code could very likely also be YOU! I just made them different people to show how easily this can happen.)

Now what happens when your deletion code runs?

mysql> delete from pet where owner_id = 1;
Query OK, 1 row affected (0.01 sec)

mysql> delete from owner where owner_id = 1;
ERROR 1146 (42S02): Table 'example.owner' doesn't exist

The pets got deleted just fine, but when we try to delete Alice, we get an error because the owner table no longer exists. What do we have in our database now?

mysql> select o.name owner_name, p.name pet_name
from user o left join pet p on o.owner_id = p.owner_id
order by o.name;
+------------+----------------+
| owner_name | pet_name       |
+------------+----------------+
| Alice      | NULL           |
| Bob        | Scruffy        |
| Bob        | Hoppy the Frog |
+------------+----------------+
3 rows in set (0.00 sec)

(Note that I changed the JOIN to a LEFT JOIN so that we'll see all users in the database, regardless of whether or not they have pets.)

As you can see, Alice is still there but her pets are gone. Is that what we wanted to do? Certainly not. Our database is now in an inconsistent state and we can't trust it. That's bad!

What if we had wrapped our deletion code in a transaction? We'll look at that, but first let's restore our table again:

mysql example -u youruser -p < example_backup.sql

Let me pause for a moment to explain how transactions are usually coded. In your server-side script (e.g. PHP, Python, ASP), you'll usually have something similar to this:

class Owner
{
  // Delete an owner and his or her pets.
  function delete()
  {
    // Start the database transaction.
    $db->startTransaction();
    try {
      $this->deletePets(); // Contains "DELETE FROM pet..." SQL
      $this->deleteSelf(); // Contains "DELETE FROM owner..." SQL
      // If everything went okay, commit the change.
      $db->commit();
    } catch (Exception $e) {
      // If there was an error, roll back the transaction,
      // leaving the database untouched.
      $db->rollback();
    }
  }
}

Before we delete an owner, we say to the database, "Hey database, what we're about to do might not work. Hold on for a second and don't actually make these changes. Just pretend like we're making the changes."

If anything went wrong in the process of deleting a pet or owner, an exception will be raised so that our server-side script knows whether or not the operation was successful. If no exception was raised, we say, "Okay, database. It worked. Go ahead and make those changes for real." Otherwise we say, "Looks like that's not going to work after all. Just forget about the whole thing."

Since the examples I'm showing here are scripting-language-agnostic, I'm just going to show you some transaction examples in plain SQL.

If you run a SHOW TABLES command when you log back into the MySQL CLI, you might be slightly surprised:

mysql> show tables;
+-------------------+
| Tables_in_example |
+-------------------+
| owner             |
| pet               |
| user              |
+-------------------+
3 rows in set (0.00 sec)

Why didn't mysqldump restore our table back to what it originally was? Well, all we're doing when we restore is playing back the original creation of the database, not wiping everything out and then starting anew. So we'll have get rid of the user table manually:

mysql> drop table user;
Query OK, 0 rows affected (0.03 sec)

mysql> show tables;
+-------------------+
| Tables_in_example |
+-------------------+
| owner             |
| pet               |
+-------------------+
2 rows in set (0.00 sec)

Now let's do our deletion again, but this time let's start a transaction first:

mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)

mysql> delete from pet where owner_id = 1;
Query OK, 1 row affected (0.00 sec)

mysql> delete from owner where owner_id = 1;
Query OK, 1 row affected (0.01 sec)

mysql> select * from owner;
+----------+------+
| owner_id | name |
+----------+------+
|        2 | Bob  |
+----------+------+
1 row in set (0.00 sec)

mysql> select * from pet;
+--------+----------+----------------+
| pet_id | owner_id | name           |
+--------+----------+----------------+
|      2 |        2 | Scruffy        |
|      3 |        2 | Hoppy the Frog |
+--------+----------+----------------+
2 rows in set (0.00 sec)

At this point it looks like everything went as expected. Alice and her pets are gone. If we roll back, however, the changes don't stick.

mysql> rollback;
Query OK, 0 rows affected (0.00 sec)

mysql> select * from owner;
+----------+-------+
| owner_id | name  |
+----------+-------+
|        1 | Alice |
|        2 | Bob   |
+----------+-------+
2 rows in set (0.00 sec)

mysql> select * from pet;
+--------+----------+----------------+
| pet_id | owner_id | name           |
+--------+----------+----------------+
|      1 |        1 | Fido           |
|      2 |        2 | Scruffy        |
|      3 |        2 | Hoppy the Frog |
+--------+----------+----------------+
3 rows in set (0.00 sec)

Let's do all the deletion stuff over again, this time committing:

mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)

mysql> delete from pet where owner_id = 1;
Query OK, 1 row affected (0.00 sec)

mysql> delete from owner where owner_id = 1;
Query OK, 1 row affected (0.00 sec)

mysql> commit;
Query OK, 0 rows affected (0.00 sec)

mysql> select * from owner;
+----------+------+
| owner_id | name |
+----------+------+
|        2 | Bob  |
+----------+------+
1 row in set (0.01 sec)

mysql> select * from pet;
+--------+----------+----------------+
| pet_id | owner_id | name           |
+--------+----------+----------------+
|      2 |        2 | Scruffy        |
|      3 |        2 | Hoppy the Frog |
+--------+----------+----------------+
2 rows in set (0.00 sec)

That exactly what you would expect, right? We made the changes, then committed them, so they stuck. Let's redo our example where the owner table got renamed to user, but this time let's use a transaction so we don't corrupt our data. But first we have to restore the database one final time.

mysql example -u youruser -p < example_backup.sql
mysql> alter table owner rename user;
Query OK, 0 rows affected (0.01 sec)

mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)

mysql> delete from pet where owner_id = 1;
Query OK, 1 row affected (0.00 sec)

mysql> delete from owner where owner_id = 1;
ERROR 1146 (42S02): Table 'example.owner' doesn't exist

There's our error again. But instead of being screwed this time, we can simply roll back:

mysql> rollback;
Query OK, 0 rows affected (0.01 sec)

mysql> select * from user;
+----------+-------+
| owner_id | name  |
+----------+-------+
|        1 | Alice |
|        2 | Bob   |
+----------+-------+
2 rows in set (0.00 sec)

mysql> select * from pet;
+--------+----------+----------------+
| pet_id | owner_id | name           |
+--------+----------+----------------+
|      1 |        1 | Fido           |
|      2 |        2 | Scruffy        |
|      3 |        2 | Hoppy the Frog |
+--------+----------+----------------+
3 rows in set (0.00 sec)

Thanks to transactions, our biggest problem went from being a corrupt database (a terrible thing to have) to being an error that we can catch and report without affecting our data. If you value your data integrity at all - and it would be very silly not to - you should use database transactions.