AutoRetryMySqlConnection.php 2.13 KB
<?php

namespace App\Database\MySQL;

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

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

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

    /**
     * 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 parent::runQueryCallback($query, $bindings, $callback);
            } catch (QueryException $e) {
                if ($attempt > $attempts_count) {
                    throw $e;
                }

                if (!$this->shouldRetry($errorCode = $e->getCode())) {
                    throw $e;
                }

                $this->logRetry($attempt, $attempts_count, $bindings, $query, $errorCode);
            }
        }
    }

    /**
     * Use the provided error code to determine if the transaction should be retried.
     *
     * @param string|integer $errorCode
     *
     * @return boolean
     */
    protected function shouldRetry($errorCode)
    {
        return (int)$errorCode === self::DEADLOCK_ERROR_CODE;
    }

    /**
     * Log when a transaction is automatically retried.
     *
     * @param integer $attempt
     * @param integer $attempts_count
     * @param array $bindings
     * @param string $query
     * @param string $errorCode
     * @return void
     */
    protected function logRetry($attempt, $attempts_count, $bindings, $query, $errorCode)
    {
        Log::warning("Transaction has been restarted due to error {$errorCode}. Attempt {$attempt}/{$attempts_count}. SQL: {$query} " . print_r($bindings, true));
    }
}