【问题标题】:Laravel migration transactionLaravel 迁移事务
【发布时间】:2013-12-23 14:37:48
【问题描述】:

在开发时,我在 laravel 中遇到了很多迁移问题。

我创建了一个迁移。当我完成创建它时,迁移中间有一个小错误(例如,外键约束),导致“php artisan migrate”失败。他确实告诉我错误在哪里,但随后 migrate 进入一个不一致的状态,在错误之前对数据库进行的所有修改都是在发生错误之前进行的,而不是接下来的。

这使得当我修复错误并重新运行迁移时,第一条语句失败,因为列/表已经创建/修改。然后我知道的唯一解决方案是转到我的数据库并手动“回滚”所有内容,这需要更长的时间。

migrate:rollback 尝试回滚以前的迁移,因为当前没有成功应用。

我也尝试将我的所有代码包装到一个 DB::transaction() 中,但它仍然不起作用。

有什么解决办法吗?或者我只需要继续用手滚动东西?



编辑,添加示例(不编写模式构建器代码,只是某种伪代码):
迁移1:

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

Migration1 执行正常。几天后,我们制作了迁移 2:

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

现在将发生的情况是 migrate 将调用第一条语句并使用他的外键创建表项给用户。然后,当他尝试应用下一条语句时,它将失败。

如果我们修复 make_some_error_here,我们将无法运行 migrate,因为它已创建表“items”。我们不能回滚(也不能刷新,也不能重置),因为我们不能删除表用户,因为表项有外键约束。

那么唯一继续的方法就是去数据库手动删除表项,让迁移保持一致状态。

【问题讨论】:

  • 确实,这很烦人。我也没有想出在 MySQL 事务中运行的方法。当我尝试时,它似乎完全忽略了它。
  • @Blossoming_Flower,MYSQL中的DDL语句无法回滚。阅读我的答案以获取更多详细信息和链接。谢谢。

标签: php laravel migrate


【解决方案1】:

这不是 Laravel 的限制,我敢打赌你用的是 MYSQL,对吧?

正如 MYSQL 文档所说 here

有些语句不能回滚。一般来说,这些包括数据 定义语言 (DDL) 语句,例如创建或 删除数据库,创建、删除或更改表或存储的数据库 例行公事。

我们有 Taylor Otwell 本人的推荐 here 说:

我最好的建议是每次迁移都执行一次操作,这样您的 迁移非常细化。

-- 更新--

别担心!

最佳实践说:

您永远不应该做出重大改变。

这意味着,在一次部署中,您可以创建新表和字段并部署使用它们的新版本。在下一次部署中,您将删除未使用的表和字段。

现在,即使您在这些部署中遇到问题,也不必担心迁移失败,工作版本无论如何都会使用功能数据结构。每次迁移只需一次操作,您很快就会发现问题。

【讨论】:

  • 如果我需要更改主键,这个不能在多次迁移中分开,如果过程中发生错误会产生很大的问题,需要创建临时表更改主键
  • @SpaceDogCS - 忘记“改变”这个词。您不会在持续集成方法中进行任何更改。您创建新元素,将它们投入使用,然后删除旧元素。
  • 那会是什么?在我的场景中,我有一个错误,即导出软件没有发送所需的字段作为表的主键(表有一组主键),所以我意识到该列不应该是主键的一部分关键,它自己的表是一个订单表,我应该创建其他表并在查询中加入它们吗?这不会更难处理吗?
  • 首先 - 迁移不应该带来错误。如果您有错误 - 您需要修复它们,然后遵循最佳实践以避免将来出现错误。其次 - 您允许“导出软件”处理您的数据库是错误的。每个数据库应该是一个项目。如果您想与其他项目共享数据,则需要通过 API 提供。您可以尝试处理 SQL 视图以用某种抽象替换 api 层,这样会更好,但这不是最佳实践。
  • 我得到了这个架构很差的项目,软件正在通过 API 导出,但它使用主键导出,因为订单是在桌面软件中生成并导出到 Web 的。我在迁移中犯了一个错误,现在才出现在特定的场景中。唯一的解决方案是更改我的主键
【解决方案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()之间的迁移,而是在此尝试之间进行

【讨论】:

  • 这是很多额外的工作。我会为你扩展我的答案。
  • 虽然它只需要几行代码,但我汗流浃背!谢谢!
  • 这是不正确的。如果您在创建 table1 时迁移失败,那么您将失败 - 我想您的失败是 schema::dropTable('table1') AND schema::dropTable('table2') 并且最后一个将失败!
  • @GiuseppeCapoluongo 它就在答案中。 “我的解决方案取决于你的 down() 方法与你在 up() 中所做的完全一样,但反过来。”
【解决方案3】:

就像 Yevgeniy Afanasyev 强调 Taylor Otwell 所说的(但我自己已经采用了一种方法):让您的迁移仅适用于特定表或执行特定操作,例如添加/删除列或键。这样,当您遇到导致此类不一致状态的迁移失败时,您可以删除表并再次尝试迁移。

我确实遇到了您所描述的问题,但到目前为止还没有找到解决方法。

【讨论】:

  • 我很高兴在提到 Taylor Otwell 的同一句话中找到我的名字。谢谢你,马丁。
  • "drop the table" :O 我认为这可能不是一个好主意
【解决方案4】:

只需从迁移文件中删除失败的代码并为失败的语句生成新的迁移。现在,当它再次失败时,数据库的创建仍然完好无损,因为它存在于另一个迁移文件中。

使用这种方法的另一个优点是,您在还原数据库时拥有更多的控制权和更小的步骤。

希望对你有所帮助:D

【讨论】:

  • 是这样工作的吗?用另一种方式解决了问题?
  • 这是一个解决方法。但是进行多件事情的迁移也很有用,我不希望有 10 个文件用于单个数据库更改。受您的回答启发,我现在所做的是,当我在迁移过程中遇到错误时,只需注释掉所有正确执行的部分。修复错误后,我取消注释,然后回滚并再次迁移以重新检查一切是否正常。这有点像你说的效果,但没有制作更多文件。仍然是一种解决方法。
  • olivarra1,我一开始以为你是对的,但后来发现 MYSQL 不支持 DDL 中的事务。它改变了我的想法。我写了一个单独的答案来解释我的愿景。谢谢。
【解决方案5】:

我知道这是一个老话题,但一个月前有活动,所以这是我的 2 美分。

此答案适用于 MySql 8 和 Laravel 5.8 MySql,从 MySql 8 开始,引入了 atomic 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】:

    我认为最好的方法是在文档中显示:

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

    见:https://laravel.com/docs/5.8/database#database-transactions

    【讨论】:

    【解决方案7】:

    大多数答案都忽略了一个非常重要的事实,即一种非常简单的方法来构建您的开发结构。如果要使所有迁移可逆通过播种机添加尽可能多的开发测试数据,那么当开发环境中的工匠迁移失败时,可以纠正错误然后做

    php artisan migrate:fresh --seed
    

    可选地加上一个 :rollback 来测试回滚。

    就我个人而言,artisan migrate:fresh --seed 是仅次于 artisan tinker 的第二常用的 artisan 命令。

    【讨论】:

      猜你喜欢
      • 2020-08-28
      • 2012-07-30
      • 2019-12-27
      • 2018-04-18
      • 2021-02-28
      • 2021-08-15
      • 1970-01-01
      相关资源
      最近更新 更多