Had an issue recently where a major fire at a datacentre destroyed one of our machines. While this server is now offline all of the other servers on our network are still functional, although this has brought to light a problem that I'm hoping to find the answer for.
Is it possible to reduce the amount of time PDO will wait before reporting that the connection failed?
In my particular scenario there are others servers that rely on remote MySql connections to retrieve data from the destroyed server in order to display certain things. Now I have measures in place to handle the situation when the requested data isn't supplied, but have never had to deal with a machine being unreachable until now.
So right now I'm in the situation where my online servers are hanging on page load due to the destroyed server being offline.
Is there any preference or setting that can be provided when trying to make the initial remote connection where you say "return false without throwing a script breaking error after 5 seconds" for example?
I call my connections through a db class :
protected static function getDB()
{
static $db = null;
if ($db === null) {
$dbhost = DB_HOST;
$dbuser = DB_USER;
$dbpass = DB_PASS;
$dbname = DB_NAME;
try {
$db = new PDO("mysql:host=$dbhost;dbname=$dbname;charset=utf8mb4",
$dbuser, $dbpass);
$db->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
} catch (PDOException $e) {
echo $e->getMessage();
}
}
return $db;
}
Like this...
$db = static::getDB();
I'm presuming that I can simply return false in the catch exception instead of echoing the error message which answers half of my question if true, but is it possible to speed up the time it will wait before throwing the exception in the event of an unreachable machine?
EDIT
I found this and tried it...
$db->setAttribute(PDO::ATTR_TIMEOUT, '5');
... but it makes no difference to the timeout whatsoever? Does this setting only relate to local connections maybe?
Just found the answer myself...
At first I found this and added it but it made NO difference whatsoever to the time taken before bomb out in my situation, maybe it works in others though?
$db->setAttribute(PDO::ATTR_TIMEOUT, '5');
But the answer that DID work for me was dropping this in right before the database connection :
ini_set("default_socket_timeout", 5);
Hope it helps somebody else.
Related
I have my db connection parameters set in a single file which I include on all pages I need it. Connection files looks like so... called connect.php :
$db_host = '111.111.111.111';
$db_database = 'test';
$db_user = 'test';
$db_pass = 'test';
$db_port = '3306';
//db connection
try {
$db = new PDO("mysql:host=$db_host;port=$db_port;dbname=$db_database;charset=utf8", $db_user, $db_pass,
array(
PDO::ATTR_EMULATE_PREPARES => false,
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, //PDO::ERRMODE_SILENT is default setting
PDO::ATTR_PERSISTENT => false //when true
)
);
}
catch(PDOException $e) {
error_log("Failed to connect to database (/connect.php): ".$e->getMessage());
}
When I need to do things with the db I include this file and end up with something like this... called example.php :
require $_SERVER['DOCUMENT_ROOT'].'/assets/functions/connect.php';
$stmt = $db->prepare("
SELECT
accounts.account_id,
FROM accounts
WHERE accounts.account_key = :account_key
");
//bindings
$binding = array(
'account_key' => $_POST['account_key']
);
$stmt->execute($binding);
//result (can only be one or none)
$result = $stmt->fetch(PDO::FETCH_ASSOC);
//if result
if($result)
{
// result found so do something
}
Occasionally the database connection will fail (updating, I shut it down, its being hammered, whatever)... when that happens the PDOException I have in the try/catch works as it should and adds an entry into my error log saying so.
What I would also like to do is add a 'check' in my example.php so it doesn't attempt to do any database work if there is no connection (the include file with my connect script failed to get a connection). How would I go about this and what is the preferred method of doing so?
I'm not sure of the correct way to 'test' $db before my $stmt entry. Would there be a way to retry the connection if it was not set?
I realize I can leave it as it and there would be no problems, other than the database query fails and the code doesn't execute, but I want to have more options like adding another entry to the error log when this happens.
To stop further processings just add an exit() at the end of each catch block, unless you want to apply a finally block.
try {
//...
} catch(PDOException $e) {
// Display a message and log the exception.
exit();
}
Also, throwing exceptions and true/false/null validations must be applied through the whole connect/prepare/fetch/close operations involving data access. You may want to see a post of mine:
Applying PDO prepared statements and exception handling
Your idea with including db connection file I find good, too. But think about using require_once, so that a db connection is created only once, not on any include.
Note: In my example I implemented a solution which - somehow - emulates the fact that all exceptions/errors should be handled only on the entry point of an application. So it's more directed toward the MVC concept, where all user requests are sent through a single file: index.php. In this file should almost all try-catch situations be handled (log and display). Inside other pages exceptions would then be thwrown and rethrown to the higher levels, until they reach the entry point, e.g index.php.
As for reconnecting to db, How it should be correlated with try-catch I don't know yet. But anyway it should imply a max-3-steps-iteration.
I keep getting the following error from my code:
Database connect failed: PDO::__construct(): send of 12 bytes failed with errno=110 Connection timed out
This error persistently happens on an api. If that api keeps getting called a lot during the day this does not happen. Only when the api is not used for some time.
I can solve this by doing a php-fpm restart/reload, but this shouldn't be the solution.
Does anybody have any idea how to solve this?
--EDIT--
This is the code for connection to the database:
public function connectDatabase($allow_persistent = true)
{
$this->db = null;
$this->readINI();
$pdo_attr = [
PDO::ATTR_PERSISTENT => $allow_persistent,
PDO::MYSQL_ATTR_INIT_COMMAND => "SET CHARACTER SET utf8;",
PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, true,
];
$this->db = new PDO("mysql:host=" . $this->db_data_dbhost . ";dbname=" . $this->db_data_dbname . ";charset=utf8", $this->db_data_username, $this->db_data_password, $pdo_attr);
$this->db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
}
To itterate some more, this is used on two API's at the moment.
One is consistently used by customers and never encounters this problem
The second is not used by a lot of customers yet seeing as this is still in development, it's only with a few customers for testing purposes. We do try to push an app to the appstore but it gets shot down by this problem. This API will eventually run into the error as depicted above and will not recover from this state without restarting/reloading the php-fpm service.
Using persistant connections is not that good (see why), but still, to solve this you may want to increase MySQL connections timeout.
To achieve this, see wait_timeout parameter for my.ini.
p.s. Also, you can catch this error and just reconnect to the database.
After much testing and research we found this bug was causing the issue:
https://bugs.php.net/bug.php?id=64549
Our solution was to use the error control operator '#' on the PDO connect:
$connection = #new PDO(
$connection_type.":dbname=".$dbname.";host=".$host,
$username,
$password,
[
PDO::ATTR_PERSISTENT => TRUE,
PDO::ATTR_TIMEOUT => 15,
PDO::MYSQL_ATTR_INIT_COMMAND => "SET sql_mode='POSTGRESQL'"
]
);
If you are using your own error handler set_error_handler() you will need to ignore if error_reporting() level === 0.
// This handles the mysqlnd persistent connections bug when the # symbol is used it and the error is suppressed
if ( 0 !== error_reporting() ) {
// Do your regular customer error handling here
}else{
// ignore this since it is throwing a false positive from the persistent connection bug
}
I hope this saves someone hours troubleshooting. I hope the people behind PHP figure out a solution for this bug. It has annoyed many people for years.
I just installed MAMP and have created a MYSQL database. I can access it via PHPMYADMIN.
In my php page I have this, pasted directly from the MAMP webstart page--
$user = 'root';
$password = 'root';
$db = 'local_db';
$host = 'localhost';
$port = 3306;
$link = mysql_connect(
"$host:$port",
$user,
$password
);
$db_selected = mysql_select_db(
$db,
$link
);
The resulting page stops at this point, won't print anything below these instructions.
I've tried changing the port in the MAMP preferences. I also included or die("Could not connect"); after the first line, but still don't get any text after the link data in the page.
I checked online, and others with the problem at least see the die text. I don't get that.
I haven't changed any passwords or data other than mess with the port number.
Any help would be appreciated!
Please give the following a try, I have developed and tested it locally, functionality within has been documented to help you understand what is going on in every step.
/**
*
* Modern method of connecting to a MySQL database and keeping it simple.
*
* If you would like to learn more about PDO,
* please visit http://php.net/manual/en/book.pdo.php
*
*/
//Set up database connection constants, so they cannot be changed.
define('DBHOST','127.0.0.1'); //Change this to the ip address of your database
define('DBNAME','test'); // Change this to the database name you are trying to connect to.
define('DBUSER','databaseuser'); // Insure this user is not the root user!!!!
define('DBPASS','databasepassword'); // Insure this is not the root password!!!!
//Let's try to connect to the database first.
try {
//Initiate a new PDO object called $MYDB and pass it the proper information to make
//the connection
$MYDB = new PDO("mysql:host=".DBHOST.";dbname=".DBNAME."", DBUSER, DBPASS);
//If we are successful show it :D for the test page, if this is for production you should not show this.
echo "Database connection was successful.";
//If this does not worth catch the exception thrown by PDO so we can use it.
} catch(PDOException $e) {
//Show that there was an issue connecting to the database. Do not be specific because,
//user's do not need to know the specific error that is causing a problem for security
//reasons.
echo "Oh, sorry there was an issue with your request please try again.";
//Since we had an issue connecting to the database we should log it, so we can review it.
error_log("Database Error" . $e->getMessage());
}
//Since this is 100% php code we do not need to add a closing php tag
//Visit http://php.net/manual/en/language.basic-syntax.phptags.php for more information.
If you have any issues with this please attempt to break it up into smaller pieces while reviewing the PDO documentation.
I have an issue, it has only cropped up now. I am on a shared web hosting plan that has a maximum of 10 concurrent database connections. The web app has dozens of queries, some pdo, some mysql_*.
Loading one page in particular peaks at 5-6 concurrent connections meaning it takes a minimum of 2 users loading it at the same time to spit an error on one or both of them.
I know this is inefficient, I'm sure I can cut that down quite a bit, but that's what my idea is at the moment is to move the pdo code into a function and just pass in a query string and an array of variables, then have it return an array (partly to tidy my code).
THE ACTUAL QUESTION:
How can I get this function to continue to retry until it manages to execute, and hold up the script that called it (and any script that might have called that one) until it manages to execute and return it's data? I don't want things executing out of order, I am happy with code being delayed for a second or so during peak times
Since someone will ask for code, here's what I do at the moment. I have this in a file on it's own so I have a central place to change connection parameters. the if statement is merely to remove the need to continuously change the parameters when I switch from my test server to the liver server
$dbtype = "mysql";
$server_addr = $_SERVER['SERVER_ADDR'];
if ($server_addr == '192.168.1.10') {
$dbhost = "localhost";
} else {
$dbhost = "xxxxx.xxxxx.xxxxx.co.nz";
}
$dbname = "mydatabase";
$dbuser = "user";
$dbpass = "supersecretpassword";
I 'include' that file at the top of a function
include 'db_connection_params.php';
$pdo_conn = new PDO("mysql:host=$dbhost;dbname=$dbname", $dbuser, $dbpass);
then run commands like this all on the one connection
$sql = "select * from tbl_sub_cargo_cap where sub_model_sk = ?";
$capq = $pdo_conn->prepare($sql);
$capq->execute(array($sk_to_load));
while ($caprow = $capq->fetch(PDO::FETCH_ASSOC)) {
//stuff
}
You shouldn't need 5-6 concurrent connections for a single page, each page should only really ever use 1 connection. I'd try to re-architect whatever part of your application is causing multiple connections on a single page.
However, you should be able to catch a PDOException when the connection fails (documentation on connection management), and then retry some number of times.
A quick example,
<?php
$retries = 3;
while ($retries > 0)
{
try
{
$dbh = new PDO("mysql:host=localhost;dbname=blahblah", $user, $pass);
// Do query, etc.
$retries = 0;
}
catch (PDOException $e)
{
// Should probably check $e is a connection error, could be a query error!
echo "Something went wrong, retrying...";
$retries--;
usleep(500); // Wait 0.5s between retries.
}
}
10 concurrent connections is A LOT. It can serve 10-15 online users easily.
Heavy efforts needed to exhaust them.
So there is something wrong with your code.
There are 2 main reasons for it:
slow queries take too much time and thus serving one hit uses one mysql connection for too long.
multiple connections opened from every script.
The former one have to be investigated but for the latter one it's simple:
Do not mix myqsl_ and PDO in one script: you are opening 2 connections at a time.
When using PDO, open connection only once and then use it throughout your code.
Reducing the number of connections in one script is the only way to go.
If you have multiple instances of PDO class in your code, you will need to add that timeout handling code you want to every call. So, heavy code rewriting required anyway.
Replace these new instances with global $pdo; instead. It will take the same amount of time but it will be permanent solution, not temporary patch as you want it.
Please be sensible.
PHP automatically closes all the connections st the end of the script, you don't have to care about closing them manually.
Having only one connection throughout one script is a common practice. It is used by ALL the developers around the world. You can use it without any doubts. Just use it.
If you have transaction and want to log something in database you sometimes need 2 connections in one script
I'm currently writing a PHP application and i noticed that my page loads kinda slow. I takes about 2 seconds (2.0515811443329 to be exact).
I've tracked down what the bottleneck was and it's the part where i'm creating a PDO connection to my MySQL database.
My 'connect()' method doesn't do any exoctic stuff. It simply looks like this:
public function connect ( $database, $host, $username, $password )
{
try
{
$this->db = new \PDO("mysql:dbname=".$database.";host=".$host, $username, $password);
if ( !$this->db )
{
throw new \Exception('Failed to connect to the database!');
}
$this->db->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
}
catch ( \Exception $e )
{
echo '<strong>Exception: </strong>'.$e->getMessage();
return false;
}
return true;
}
So when i comment out the call to the 'connect()' method, then my page loads in: 0.035506010055542
This is a huge difference. I can imagine that creating a connection to a database does take up some time, but it takes more than 1,5 seconds... I'm not sure if this is normal?
If it is normal, that it takes up that amount of time then is there a way to store the database connection? Like putting it in a session? Actually, as far as i know storing it in a session isn't possible. But it would be the ideal solution. Storing the connection somewhere until the user closes his browser.
In anyway, is there a problem with my PDO / MySQL? And can i simply store the connection resource somehow? So that i don't have to reconnect to my database everytime for every new page?
PS. I'm doing this all on a localhost (Windows).
You're probably making a connection with 'localhost' as address. Try to change that to '127.0.0.1'. That should fix the problem.
You can create a persistent connection to database using PDO. From the manual
Many web applications will benefit from making persistent connections to database servers. Persistent connections are not closed at the end of the script, but are cached and re-used when another script requests a connection using the same credentials. The persistent connection cache allows you to avoid the overhead of establishing a new connection every time a script needs to talk to a database, resulting in a faster web application.
And example:
<?php
$dbh = new PDO('mysql:host=localhost;dbname=test', $user, $pass, array(
PDO::ATTR_PERSISTENT => true
));
?>