I got this error in my Laravel
application when I execute some insert/update query by using Laravel Eloquent
SQLSTATE[40001]: Serialization failure: 1213 Deadlock found
How can I re-execute the query until it done?
runQueryCallback runs all the db executions(selects, inserts, updates). So,if it catches QueryException,as it does,and check if it is error4001 it repeat the command that would theoretically prevent deadlock.
A deadlock is a situation where different transactions are unable to proceed because each holds a lock that the other needs. Because both transactions are waiting for a resource to become available, neither ever release the locks it holds.
This solution works for Laravel 5.1, but I believe that it can be used for new versions of the framework with minor changes.
Following code assumes that the default database connection name is "mysql". Check it in config/database.php
field default
.
Create new class extending 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}");
}
}
}
}
}
Extend basic connection factory 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]");
}
}
And now we should replace standard framework's DB connection factory in Providers/AppServiceProvider.php
(or create new service provider)
public function register()
{
$this->app->singleton('db.factory', function ($app) {
return new YourAppConnectionFactory($app);
});
}
That's it! Now all queries failed on deadlocks should be restarted.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With