0

My query is taking too long to run when I use general filters, but it's fine with specific filters. I suspect it's an optimization issue.

To fix it, I'm trying to create a non-clustered index on a new column. This column will extract a specific value from a JSON attributes column.

The table has over 2 million rows. I ran the migration and it created the new column instantly, but it's hanging indefinitely while trying to populate the data. I'm also planning to create a composite index on three columns, including this new one.

What is the best way to backfill a column on a large table without causing the migration to fail or hang?

<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    public function up(): void
    {
        Schema::table('answer_tests', function (Blueprint $table) {
            $table->BigInteger('unity_id')->after('institution_id')->nullable();
        });

        DB::table('answer_tests')->orderBy('id')->chunkById(100, function ($rows) {
            foreach ($rows as $row) {
                DB::table('answer_tests')
                    ->where('id', $row->id)
                    ->update([
                        'unity_id' => DB::raw("JSON_UNQUOTE(JSON_EXTRACT(attributes, '$.class_school.unity.id'))"),
                    ]);
            }
        });

        Schema::table('answer_tests', function (Blueprint $table) {
            $table->index(['institution_id', 'unity_id', 'created_at'], 'idx_unity_institution_created_at');
        });

    }

    public function down(): void
    {
        Schema::table('answer_tests', function (Blueprint $table) {
            $table->dropIndex('idx_unity_institution_created_at');
            $table->dropColumn('unity_id');
        });
    }
};

1 Answer 1

3

running the backfill logic inside the migration file will most likely lock your db, or cause it to hang because you are running multiple db updates.
Try these steps:
1. Maintain the migration to just add the column keeping it simple and fast.
2. Create an artisan command with raw SQL to run the backfill function in chunks of 5k-10k or your preferred size
3. Create a new migration to create the index after backfill command runs

See sample code for the artisan command to backfill your table in chunks.

$batchSize = 5000;
$lastId = 0;

do {
    $updated = DB::update("
        UPDATE answer_tests
        SET unity_id = JSON_UNQUOTE(JSON_EXTRACT(attributes, '$.class_school.unity.id'))
        WHERE id > ? AND id <= ? AND unity_id IS NULL
    ", [$lastId, $lastId + $batchSize]);

    $lastId += $batchSize;
} while ($updated > 0);
Sign up to request clarification or add additional context in comments.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.