please refactor class \yii\db\Command
$token = $rawSql;
try {
Yii::beginProfile($token, 'yii\db\Command::query');
if(!$this->isConnected()){
$this->db->close();
$this->db->open();
}
$this->pdoStatement->execute();
if ($method === '') {
$result = new DataReader($this);
} else {
if ($fetchMode === null) {
$fetchMode = $this->fetchMode;
}
$result = call_user_func_array([$this->pdoStatement, $method], (array) $fetchMode);
$this->pdoStatement->closeCursor();
}
Yii::endProfile($token, 'yii\db\Command::query');
} catch (\Exception $e) {
Yii::endProfile($token, 'yii\db\Command::query');
throw $this->db->getSchema()->convertException($e, $rawSql);
}
public function isConnected()
{
// Some check code
return false;
}
I think it's nice to have feature.
+
It may be useful when you write a service or a daemon
add please! I need it too!
@solovjov, @anatoliyarkhipov, @pernatiy02 what do you think about the solution @Mirocow proposed?
Constant triggering of SQL statement even such simple as SELECT 1
will degrade performance and produce extra workload on the SQL server side.
For MySQL it could be DO 1
which should affect performance less. Not sure it would work with other DBs though.
@klimov-paul could it be avoided while achieving reconnecting?
The only possible solution is wrapping existing execute()
in try...catch
and handle closed by timout connection via exception code like it is done at yii\db\IntegrityException
.
public function execute()
{
try {
return $this->executeInternal();
} catch (Exception $e) {
if (/* $e indicates DB connection is closed by timeout */) {
$this->db->close();
$this->db->open();
return $this->executeInternal();
}
throw $e;
}
}
We may add flag permanent such
'db' => [
'class' => 'yii\db\Connection',
'dsn' => 'mysql:host=localhost;dbname=mydb',
'username' => 'root',
'password' => 'password',
'charset' => 'utf8',
'tablePrefix' => 'tbl_',
// turn on schema caching to improve performance
'schemaCache' => 'db_cache',
'schemaCacheDuration' => 3600,
'enableSchemaCache' => false,
'permanent': true,
],
DO 1; very nice sql query
public function execute()
{
try {
return $this->executeInternal();
} catch (Exception $e) {
if (/* $e indicates DB connection is closed by timeout */) {
$this->db->close();
$this->db->open();
return $this->executeInternal();
}
throw $e;
}
}
bad idea because sql query should be very hard
This is terrible! Please don't do this!
Duplicates https://github.com/yiisoft/yii2/issues/10168
Discussion on forum for Yii 1: http://www.yiiframework.com/forum/index.php/topic/20063-general-error-2006-mysql-server-has-gone-away/
This is terrible! Please don't do this!
why?
We may add flag permanent https://github.com/yiisoft/yii2/issues/12634#issuecomment-250411966
As for me, better to do like this:
<?php
namespace app\components\db;
use yii\db\Exception;
class Command extends \yii\db\Command
{
/**
* @var ReConnection
*/
public $db;
protected function queryInternal($method, $fetchMode = null)
{
try {
return parent::queryInternal($method, $fetchMode);
} catch (Exception $e) {
$this->cancel();
$this->db->reconnect();
$this->bindValues($this->params);
return parent::queryInternal($method, $fetchMode);
}
}
}
<?php
namespace app\components\db;
use Yii;
use yii\base\Exception;
use yii\db\Connection;
use app\components\db\mysql\PDO;
class ReConnection extends Connection
{
/** @var int - attempts retry */
public $retry_attempts = 10;
/** @var int - millisecond sleep time */
public $sleep_time = 100;
/** @var string */
public $commandClass = Command::class;
/**
* Creates a command for execution.
* @param string $sql the SQL statement to be executed
* @param array $params the parameters to be bound to the SQL statement
* @return Command the DB command
*/
public function createCommand($sql = null, $params = [])
{
$class_name = $this->commandClass
$command = new $class_name([
'db' => $this,
'sql' => $sql,
]);
return $command->bindValues($params);
}
/**
* ReCreates the PDO instance.
*
* @return PDO the pdo instance
* @throws Exception - Cannot connect to db
*/
protected function createPdoInstance()
{
$count = 0;
do {
try {
return parent::createPdoInstance();
} catch (\PDOException $e) {
if (++$count > $this->retry_attempts) {
break;
}
usleep($this->sleep_time);
Yii::info("Reconnect attempt: $count ". $this->dsn, __METHOD__);
}
} while (true);
throw $e;
}
/**
* @throws \yii\base\InvalidConfigException
* @throws \yii\db\Exception
*/
public function reconnect()
{
Yii::info('Reopening DB connection: ' . $this->dsn, __METHOD__);
$this->close();
$this->open();
}
}
so hard for me ;)
Are you trying to check you code?
$this->_instance
There no variable _instance in Command class
if(!$this->isConnected()){
\Yii::$app->db->close();
\Yii::$app->db->open();
}
$this->pdoStatement->execute();
Variable pdoStatement will content old instance of PDO, not a new one.
fixed
if(!$this->isConnected()){
$this->db->close();
$this->db->open();
}
because this code should be in the namespace \yii\db\Command
There no variable _instance in \yii\db\Command
Faced this problem in my long-running queue workers. Found that the best solution is to store date of open
operation and check duration of connection. I introduced $reconnectTimeout
in Connection
which determines when to reconnect.
Something like that:
if (time() - $this->_openedAt > $this->reconnectTimeout) {
$this->close();
}
@Mirocow I don't know why you've reacted with thumb down on comment on my previous comment if you think it's not related - explain your opinion, please.
I see the following use cases and solutions for the problem:
wait_timeout
as it was described in the issue I've linked as duplication.execute()
method, as @klimov-paul proposed in his comment or extend Connection
and register a customized one as a component.Why do I think auto-reconnect can not be accepted:
COMMIT
new changes.because we must execute very simle sql query
in my app i did such
// TODO 6: ΠΠΎΡΠΈΠΊΡΠΈΡΡ ΠΊΠΎΠ³Π΄Π° ΡΠ΅ΠΊΠΎΠ½Π΅ΠΊΡ Π·Π°ΠΏΠΈΠ»ΡΡ Π² yii2
try{
\Yii::$app->db->createCommand("DO 1")->execute();
}catch (\yii\db\Exception $e){
\Yii::$app->db->close();
\Yii::$app->db->open();
}
$sheet = PriceList::findOne($message['id']);
Another way is to introduce idle
event in Yii application. Developer can call it when script is going idle.
Yii::$app->trigger('idle');
This will allow us to close any kind of resources in framework core.
// Any connection based component
public function init()
{
Yii::$app->on('idle', [$this, 'close']);
parent::init()
}
This may be useful only in daemon scripts, but I think you should consider this solution for its flexibility.
Daemons is a complex thing and daemons know a plenty of ways how to fuck you up :)
Gone SQL connection is probably one of the easiest things you should worry about.
There are much wiser ways to handle gone connection than sending SQL pings through it each time you want to execute something real. And @AnatolyRugalev have posted some of them.
I think we should close the issue.
@Mirocow do you have anything to say about the transactions use case I've posted?
@SilverFire another case, we write daemon or service and our service execute some queries for some settings or update some flags
if (time() - $this->_openedAt > $this->reconnectTimeout) {
$this->close();
}
:+1:
Motorized crutch
ΠΠΎΡΠΎΡΠΈΠ·ΠΈΡΠΎΠ²Π°Π½Π½ΡΠΉ ΠΊΠΎΡΡΡΠ»Ρ
But if you will have a network problem it will not help :)
@SilverFire another case, we write daemon or service and our service execute some queries for some settings or update some flags
I'm sorry, but I don't understand the use case.
example:
we use wile(1) or videlalvaro/php-amqplib for create our daemon and we receive messages from redis or rabbitmq server
Fine, we do receive. What's next? :) What is the problem you are trying to explain? ΠΠΎΠΆΠ½ΠΎ ΠΏΠΎ ΡΡΡΡΠΊΠΈ.
Hmm... considering transactions issue @SilverFire mentioned, I think the only reliable solution in case you're running a daemon is to send ping each N seconds so connection isn't closed. That has nothing to do with regular web apps though.
Π²ΠΈΡΠΈΡ Π΄Π΅ΠΌΠΎΠ½ ΠΈ ΠΆΠ΄Π΅Ρ Π΄Π°Π½Π½ΡΠ΅ ΠΎΡ ΡΠ΅Π΄ΠΈΡΠ° ΠΈΠ»ΠΈ ΡΠ°Π±Π±ΠΈΡΠ°
ΠΎΠ½ΠΈ ΠΏΡΠΈΡΠ»ΠΈ ΠΈ ΠΎΠ½ Π²ΡΠΏΠΎΠ»Π½ΡΠ΅Ρ ΠΎΠ±ΡΡΠ΅Ρ ΡΠ΅Π³ΠΎ Π»ΠΈΠ±ΠΎ Π° ΠΏΠΎΡΠΎΠΌ ΠΏΠΈΡΠ΅Ρ Π² Π±Π΄ ΠΈ Π²ΠΎΡ ΡΡΡ ΡΡΠ°Π±Π»Π° ΡΠ°ΠΊ ΠΊΠ°ΠΊ ΡΠΎΠ΅Π΄Π΅Π½Π΅Π½ΠΈΡ Π½Π΅Ρ
I think the only reliable solution in case you're running a daemon is to send ping each N seconds so connection isn't closed.
But if you will have a network problem it will not help :)
Π²ΠΈΡΠΈΡ Π΄Π΅ΠΌΠΎΠ½ ΠΈ ΠΆΠ΄Π΅Ρ Π΄Π°Π½Π½ΡΠ΅ ΠΎΡ ΡΠ΅Π΄ΠΈΡΠ° ΠΈΠ»ΠΈ ΡΠ°Π±Π±ΠΈΡΠ°
ΠΎΠ½ΠΈ ΠΏΡΠΈΡΠ»ΠΈ ΠΈ ΠΎΠ½ Π²ΡΠΏΠΎΠ»Π½ΡΠ΅Ρ ΠΎΠ±ΡΡΠ΅Ρ ΡΠ΅Π³ΠΎ Π»ΠΈΠ±ΠΎ Π° ΠΏΠΎΡΠΎΠΌ ΠΏΠΈΡΠ΅Ρ Π² Π±Π΄ ΠΈ Π²ΠΎΡ ΡΡΡ ΡΡΠ°Π±Π»Π° ΡΠ°ΠΊ ΠΊΠ°ΠΊ ΡΠΎΠ΅Π΄Π΅Π½Π΅Π½ΠΈΡ Π½Π΅Ρ
The simplest way - cover you db query in try catch.
If you will receive catch - reconnect to db and repeate your query.
Yes. In this case there should be additional try-catch with a failure handling that's application-specific. Right?
http://www.php.net/pdo.connections
<?php
$dbh = new PDO('mysql:host=localhost;dbname=test', $user, $pass, array(
PDO::ATTR_PERSISTENT => true
));
?>
This is persistant connect, but it will not rescue when you will have connection timed out.
You should ping anyway in order to keep connection open even it's opened as persistent one.
I don't think that there is any good and universal solution, because it depends on application specifics and developer is responsible for approach choosing.
I'm closing the issue, thanks to all for participating in this discussion.
So overall issue is the same when using both regular and persistent connection. You attempting making a query and DB server replies that it "has gone away". Sending additional query doesn't help the situation in any way. try-catch seems to be better solution but re-querying automatically could be dangerous if you use transactions so it seems overall it's very app-specific and could not be introduced at the framework level.
thx
Yes. In this case there should be additional try-catch with a failure handling that's application-specific. Right?
Yes, but this is crunch if we will cover each request.
So, the next way is extend Command with my example
https://github.com/yiisoft/yii2/issues/12634#issuecomment-250416584
Indeed but your example could be dangerous if transactions are used. May lead to inconsistent DB state.
Why?
It was expected that post title change, comment 1 and comment 2 are written but only comment 2 was.
You can exclude this for transaction :)
For other ways it will help
You still need to reconnect for transactions but you need to repeat the whole transaction instead of part of it.
I don't want to give a way to shoot yourself in the foot silently.
Yeah. That's tricky to solve in a good way for all cases.
I don't want to give a way to shoot yourself in the foot silently.
Absolutely agry with you!
This is not issue for Yii2, this is crunch for current project ;)
For Yii 1.1 I solve this in this way:
class ImportDbConnection extends CDbConnection {
private $stamp;
/**
* {@inheritdoc}
*/
public function createCommand($query = null) {
$this->setActive(true);
try {
// send ping on every 10 seconds
if ($this->stamp < time()) {
$this->stamp = time() + 10;
$ping = new CDbCommand($this, 'SELECT 1');
$ping->execute();
}
} catch (CDbException $e) {
// if ping fail, reconnect
$this->setActive(false);
$this->setActive(true);
}
return parent::createCommand($query);
}
}
10 seconds could be parametrized and set by default to null
, so reconnect will not work by default.
It won't send ping if there aren't queries for a minute, right?
@samdark No, it will not prevent disconnecting, only try to reconnect if connection is lost. I use this for read only database for some long-running import scripts.
Yes. For read-only it's safe. I wonder why doing a special query instead of try-catch-ing the real one?
CDbException
can be thrown not only on disconnection - this is simplest way to ensure connection and avoid exception swallowing.
You can grep for "gone away", right?
You can grep for "gone away", right?
This is dirty hack - what if I will have 'gone away' string in my query that failed and it will be part of error message? Or for some reason (localization?) in one of the servers message will be different? In my case the performance was not so crucial to waste time on the nuances.
Most helpful comment
It may be useful when you write a service or a daemon