@MarkLb

Как заставить миграцию, в которой произошла ошибка, откатывать сделанные изменения в Yii2?

Несколько раз замечал, что если в процессе выполнения миграции возникает ошибка — сделанные изменения до ошибки не откатываются.

Пример такого случая:
Миграция

<?php

use yii\db\Migration;

/**
 * Handles the creation of table `{{%merchant}}`.
 */
class m221219_123909_create_merchant_table extends Migration
{
    /**
     * {@inheritdoc}
     */
    public function safeUp()
    {
        $tableOptions = 'CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE=InnoDB';
        $this->truncateTable('{{%webhook_log}}');
        $this->db->createCommand('DELETE FROM `payment_request`')->execute();
        $this->createTable('{{%merchant}}', [
            'merchant_uuid' => $this->string(36)->unique()->notNull(),
            'user_id' => $this->integer()->notNull(),
            'name' => $this->string(255)->notNull(),
            'url' => $this->string(255)->notNull(),
            'image' => $this->string(255)->null(),
            'commission' => $this->tinyInteger(1)->notNull()->defaultValue(1),
            'commission_percent' => $this->tinyInteger(3)->notNull()->defaultValue(0),
            'trc_wallet' => $this->string(128)->notNull(),
            'webhook_url' => $this->string(255)->notNull(),
            'status' => $this->integer()->notNull()->defaultValue(10),
            'public_key' => $this->string(64)->notNull()->unique(),
            'private_key' => $this->string(128)->notNull(),
            'created_at' => $this->integer()->notNull(),
            'updated_at' => $this->integer()->null(),
        ], $tableOptions);
        $this->addPrimaryKey('merchant_PK', '{{%merchant}}', 'merchant_uuid');
        $this->createIndex('user_id', '{{%merchant}}', 'user_id');
        $this->addForeignKey('FK_merchant__user_id__user__id', '{{%merchant}}', 'user_id', '{{%user}}', 'id', 'CASCADE');

        if (Yii::$app->db->schema->getTableSchema('{{%user_api_key}}')) {
            $this->dropTable('{{%user_api_key}}');
        }

        $this->dropForeignKey('FK_payment_request__user_id', '{{%payment_request}}');
        $this->dropColumn('{{%payment_request}}', 'user_id');
        $this->addColumn('{{%payment_request}}', 'merchant_uuid', $this->string(36)->notNull()->after('id'));
        $this->createIndex('payment_request__merchant_uuid', '{{%payment_request}}', 'merchant_uuid');
        $this->addForeignKey('FK_payment_request__merchant_uuid', '{{%payment_request}}', 'merchant_uuid', '{{%merchant}}', 'merchant_uuid');
    }

    /**
     * {@inheritdoc}
     */
    public function safeDown()
    {
        $tableOptions = 'CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE=InnoDB';
        $this->truncateTable('{{%webhook_log}}');
        $this->db->createCommand('DELETE FROM `payment_request`')->execute();
        $this->dropForeignKey('FK_payment_request__merchant_uuid', '{{%payment_request}}');
        $this->dropIndex('payment_request__merchant_uuid', '{{%payment_request}}');
        $this->dropColumn('{{%payment_request}}', 'merchant_uuid');
        $this->addColumn('{{%payment_request}}', 'user_id', $this->integer()->null()->after('id'));
        $this->createIndex('user_id', '{{%payment_request}}', 'user_id');
        $this->addForeignKey('FK_payment_request__user_id', '{{%payment_request}}', 'user_id', '{{%user}}', 'id', 'CASCADE');
        $this->createTable('{{%user_api_key}}', [
            'id' => $this->primaryKey(),
            'user_id' => $this->integer()->notNull(),
            'public_key' => $this->string(64)->notNull(),
            'private_key' => $this->string(128)->notNull(),
            'created_at' => $this->integer(),
        ], $tableOptions);

        $this->createIndex('user_id', '{{%user_api_key}}', 'user_id');
        $this->addForeignKey('user_id__user__id', '{{%user_api_key}}', 'user_id', '{{%user}}', 'id', 'CASCADE');

        $this->dropForeignKey('FK_merchant__user_id__user__id', '{{%merchant}}');
        $this->dropIndex('user_id', '{{%merchant}}');
        $this->dropPrimaryKey('merchant_PK', '{{%merchant}}');
        $this->dropTable('{{%merchant}}');
    }
}



Допустим, миграция сломалась на строке:
$this->dropForeignKey('FK_payment_request__user_id', '{{%payment_request}}');

Yii2 выведет ошибку, при этом не запишет миграцию в таблицу "migrations", поскольку она не будет считаться выполненной.

Мы внесем правки в коде и/или в БД, но при повторном запуске — миграция сломается на 3-ей команде:
$this->createTable('{{%merchant}}', [...
Потому что указанная таблица была создана предыдущим неудачным запуском миграции.

Итог: Теперь приходится "руками" вычищать сделанное предыдущей миграцией, и только после её запускать заново.
Вопрос: Как заставить миграцию, в которой произошла ошибка, откатывать сделанные изменения в Yii2?

Возможно, применить Транзакции? Но, насколько я знаю, safeUp отличается от safe именно использованием Транзакций.
  • Вопрос задан
  • 95 просмотров
Пригласить эксперта
Ответы на вопрос 2
Maksclub
@Maksclub
maksfedorov.ru
Особенности некоторых БД в том, что откатить транзакцией можно не все (например создание таблицы)

Делайте такие миграции, которые корректные и руками дорабатывайте нюансы :)
Ну или разделите миграции, где создаются таблицы и где добавляются/удаляются/изменяются колонки и ограничения
Ответ написан
@PiloTeZ
...
Транзакциями тут не поможешь, поэтому только проверять наличие FK вручную перед его добавлением. Можно доработать базовый класс, что бы не делать это вручную.
А вообще такое не часто встречается, просто надо хорошо тестировать миграции перед накатыванием на прод
Ответ написан
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы