@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 именно использованием Транзакций.
  • Вопрос задан
  • 29 просмотров
Пригласить эксперта
Ответы на вопрос 2
Maksclub
@Maksclub
maksfedorov.ru
Особенности некоторых БД в том, что откатить транзакцией можно не все (например создание таблицы)

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

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

Войти через центр авторизации
Похожие вопросы
22 мар. 2023, в 01:49
5000 руб./за проект
22 мар. 2023, в 01:01
3000 руб./за проект
22 мар. 2023, в 00:50
15000 руб./за проект