Laravel的DB::transaction在异常情况下未回滚

17

我在使用Laravel 4.2和DB::transaction时遇到了一个小问题。由于事务没有被回滚,我尝试了最简单的代码片段,并将其放入routes.php进行测试:

routes.php:

DB::transaction(function(){

  $user = App::make("User");
  $user->save();
  throw new Exception("Should not create users");
});
...
...
... 
Some other code here 
我试图在一个事务闭包中创建用户,当创建完用户后,我抛出异常来强制回滚事务。但是即使抛出异常,事务也不会回滚。每次刷新应用程序时,在数据库中都会得到一个新的用户。这段代码在本地机器上按预期工作,但在我计划用于生产的服务器上却无法回滚事务。你有任何想法为什么吗?
编辑:
服务器 MySql 版本为 5.1.73-cll - MySQL Community Server (GPLv2)
服务器 PHP 版本为 PHP 5.4.30 (cli) (built: Jul 19 2014 15:22:18)
本地 PHP 版本为 5.5.9
本地 MySql 版本为 5.6.16
服务器运行在 CentOS 上,而本地机器是 Windows 7。

你的本地环境和生产环境中PHP和MySQL(或其他数据库)的版本是否相同? - rmobis
编辑已经完成,以反映两个环境。 - MikeWu
5个回答

30

所以我在回答自己的问题。 直到MySql 5.5之前,InnoDB不是默认的存储引擎。 在我的情况下,默认的存储引擎是MYISAM,它不支持事务。 我需要在CPanel服务器安装的MySQL中启用InnoDB。 然后,我必须确保Laravel迁移中的每个表都使用InnoDB引擎创建。 我通过添加以下内容来实现:

     $table->engine = "InnoDB"; 

对于每个迁移文件都需要设置表的InnoDB引擎,一旦所有表格都设置好了,事务就会按预期工作。


16
  1. 请确保在您的 databases.php 连接数组配置中设置了 'engine' => 'InnoDB'

  2. 如果您的项目使用多个数据库连接,您需要指定要提交和回滚的连接。

    DB::connection('name_of_connection')->beginTransaction();
    try {
        //Do something
        DB::connection('name_of_connection')->commit();
    } catch (Exception $e) {
        DB::connection('name_of_connection')->rollback();
    }

1
DB连接将使用app/config/database.php中数据库连接的名称,而不是数据库的名称。 - Dip Ghosh

15

一个替代方案是更改

/config/database.php

中引擎值的设置,从null改为InnoDB。

'mysql' => [
            'driver' => 'mysql',
            'host' => env('DB_HOST', '127.0.0.1'),
            'port' => env('DB_PORT', '3306'),
            'database' => env('DB_DATABASE', 'forge'),
            'username' => env('DB_USERNAME', 'forge'),
            'password' => env('DB_PASSWORD', ''),
            'charset' => 'utf8',
            'collation' => 'utf8_unicode_ci',
            'prefix' => '',
            'strict' => true,
            'engine' => 'InnoDB',
        ],

2
如果您仍然无法回滚事务并且已经在使用InnoDB,则可能是由于您正在运行的查询引起的。
例如,MySQL存在一个错误/特性,即TRUNCATE 有一个隐式提交,因此表格在尝试回滚前已保存。解决方法是改用DELETE FROM,然后使用ALTER TABLE <table> AUTO_INCREMENT = 1(如果希望保留重置自增id的行为)。
我建议尝试在php artisan tinker中运行基本查询,例如:
DB::transaction(function () {
    //DB::statement('SET FOREIGN_KEY_CHECKS = 0;');

    $autoIncrement = DB::select("SHOW TABLE STATUS LIKE 'users'")[0]->Auto_increment;

    $id = DB::table('users')->insertGetId([
        'name' => 'Jane Doe',
        'email' => 'example@example.com',
        'password' => 'password'
    ]);

    DB::table('users')->where('id', $id)->delete();

    // ALTER TABLE has implicit commit just like TRUNCATE, so prevents rollback:
    //DB::statement("ALTER TABLE `users` AUTO_INCREMENT = " . ((int) $autoIncrement));
    //DB::statement("ALTER TABLE `users` AUTO_INCREMENT = ?", [1]); // doesn't work
    //DB::statement("ALTER TABLE `users` AUTO_INCREMENT = :incrementStart", ['incrementStart' => 1]); // still doesn't work

    //DB::statement('SET FOREIGN_KEY_CHECKS = 1;');

    dd("User $id deleted, rolling back...");
});

请注意语句中的不一致性,其中一个地方是'users',另一个地方是`users`。有时候不引用关键字也可以工作。
请注意在原始语句中无法转义参数的危险,我不得不将$autoIncrement强制转换为整数以确保安全。如果有人知道解决方法,请告诉我们!
我添加了更多的行来玩耍,因为查询也可能由于外键约束而失败。
谨防勘误、未实现的用例和MySQL、PostgreSQL等引擎之间的不一致性。这里有风险。
更多信息: transaction doesn't work on truncate https://laracasts.com/discuss/channels/eloquent/db-transaction-doesnt-appear-to-rollback-properly Laravel 的原始 SQL 函数的区别 我可以在 ALTER TABLE 中使用事务吗?

0
如果你的数据库是MySQL并且已经创建好了,那么在config/database中设置InnoDB。
'mysql' => [
            'driver' => 'mysql',
            'host' => env('DB_HOST', '127.0.0.1'),
            'port' => env('DB_PORT', '3306'),
            'database' => env('DB_DATABASE', 'forge'),
            'username' => env('DB_USERNAME', 'forge'),
            'password' => env('DB_PASSWORD', ''),
            'charset' => 'utf8',
            'collation' => 'utf8_unicode_ci',
            'prefix' => '',
            'strict' => true,
            'engine' => 'InnoDB',
        ],

使用以下命令重新创建您的数据库

php artisan migrate:fresh --seed 

这将使用InnoDB引擎创建数据库,并且事务将正常工作。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接