Транзакция Laravel

При разработке у меня так много проблем с миграциями в laravel.

Я создаю миграцию. Когда я завершу его создание, небольшая ошибка в середине миграции (скажем, ограничение внешнего ключа), которая приводит к ошибке "php artisan migrate". Он говорит мне, где ошибка, действительно, но затем миграция переходит в несогласованное состояние, где сделаны все изменения в базе данных, сделанные до ошибки, а не следующие.

Это делает это, когда я исправляю ошибку и повторно запускаю migrate, первый оператор терпит неудачу, поскольку столбец/таблица уже создан/изменен. Тогда единственное решение, которое я знаю, - это перейти в мою базу данных и "откат" вручную, что намного дольше.

migrate: rollback пытается отменить предыдущие миграции, так как текущий не был успешно применен.

Я также попытался обернуть весь мой код в DB:: transaction(), но он все еще не работает.

Есть ли какое-либо решение для этого? Или мне просто нужно постоянно перекатывать вещи?




редактировать, добавляя пример (не записывая код компоновщика схемы, просто какой-то псевдокод):
Migration1:

Create Table users (id, name, last_name, email)

Миграция1 выполнена ОК. Через несколько дней мы делаем Migration 2:

Create Table items (id, user_id references users.id)
Alter Table users make_some_error_here

Теперь произойдет то, что migrate вызовет первый оператор и создаст элементы таблицы с его внешним ключом для пользователей. Затем, когда он попытается применить следующее утверждение, он потерпит неудачу.

Если мы исправим make_some_error_here, мы не сможем выполнить миграцию, потому что созданная таблица "элементы". Мы не можем откатить (и не обновить, ни reset), потому что мы не можем удалить пользователей таблицы, так как есть ограничение внешнего ключа из элементов таблицы.

Тогда единственный способ продолжить - это перейти в базу данных и вручную удалить элементы таблицы, чтобы перейти в согласованное состояние.

Ответы

Ответ 1

Это не ограничение Laravel, держу пари, вы используете MYSQL, верно?

Как MYSQL документация говорит здесь

Некоторые заявления не могут быть отменены. Как правило, они включают в себя операторы языка определения данных (DDL), такие как те, которые создают или отбрасывают базы данных, те, которые создают, отбрасывают или изменяют таблицы или хранимые подпрограммы.

И у нас есть рекомендации самого Тейлора Otwell здесь, говоря:

Мой лучший совет - выполнять одну операцию на миграцию, чтобы ваши миграции оставались очень детальными.

Ответ 2

Я использую MySql, и у меня есть эта проблема.

Мое решение зависит от того, что ваш метод down() делает именно то, что вы делаете в up(), но назад.

Вот что я хочу:

try{
    Schema::create('table1', function (Blueprint $table) {
        //...
    });
    Schema::create('tabla2', function (Blueprint $table) {
        //...
    });
}catch(PDOException $ex){
    $this->down();
    throw $ex;
}

Итак, если что-то не удается автоматически вызывает метод down() и снова генерирует исключение.

Вместо использования перехода между transaction() сделайте это между этой попыткой

Ответ 3

Как сказал Евгений Афанасьев, Тейлор Отуэлл сказал (но подход, который я уже взял): ваши миграции работают только на определенных таблицах или выполняют определенную операцию, например, добавление/удаление столбца или ключа. Таким образом, когда вы получаете неудачные миграции, которые вызывают несогласованные состояния, подобные этому, вы можете просто отбросить таблицу и снова выполнить миграцию.

Я испытал именно то, о чем вы говорили, но пока еще не нашел пути вокруг него.

Ответ 4

Просто удалите неудавшийся код из файла миграции и создайте новую миграцию для отказавшего оператора. Теперь, когда он терпит неудачу, создание базы данных по-прежнему неповреждено, поскольку оно живет в другом файле миграции.

Другим преимуществом использования этого подхода является то, что при обращении к БД у вас больше контроля и меньших шагов.

Надеюсь, что это поможет: D

Ответ 5

Я знаю, это старая тема, но месяц назад была активность, поэтому вот мои 2 цента.

Этот ответ для MySql 8 и Laravel 5.8 MySql, начиная с MySql 8, представил атомарный DDL: https://dev.mysql.com/doc/refman/8.0/en/atomic-ddl.html Laravel в начале миграции проверяет, поддерживает ли грамматика схемы миграции в транзакции и запускает ли она ее как таковую. Проблема в том, что в грамматике схемы MySql установлено значение false. Мы можем расширить Migrator, грамматику схемы MySql и MigrationServiceProvider, а также зарегистрировать поставщика услуг следующим образом:

<?php

namespace App\Console;

use Illuminate\Database\Migrations\Migrator as BaseMigrator;

use App\Database\Schema\Grammars\MySqlGrammar;

class Migrator extends BaseMigrator {
    protected function getSchemaGrammar( $connection ) {
        if ( get_class( $connection ) === 'Illuminate\Database\MySqlConnection' ) {
            $connection->setSchemaGrammar( new MySqlGrammar );
        }
        if ( is_null( $grammar = $connection->getSchemaGrammar() ) ) {
            $connection->useDefaultSchemaGrammar();
            $grammar = $connection->getSchemaGrammar();
        }
        return $grammar;
    }
}


<?php

namespace App\Database\Schema\Grammars;

use Illuminate\Database\Schema\Grammars\MySqlGrammar as BaseMySqlGrammar;

class MySqlGrammar extends BaseMySqlGrammar {
    public function __construct() {
        $this->transactions = config( "database.transactions", false );
    }
}


<?php

namespace App\Providers;

use Illuminate\Database\MigrationServiceProvider as BaseMigrationServiceProvider;

use App\Console\Migrator;

class MigrationServiceProvider extends BaseMigrationServiceProvider {   
    /**
     * Register the migrator service.
     * @return void
     */
    protected function registerMigrator() {
        $this->app->singleton( 'migrator', function( $app ) {
            return new Migrator( $app[ 'migration.repository' ], $app[ 'db' ], $app[ 'files' ] );
        } );
        $this->app->singleton(\Illuminate\Database\Migrations\Migrator::class, function ( $app ) {
            return $app[ 'migrator' ];
        } );
    }


<?php

return [
    'providers' => [

        /*
         * Laravel Framework Service Providers...
         */
        App\Providers\MigrationServiceProvider::class,

    ],
];

Конечно, мы должны добавить транзакции в нашу конфигурацию базы данных... ОТКАЗ ОТ ОТВЕТСТВЕННОСТИ - Еще не тестировал, но, глядя только на код, он должен работать как рекламируется :) Обновление, чтобы следовать, когда я тестирую...

Ответ 6

UPDATE: мое решение ниже на самом деле не работает (если вы используете Mysql, скорее всего), см. комментарии. Он работает только для операторов delete/insert/update. Мой совет, как говорили другие, разделить ваши миграции на несколько миграций, которые не оставляют ничего позади, когда один не удается.


Я часто сталкивался с этой проблемой раньше. Решение состоит в том, чтобы начать транзакцию в начале вашей миграции и зафиксировать после завершения всей работы:

<?php

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

class UpdateUserTableAddColumnName extends Migration {

  public function up()
  {
    DB::beginTransaction();

    Schema::table('user', function(Blueprint $table) {
      // update your table here
      $table->string('name')
    });

    DB::commit();
  }
}

Если миграция завершилась неудачно, ваша база данных по-прежнему по-прежнему предшествует миграции. Даже таблица миграции не обновляется.

Ответ 7

Я думаю, что лучший способ сделать это, как показано в документации:

DB::transaction(function () {
    DB::table('users')->update(['votes' => 1]);

    DB::table('posts')->delete();
});

Смотрите: https://laravel.com/docs/5.8/database#database-transactions