Currently Available: Need a skilled Software Developer for your next project?
Categories
Laravel

Wrap Complex Laravel Migrations in Transactions

Ever messed up a production database with a failed migration? Yeah, me too. Let's fix that.

The Basics

Laravel's Schema Builder already wraps schema changes in transactions. This means simple stuff like creating tables or adding columns is already safe:

Schema::table('users', function (Blueprint $table) {
    $table->string('new_column');
});

When Things Get Messy

But real migrations often do more than just schema changes. Maybe you're:

  • Converting data formats
  • Updating thousands of rows
  • Doing complex JSON transformations
  • Making your app multilingual

That's when you need to handle transactions yourself.

The Right Way to Do It

Here's the pattern you want to use:

public function up()
{
    // 1. Schema changes - let Laravel handle transactions
    Schema::table('products', function (Blueprint $table) {
        $table->text('name')->change();
    });

    // 2. Data updates - wrap in your own transaction
    DB::beginTransaction();
    try {
        DB::table('products')->update([
            'name' => DB::raw("JSON_OBJECT('en', name)")
        ]);
        DB::commit();
    } catch (\Exception $e) {
        DB::rollBack();
        throw $e;
    }

    // 3. More schema changes - Laravel's got this
    Schema::table('products', function (Blueprint $table) {
        $table->json('name')->change();
    });
}

What NOT to Do

Don't try to wrap Schema changes in your own transactions:

// This will blow up with "There is no active transaction"
DB::beginTransaction();
Schema::table('products', function (Blueprint $table) {
    $table->string('name');
});

The TL;DR

  1. Let Laravel handle transactions for schema changes
  2. Wrap data modifications in your own transactions
  3. Keep schema changes and data updates separate
  4. Always include try-catch with rollback
  5. Re-throw exceptions so Laravel knows the migration failed

That's it. Now your migrations won't leave your database in a weird state when they fail.

Pro Tip

For huge data updates, consider chunking:

DB::beginTransaction();
try {
    DB::table('huge_table')->orderBy('id')->chunk(1000, function ($records) {
        foreach ($records as $record) {
            // Do your updates
        }
    });
    DB::commit();
} catch (\Exception $e) {
    DB::rollBack();
    throw $e;
}
What I'm building

Delegate tasks. Get software.

Give Vroni a GitHub issue, bug report, spec, or rough idea. It reads the repo, plans the change, writes code, runs checks, and works toward a review-ready pull request.

Take a look at vroni.com

Subscribe to my newsletter

Get new posts when I publish them.

I respect your privacy. Unsubscribe at any time.

Leave a Reply

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