处理laravel中的死锁异常

Sae*_*iry 7 mysql laravel eloquent

Laravel当我使用执行某些插入/更新查询时,我的应用程序中出现此错误Laravel Eloquent

SQLSTATE[40001]: Serialization failure: 1213 Deadlock found
Run Code Online (Sandbox Code Playgroud)

我怎样才能重新执行查询直到它完成?

Ale*_*dar 7

以下是在 Laravel 5 中执行此操作的方法(在版本 5.7 和 5.8 上测试):

$numberOfAttempts = 5; // how many times the transaction will retry
// Since we are passing a closure, we need to send
// any "external" variables to it with the `use` keyword
DB::transaction(function () use ($user, $somethingElse) {
    // this is just an example
    $user->update(...);
    $somethingElse->delete();
}, $numberOfAttempts);
Run Code Online (Sandbox Code Playgroud)


Bus*_*kot 6

这个解决方案适用于 Laravel 5.1,但我相信它可以用于框架的新版本,只需稍作改动。

以下代码假定默认数据库连接名称为“ mysql ”。在config/database.php字段中检查它default

创建新类扩展Illuminate\Database\MySqlConnection

namespace App\Helpers\MySQL;

use Closure;
use Exception;
use Illuminate\Database\MySqlConnection;
use Illuminate\Database\QueryException;
use Log;
use PDOException;

/**
 * Class DeadlockReadyMySqlConnection
 *
 * @package App\Helpers
 */
class DeadlockReadyMySqlConnection extends MySqlConnection
{
    /**
     * Error code of deadlock exception
     */
    const DEADLOCK_ERROR_CODE = 40001;

    /**
     * Number of attempts to retry
     */
    const ATTEMPTS_COUNT = 3;

    /**
     * Run a SQL statement.
     *
     * @param  string    $query
     * @param  array     $bindings
     * @param  \Closure  $callback
     * @return mixed
     *
     * @throws \Illuminate\Database\QueryException
     */
    protected function runQueryCallback($query, $bindings, Closure $callback)
    {
        $attempts_count = self::ATTEMPTS_COUNT;

        for ($attempt = 1; $attempt <= $attempts_count; $attempt++) {
            try {
                return $callback($this, $query, $bindings);
            } catch (Exception $e) {
                if (((int)$e->getCode() !== self::DEADLOCK_ERROR_CODE) || ($attempt >= $attempts_count)) {
                    throw new QueryException(
                        $query, $this->prepareBindings($bindings), $e
                    );
                } else {
                    $sql = str_replace_array('\?', $this->prepareBindings($bindings), $query);
                    Log::warning("Transaction has been restarted. Attempt {$attempt}/{$attempts_count}. SQL: {$sql}");
                }
            }
        }

    }
}
Run Code Online (Sandbox Code Playgroud)

扩展基本连接工厂Illuminate\Database\Connectors\ConnectionFactory

namespace App\Helpers\MySQL;

use Config;
use Illuminate\Database\Connectors\ConnectionFactory;
use Illuminate\Database\MySqlConnection;
use Illuminate\Database\PostgresConnection;
use Illuminate\Database\SQLiteConnection;
use Illuminate\Database\SqlServerConnection;
use InvalidArgumentException;
use PDO;

/**
 * Class YourAppConnectionFactory
 *
 * @package App\Helpers\MySQL
 */
class YourAppConnectionFactory extends ConnectionFactory
{
    /**
     * Create a new connection instance.
     *
     * @param  string   $driver
     * @param  PDO     $connection
     * @param  string   $database
     * @param  string   $prefix
     * @param  array    $config
     * @return \Illuminate\Database\Connection
     *
     * @throws InvalidArgumentException
     */
    protected function createConnection($driver, PDO $connection, $database, $prefix = '', array $config = [])
    {
        if ($this->container->bound($key = "db.connection.{$driver}")) {
            return $this->container->make($key, [$connection, $database, $prefix, $config]);
        }

        switch ($driver) {
            case 'mysql':
                if ($config['database'] === Config::get('database.connections.mysql.database')) {
                    return new DeadlockReadyMySqlConnection($connection, $database, $prefix, $config);
                } else {
                    return new MySqlConnection($connection, $database, $prefix, $config);
                }
            case 'pgsql':
                return new PostgresConnection($connection, $database, $prefix, $config);
            case 'sqlite':
                return new SQLiteConnection($connection, $database, $prefix, $config);
            case 'sqlsrv':
                return new SqlServerConnection($connection, $database, $prefix, $config);
        }

        throw new InvalidArgumentException("Unsupported driver [$driver]");
    }
}
Run Code Online (Sandbox Code Playgroud)

现在我们应该替换标准框架的数据库连接工厂Providers/AppServiceProvider.php(或创建新的服务提供者)

public function register()
{
    $this->app->singleton('db.factory', function ($app) {
        return new YourAppConnectionFactory($app);
    });
}
Run Code Online (Sandbox Code Playgroud)

就是这样!现在所有因死锁而失败的查询都应该重新启动。