mirror of
https://gitee.com/hyperf/hyperf.git
synced 2024-11-29 18:27:44 +08:00
Added an error count to the database connection to ensure that the connection can be reset when occur too many exceptions. (#6085)
This commit is contained in:
parent
5d2d69a80b
commit
d4f14fd945
@ -37,6 +37,7 @@
|
||||
- [#5994](https://github.com/hyperf/hyperf/pull/5994) Added `events` of `crontab` lifecycle.
|
||||
- [#6039](https://github.com/hyperf/hyperf/pull/6039) Support semantic crontab rules.
|
||||
- [#6082](https://github.com/hyperf/hyperf/pull/6082) Added `hyperf/stdlib` component.
|
||||
- [#6085](https://github.com/hyperf/hyperf/pull/6085) Added an error count to the database connection to ensure that the connection can be reset when occur too many exceptions.
|
||||
|
||||
## Optimized
|
||||
|
||||
|
@ -146,6 +146,11 @@ class Connection implements ConnectionInterface
|
||||
*/
|
||||
protected static array $beforeExecutingCallbacks = [];
|
||||
|
||||
/**
|
||||
* Error count for executing SQL.
|
||||
*/
|
||||
protected int $errorCount = 0;
|
||||
|
||||
/**
|
||||
* Create a new database connection instance.
|
||||
*
|
||||
@ -967,6 +972,11 @@ class Connection implements ConnectionInterface
|
||||
return static::$resolvers[$driver] ?? null;
|
||||
}
|
||||
|
||||
public function getErrorCount(): int
|
||||
{
|
||||
return $this->errorCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Escape a string value for safe SQL embedding.
|
||||
*/
|
||||
@ -1115,11 +1125,9 @@ class Connection implements ConnectionInterface
|
||||
/**
|
||||
* Run a SQL statement.
|
||||
*
|
||||
* @param string $query
|
||||
* @param array $bindings
|
||||
* @throws QueryException
|
||||
*/
|
||||
protected function runQueryCallback($query, $bindings, Closure $callback)
|
||||
protected function runQueryCallback(string $query, array $bindings, Closure $callback)
|
||||
{
|
||||
// To execute the statement, we'll simply call the callback, which will actually
|
||||
// run the SQL against the PDO connection. Then we can calculate the time it
|
||||
@ -1132,11 +1140,15 @@ class Connection implements ConnectionInterface
|
||||
// message to include the bindings with SQL, which will make this exception a
|
||||
// lot more helpful to the developer instead of just the database's errors.
|
||||
catch (Exception $e) {
|
||||
++$this->errorCount;
|
||||
throw new QueryException(
|
||||
$query,
|
||||
$this->prepareBindings($bindings),
|
||||
$e
|
||||
);
|
||||
} catch (Throwable $throwable) {
|
||||
++$this->errorCount;
|
||||
throw $throwable;
|
||||
}
|
||||
|
||||
return $result;
|
||||
@ -1153,13 +1165,9 @@ class Connection implements ConnectionInterface
|
||||
/**
|
||||
* Handle a query exception.
|
||||
*
|
||||
* @param Exception $e
|
||||
* @param string $query
|
||||
* @param array $bindings
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
protected function handleQueryException($e, $query, $bindings, Closure $callback)
|
||||
protected function handleQueryException(QueryException $e, string $query, array $bindings, Closure $callback)
|
||||
{
|
||||
if ($this->transactions >= 1) {
|
||||
throw $e;
|
||||
@ -1176,11 +1184,9 @@ class Connection implements ConnectionInterface
|
||||
/**
|
||||
* Handle a query exception that occurred during query execution.
|
||||
*
|
||||
* @param string $query
|
||||
* @param array $bindings
|
||||
* @throws QueryException
|
||||
*/
|
||||
protected function tryAgainIfCausedByLostConnection(QueryException $e, $query, $bindings, Closure $callback)
|
||||
protected function tryAgainIfCausedByLostConnection(QueryException $e, string $query, array $bindings, Closure $callback)
|
||||
{
|
||||
if ($this->causedByLostConnection($e->getPrevious())) {
|
||||
$this->reconnect();
|
||||
|
@ -109,6 +109,11 @@ class Connection extends BaseConnection implements ConnectionInterface, DbConnec
|
||||
if ($this->connection instanceof \Hyperf\Database\Connection) {
|
||||
// Reset $recordsModified property of connection to false before the connection release into the pool.
|
||||
$this->connection->resetRecordsModified();
|
||||
if ($this->connection->getErrorCount() > 100) {
|
||||
// If the error count of connection is more than 100, we think it is a bad connection,
|
||||
// So we'll reset it at the next time
|
||||
$this->lastUseTime = 0.0;
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->transactionLevel() > 0) {
|
||||
|
@ -11,11 +11,14 @@ declare(strict_types=1);
|
||||
*/
|
||||
namespace HyperfTest\DbConnection;
|
||||
|
||||
use Exception;
|
||||
use Hyperf\Context\Context;
|
||||
use Hyperf\Contract\ConfigInterface;
|
||||
use Hyperf\Database\ConnectionResolverInterface;
|
||||
use Hyperf\Database\Exception\QueryException;
|
||||
use Hyperf\DbConnection\Connection;
|
||||
use Hyperf\DbConnection\Pool\PoolFactory;
|
||||
use Hyperf\Support\Reflection\ClassInvoker;
|
||||
use HyperfTest\DbConnection\Stubs\ConnectionStub;
|
||||
use HyperfTest\DbConnection\Stubs\ContainerStub;
|
||||
use HyperfTest\DbConnection\Stubs\PDOStub;
|
||||
@ -117,17 +120,21 @@ class ConnectionTest extends TestCase
|
||||
$pool = $container->get(PoolFactory::class)->getPool('default');
|
||||
$config = $container->get(ConfigInterface::class)->get('databases.default');
|
||||
|
||||
$callables = [function ($connection) {
|
||||
$connection->selectOne('SELECT 1;');
|
||||
}, function ($connection) {
|
||||
$connection->table('user')->leftJoin('user_ext', 'user.id', '=', 'user_ext.id')->get();
|
||||
}];
|
||||
$callables = [
|
||||
function ($connection) {
|
||||
$connection->selectOne('SELECT 1;');
|
||||
}, function ($connection) {
|
||||
$connection->table('user')->leftJoin('user_ext', 'user.id', '=', 'user_ext.id')->get();
|
||||
},
|
||||
];
|
||||
|
||||
$closes = [function ($connection) {
|
||||
$connection->close();
|
||||
}, function ($connection) {
|
||||
$connection->reconnect();
|
||||
}];
|
||||
$closes = [
|
||||
function ($connection) {
|
||||
$connection->close();
|
||||
}, function ($connection) {
|
||||
$connection->reconnect();
|
||||
},
|
||||
];
|
||||
|
||||
foreach ($callables as $callable) {
|
||||
foreach ($closes as $closure) {
|
||||
@ -147,45 +154,83 @@ class ConnectionTest extends TestCase
|
||||
{
|
||||
$container = ContainerStub::mockReadWriteContainer();
|
||||
|
||||
parallel([function () use ($container) {
|
||||
$resolver = $container->get(ConnectionResolverInterface::class);
|
||||
parallel([
|
||||
function () use ($container) {
|
||||
$resolver = $container->get(ConnectionResolverInterface::class);
|
||||
|
||||
/** @var \Hyperf\Database\Connection $connection */
|
||||
$connection = $resolver->connection();
|
||||
$connection->statement('UPDATE hyperf.test SET name = 1 WHERE id = 1;');
|
||||
/** @var \Hyperf\Database\Connection $connection */
|
||||
$connection = $resolver->connection();
|
||||
$connection->statement('UPDATE hyperf.test SET name = 1 WHERE id = 1;');
|
||||
|
||||
/** @var PDOStub $pdo */
|
||||
$pdo = $connection->getPdo();
|
||||
$this->assertSame('mysql:host=192.168.1.2;dbname=hyperf', $pdo->dsn);
|
||||
$pdo = $connection->getReadPdo();
|
||||
$this->assertSame('mysql:host=192.168.1.2;dbname=hyperf', $pdo->dsn);
|
||||
}]);
|
||||
/** @var PDOStub $pdo */
|
||||
$pdo = $connection->getPdo();
|
||||
$this->assertSame('mysql:host=192.168.1.2;dbname=hyperf', $pdo->dsn);
|
||||
$pdo = $connection->getReadPdo();
|
||||
$this->assertSame('mysql:host=192.168.1.2;dbname=hyperf', $pdo->dsn);
|
||||
},
|
||||
]);
|
||||
|
||||
parallel([function () use ($container) {
|
||||
$resolver = $container->get(ConnectionResolverInterface::class);
|
||||
parallel([
|
||||
function () use ($container) {
|
||||
$resolver = $container->get(ConnectionResolverInterface::class);
|
||||
|
||||
/** @var \Hyperf\Database\Connection $connection */
|
||||
$connection = $resolver->connection();
|
||||
/** @var \Hyperf\Database\Connection $connection */
|
||||
$connection = $resolver->connection();
|
||||
|
||||
/** @var PDOStub $pdo */
|
||||
$pdo = $connection->getPdo();
|
||||
$this->assertSame('mysql:host=192.168.1.2;dbname=hyperf', $pdo->dsn);
|
||||
$pdo = $connection->getReadPdo();
|
||||
$this->assertSame('mysql:host=192.168.1.1;dbname=hyperf', $pdo->dsn);
|
||||
}]);
|
||||
/** @var PDOStub $pdo */
|
||||
$pdo = $connection->getPdo();
|
||||
$this->assertSame('mysql:host=192.168.1.2;dbname=hyperf', $pdo->dsn);
|
||||
$pdo = $connection->getReadPdo();
|
||||
$this->assertSame('mysql:host=192.168.1.1;dbname=hyperf', $pdo->dsn);
|
||||
},
|
||||
]);
|
||||
}
|
||||
|
||||
public function testDbConnectionUseInDefer()
|
||||
{
|
||||
$container = ContainerStub::mockReadWriteContainer();
|
||||
|
||||
parallel([function () use ($container) {
|
||||
$resolver = $container->get(ConnectionResolverInterface::class);
|
||||
parallel([
|
||||
function () use ($container) {
|
||||
$resolver = $container->get(ConnectionResolverInterface::class);
|
||||
|
||||
defer(function () {
|
||||
$this->assertFalse(Context::has('database.connection.default'));
|
||||
});
|
||||
$resolver->connection();
|
||||
}]);
|
||||
defer(function () {
|
||||
$this->assertFalse(Context::has('database.connection.default'));
|
||||
});
|
||||
$resolver->connection();
|
||||
},
|
||||
]);
|
||||
}
|
||||
|
||||
public function testDbConnectionResetWhenThrowTooManyExceptions()
|
||||
{
|
||||
$container = ContainerStub::mockContainer();
|
||||
$pool = $container->get(PoolFactory::class)->getPool('default');
|
||||
$dbConnection = $pool->get();
|
||||
$connection = $dbConnection->getConnection();
|
||||
$this->assertSame(0, $connection->getErrorCount());
|
||||
$id = spl_object_hash((new ClassInvoker($connection))->connection);
|
||||
|
||||
$dbConnection->release();
|
||||
$dbConnection = $pool->get();
|
||||
$connection = $dbConnection->getConnection();
|
||||
$id2 = spl_object_hash((new ClassInvoker($connection))->connection);
|
||||
|
||||
$this->assertSame($id, $id2);
|
||||
|
||||
$invoker = new ClassInvoker($connection);
|
||||
for ($i = 0; $i < 101; ++$i) {
|
||||
try {
|
||||
(new ClassInvoker($invoker->connection))->runQueryCallback('', [], fn () => throw new Exception('xxx'));
|
||||
} catch (QueryException) {
|
||||
}
|
||||
}
|
||||
$this->assertSame(101, $connection->getErrorCount());
|
||||
|
||||
$dbConnection->release();
|
||||
$dbConnection = $pool->get();
|
||||
$connection = $dbConnection->getConnection();
|
||||
$id3 = spl_object_hash((new ClassInvoker($connection))->connection);
|
||||
$this->assertNotSame($id, $id3);
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user