Tested in PHP 5.5.22 and 5.5.25
When using PDO that has extended PDOStatement, MySQL keep connection until when PHP script are finished.
use PDO;
$dbinfoCode = array(
'userid' => 'userid',
'password' => 'password',
'engine' => 'mysql',
'host' => '192.168.100.2',
'database' => 'test',
);
for ($i = 0; $i < 10000; $i++) {
$dsn = sprintf("%s:host=%s;dbname=%s", $dbinfo['engine'], $dbinfo['host'], $dbinfo['database']);
$pdo = new PDO($dsn, $dbinfo['userid'], $dbinfo['password'], $options);
$pdo->setAttribute (PDO::ATTR_STATEMENT_CLASS, array ('PDOStatement2', array($pdo)));
$pdo = null;
}
class PDOStatement2 extends PDOStatement {
}
I can see increasingly stacked "Sleep" processes on MySQL query. Finally, MySQL throw error "Too many connections".
SHOW PROCESSLIST;
If there is no setAttribute about PDO::ATTR_STATEMENT_CLASS, The MySQL connection is disconnected in working order.
$pdo = new PDO($dsn, $dbinfo['userid'], $dbinfo['password'], $options);
//$pdo->setAttribute (PDO::ATTR_STATEMENT_CLASS, array ('PDOStatement2', array($pdo)));
$pdo = null;
I have no idea about it wheather is this a bug or has another solutions.
Finally, I found the solution.
$pdo object on following statement will be duplicated
$pdo->setAttribute (PDO::ATTR_STATEMENT_CLASS, array ('PDOStatement2', array($pdo)));
Use &$pdo instead of $pdo.
$pdo->setAttribute (PDO::ATTR_STATEMENT_CLASS, array ('PDOStatement2', array(&$pdo)));
Related
In PDO (And likewise DBAL) if there's a problem authenticating with the server it will throw an exception containing a trace including the database username and password.
Needless to say, this is a problem, and in PDO I would wrap it in a try block and re-throw the error without the stack trace. Problem solved.
However, DBAL doesn't actually initiate the connection until the first query is called, so it misses the try block completely and spews my database credentials the first chance it gets!
How can I force DBAL to connect immediately so I can catch any authentication errors?
\Doctrine\DBAL\Connection::connect()
Obvious in retrospect.
My database is ibm db2 so I use the odbc pdo connection with dbal. This is how I set up my connection, notice that the PDO is in a try catch block
use Doctrine\DBAL\Configuration;
use Doctrine\DBAL\DriverManager;
use Doctrine\DBAL\Connection;
use Doctrine\DBAL\Query\QueryBuilder;
$connPdo = null;
try {
$connPdo = new PDO('odbc:'.SYSTEM_DSN_NAME, DB_USERNAME, DB_PASSWORD, array(
PDO::ATTR_PERSISTENT => TRUE,
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION)
);
} catch (PDOException $e) {
echo $e->getMessage() . '</br>';
}
$config = new Configuration();
$connectionParams = array(
'user' => DB_USERNAME,
'password' => DB_PASSWORD,
'pdo' => $connPdo,
'driverClass' =>'Doctrine\DBAL\Driver\PDOIbm\Driver',
);
$connection = DriverManager::getConnection($connectionParams, $config);
$queryBuilder = $connection->createQueryBuilder();
Hi I have a php daemon that handle request from rabbitmq
After a day, it can no longer execute due to error MySQL has gone away.
PHP Warning: PDOStatement::execute(): MySQL server has gone away in /var/www/daemon/www/vendor/zendframework/zendframework/library/Zend/Db/Adapter/Driver/Pdo/Statement.php on line 239
PHP Warning: PDOStatement::execute(): Error reading result set\'s header in /var/www/daemon/www/vendor/zendframework/zendframework/library/Zend/Db/Adapter/Driver/Pdo/Statement.php on line 239
I didn't use doctrine, instead I send my \Zend\Db\Adapter\Adapter to a db wrapper class with below function.
public static function executeScalar($statement, $parameters, \Zend\Db\Adapter\Adapter $dbAdapter)
{
$dbResult = new DbResult();
if (! $statement) {
$dbResult->addError('No statement given');
return $dbResult;
}
$stmt = $dbAdapter->createStatement();
$stmt->prepare($statement);
foreach ($parameters as $key => &$param) {
$stmt->getResource()->bindParam($key + 1, $param[0], $param[1]);
}
try {
$result = $stmt->execute();
$dbResult->setResult($result);
} catch (\Zend\Db\Adapter\ExceptionInterface $e) {
$dbResult->addError('DB Error');
$message = $e->getPrevious() ? $e->getPrevious()->getMessage() : $e->getMessage();
$dbResult->addError($message);
} catch (\Zend\Db\Adapter\Exception $e) {
$dbResult->addError('DB Error');
$dbResult->addError($e->getMessage());
} catch (\PDOException $e) {
$dbResult->addError('DB Error');
$dbResult->addError($e->getMessage());
} catch (\Exception $e) {
$dbResult->addError('DB Error');
$dbResult->addError($e->getMessage());
}
$stmt->getResource()->closeCursor();
return $dbResult;
}
DbResult is my own db result wrapper class it mainly check whether it return empty, what's the error, how many rows, etc.
Here is my database.local.php configuration
return array(
'service_manager' => array(
'factories' => array(
'mysql' => function ($sm)
{
return new Zend\Db\Adapter\Adapter(array(
'driver' => 'PdoMysql',
'hostname' => 'localhost',
'database' => 'daemon',
'username' => 'daemon',
'password' => 'password',
'driver_options' => array(
\PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES \'UTF8\'',
\PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION,
\PDO::ATTR_EMULATE_PREPARES => true,
\PDO::MYSQL_ATTR_LOCAL_INFILE => true
)
));
},
)
)
)
So everytime I want to execute a sql I do this inside controller or any other class ( Just an example )
$service = $this->getServiceLocator();
$dbAdapter = $service->get('mysql');
$get = \Db\Database::executeScalar('SELECT * FROM mytable WHERE id <= ?', array(10), $dbAdapter);
It seems I cannot catch the warning, and is there a way to force reconnect or perhaps I just do a disconnect after each request ?
Will this works, to handle the error ?
On every new request I do this
$dbAdapter->getDriver()->getConnection()->connect();
At the end of request I do this
$dbAdapter->getDriver()->getConnection()->disconnect();
Yes, I check the persistent connection option, but I also not fond of it.
I find the problem, it cause by mysql server close idle connection after 'wait timeout'. when mysql closing the idle connection, PDO will not receive any event so the next time you initiate a query it will return Mysql has gone away error.
For http request this is acceptable since after the server response the request it will stop/exit php execution which close all connection to database.
For daemon/service this is not acceptable since most of the time it waiting for client request (idle). My solution is to close the connection everytime it finish handling client request. e.g :
while (true) {
//listen to rabbitmq queue
//...
//do something base on client request from rabbitmq queue
//...
//close the connection whether it use database or not
//connection will be reconnected when we call $service->get('mysql');
$service = $this->getServiceLocator();
$dbAdapter = $service->get('mysql');
$dbAdapter->getDriver()->getConnection()->disconnect();
}
You can create a persistent connection to your database but be warned that creating a persistent connection should not be the first solution to look for. Be sure to do some research on the subject before trying it. You can find some documentation here :
http://php.net/manual/en/pdo.connections.php#example-954
On the other hand, you should look for the queries sent so the reason of the gone away message is not caused by the mysql server recieving a packet too large (ex: inserting a large blob). Because if it is, the connection will still close unexpectedly.
Are there any needs for using set names ourcharset with DBAL with PHP >=5.3.2 and <5.3.6?
Prior to PHP 5.3.6, the charset option in PDO connection was ignored.
If we were running an older version of PHP, we had to use set names ourcharset.
Actual Doctrine DBAL 2.5.1 require PHP >=5.3.2.
I can't find what Doctrine team is advising if someone have PHP <5.3.6 version.
DBAL is mostly based on PDO, but it also have some improvements over it, so I was thinking maybe this was improved... but at Doctrine DBAL documentation page I have found only this:
Up until PHP 5.3.6 PDO has a security problem when using non ascii
compatible charsets. Even if specifying the charset using “SET NAMES”,
emulated prepared statements and PDO#quote could not reliably escape
values, opening up to potential SQL injections. If you are running PHP
5.3.6 you can solve this issue by passing the driver option “charset” to Doctrine PDO MySQL driver. Using SET NAMES does not suffice!
In PDO to this time I have done:
<?php
$dsn = 'mysql:host='.$_SESSION['options']['host'].';port='.$_SESSION['options']['port'].';dbname='.$_SESSION['options']['dbname'].';charset='.$_SESSION['options']['charset'];
try {
$conn = new \PDO($dsn, $_SESSION['options']['user'], $_SESSION['options']['pass']);
if(version_compare(PHP_VERSION, '5.3.6', '<')) //is this required with DBAL?
$conn->exec("set names {$_SESSION['options']['charset']}");
} catch (\PDOException $e) {
trigger_error($e->getMessage(), E_USER_ERROR);
}
?>
With DBAL it's:
<?php
require_once "lib/autoload.php";
$config = new \Doctrine\DBAL\Configuration();
$params = array(
'dbname' => $_SESSION['options']['dbname'],
'user' => $_SESSION['options']['user'],
'password' => $_SESSION['options']['pass'],
'host' => $_SESSION['options']['host'],
'port' => $_SESSION['options']['port'],
'driver' => 'pdo_mysql',
'charset' => $_SESSION['options']['charset'],
);
try {
$conn = \Doctrine\DBAL\DriverManager::getConnection($params, $config);
} catch (\Exception $e) {
trigger_error($e->getMessage(), E_USER_ERROR);
}
?>
It looks that DBAL do not improved here anything.
So, if there is possibility that our app will used with PHP between >=5.3.2 and <5.3.6, then yes, use additional SET NAMES:
<?php
require_once "lib/autoload.php";
$config = new \Doctrine\DBAL\Configuration();
$params = array(
'dbname' => $_SESSION['options']['dbname'],
'user' => $_SESSION['options']['user'],
'password' => $_SESSION['options']['pass'],
'host' => $_SESSION['options']['host'],
'port' => $_SESSION['options']['port'],
'driver' => 'pdo_mysql',
'charset' => $_SESSION['options']['charset'],
);
if(version_compare(PHP_VERSION, '5.3.6', '<'))
$params['driverOptions'] = array(1002=>'SET NAMES '.$_SESSION['options']['charset']);
//"1002" is value of constant MYSQL_ATTR_INIT_COMMAND
try {
$conn = \Doctrine\DBAL\DriverManager::getConnection($params, $config);
} catch (\Exception $e) {
trigger_error($e->getMessage(), E_USER_ERROR);
}
?>
I'm having trouble getting errors to display with when making a new PDO connection. It only shows an error if I put the incorrect password in my config file. It won't show errors for an incorrect database name, username, or host.
<?php
// config file
return [
'driver' => 'mysql',
'host' => 'localhost',
'database' => 'query_test',
'username' => 'root',
'password' => '',
'options' => [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_CLASS,
PDO::ATTR_EMULATE_PREPARES => false
]
];
// connector file
use PDO;
class MySqlConnector implements ConnectorInterface
{
public function connect(array $config)
{
extract($config);
$dsn = "mysql:{$host};dbname={$database}";
$connection = new PDO($dsn, $username, $password, $options);
return $connection;
}
}
I've tried using try/catch blocks as well as setting ini display errors and error reporting E_ALL. Can't seem to figure this out.
Change:
$connection = new PDO($dsn, $username, $password, $options);
return $connection;
to:
try {
$connection = new PDO('mysql:host=localhost;dbname=myDatabase', $username, $password);
$connection->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
} catch(PDOException $e) {
echo 'ERROR: ' . $e->getMessage();
}
return $connection;
I overlooked a simple mistake. I had "mysql:{$host}" instead of "mysql:host={$host}".
Sorry for the silly mistake but thank you for your answer!
I want to create a database connection programmatically without using the config file. I have a form wherein the user enters the database details like the hostname, username, password and the database name. On the basis of these details a new test connection is made and if it passes it should generate the necessary files.
This is what I have tried:
// Create Test DB Connection
Yii::$app->set('id', [
'class' => 'yii\db\Connection',
'dsn' => $dsn,
'username' => $form->username,
'password' => $form->password,
'charset' => 'utf8'
]);
try {
// Check DB Connection
if (Yii::$app->db->getIsActive()) {
// Write Config
$config['components']['db']['class'] = 'yii\db\Connection';
$config['components']['db']['dsn'] = $dsn;
$config['components']['db']['username'] = $username;
$config['components']['db']['password'] = $password;
$config['components']['db']['charset'] = 'utf8';
Configuration::setConfig($config);
$success = TRUE;
return $this->redirect(['init']);
}else{
$errorMsg = 'Incorrect Configurations';
}
} catch (Exception $e) {
$errorMsg = $e->getMessage();
}
I have tested this again and again and even with correct configurations it is giving an error.
All the help is appreciated. Thanks in advance!
You can define a new connection this way:
$db = new yii\db\Connection([
'dsn' => 'mysql:host=localhost;dbname=example',
'username' => 'root',
'password' => '',
'charset' => 'utf8',
]);
$db->open();
After the DB connection is established, one can execute SQL statements like the following eg:
$command = $db->createCommand('SELECT * FROM post');
$posts = $command->queryAll();
// or
$command = $connection->createCommand('UPDATE post SET status=1');
$command->execute();
you can look at this for doc and guide
http://www.yiiframework.com/doc-2.0/guide-db-dao.html
http://www.yiiframework.com/doc-2.0/yii-db-connection.html
I realised my mistake. When using Yii::$app->set() for setting up the db connection, you have to even manually open the connection using Yii::$app->db->open(). Yii doesn't open up the connection for you.