how come PDO::bindParam is so slow? - php

SOLVED: The quoting of values in second test seemed to be the problem.
Instead doing 'VALUES ($id, $t1....)' I needed to do 'VALUES ("$id", "$t1".....), in the second test this was indeed throwing errors. which I couldn't see, only after printing them manually.
Hey there I've been working with bindParam lately and noticed the load time of my page went up dramatically.
So I spend a few hours to diagnose the problem and it seems that bindParam is using A LOT more processing time then the old fashion way (using parameters in query directly)
To make sure the results are valid, I did both tests twice.
Here is my test:
for ($j = 0; $j < 2; $j++) {
unset($t);
$dbh = new PDO('mysql:host=localhost;port=3306', 'root', 'root');
$dbh->query('USE Web_Amicrom_HQ');
$t['start'] = microtime(true);
for ($i = 0; $i < 50; $i++) {
$id = rand(1000000, 9999999);
$t1 = string_random(240);
$t2 = string_random(240);
$t3 = string_random(1000);
$ins = $dbh->prepare('INSERT INTO `test` (id, t1, dt, t2, t3) VALUES(:1, :2, now(), :3, :4)');
$ins->bindParam(':1', $id, PDO::PARAM_INT);
$ins->bindParam(':2', $t1, PDO::PARAM_STR);
$ins->bindParam(':3', $t2, PDO::PARAM_STR);
$ins->bindParam(':4', $t3, PDO::PARAM_STR);
$ins->execute();
}
$t['loop_fact'] = microtime(true);
echo "---- with bindParam ----\n";
$str_result_bench = mini_bench_to($t);
echo $str_result_bench; // string return
echo "\n\n";
}
for ($j = 0; $j < 2; $j++) {
unset($t);
$dbh = new PDO('mysql:host=localhost;port=3306', 'root', 'root');
$dbh->query('USE Web_Amicrom_HQ');
$t['start'] = microtime(true);
for ($i = 0; $i < 50; $i++) {
$id = rand(1000000, 9999999);
$t1 = string_random(240);
$t2 = string_random(240);
$t3 = string_random(1000);
$ins = $dbh->prepare("INSERT INTO `test` (id, t1, dt, t2, t3) VALUES($id, $t1, now(), $t2, $t3)");
$ins->execute();
}
$t['loop_fact'] = microtime(true);
echo "---- with parameter in query ----\n";
$str_result_bench = mini_bench_to($t);
echo $str_result_bench; // string return
echo "\n\n";
}
Results:
---- with bindParam ----
total time : 3136.148ms
---- with bindParam ----
total time : 2645.822ms
---- with parameter in query ----
total time : 41.693ms
---- with parameter in query ----
total time : 52.9752ms
Things I tried.
Change quotes from Double to Single - No difference
bindParam without parameter type (e.g PDO::PARAM_INT) - No difference
Changed all param names (e.g :1 to :id etc.) - No difference
This is a big performance difference, especially for just a few queries.

In line 3 in your code you have the following error host=127 that is why bindParam has too slow benchmarks
$dbh = new PDO('mysql:host=127.0.0.1;port=3306', 'root', 'root');
EDIT
In your example you can avoid connecting to MySQL server 4 times (2 loops of 2 times each) and not close the connection once. Also there is not need to have an extra query to USE database Web_Amicrom_HQ, you can pass that piece of information in the connection.
Full code
$dbh = new PDO('mysql:host=localhost;dbname=Web_Amicrom_HQ', 'root', 'root');
for ($j = 0; $j < 2; $j++) {
$start = microtime(true);
for ($i = 0; $i < 50; $i++) {
$id = rand(1000000, 9999999);
$t1 = string_random(240);
$t2 = string_random(240);
$t3 = string_random(1000);
$ins = $dbh->prepare('INSERT INTO `test` (id, t1, dt, t2, t3) VALUES(:1, :2, now(), :3, :4)');
$ins->bindParam(':1', $id, PDO::PARAM_INT);
$ins->bindParam(':2', $t1, PDO::PARAM_STR);
$ins->bindParam(':3', $t2, PDO::PARAM_STR);
$ins->bindParam(':4', $t3, PDO::PARAM_STR);
$ins->execute();
}
echo "---- with bindParam ----\n";
echo microtime(true) - $start; // time elapsed
echo "\n\n";
}
for ($j = 0; $j < 2; $j++) {
$start = microtime(true);
for ($i = 0; $i < 50; $i++) {
$id = rand(1000000, 9999999);
$t1 = string_random(240);
$t2 = string_random(240);
$t3 = string_random(1000);
$ins = $dbh->prepare("INSERT INTO `test` (id, t1, dt, t2, t3) VALUES($id, $t1, now(), $t2, $t3)");
$ins->execute();
}
echo "---- with bindParam ----\n";
echo microtime(true) - $start; // time elapsed
echo "\n\n";
}
$dbh = null;

Q: Why is PDO::bindParam so slow?
A: PDO::bindParam isn't slow.
The benchmark test demonstrated in the question isn't a valid measure of bindParam performance.
The second test is going to throw errors for nearly every INSERT. That's because the string literals (?) are not enclosed in single quotes. I originally thought those would be interpreted as numeric. (Confusingly, we don't see a specification for the return from string_random(240). We don't see a definition of that function, or even any examples of what that returns.)
If that's returning a string of 240 characters which doesn't represent a valid numeric literal... the benchmark is comparing inserts of rows ~1500 characters (using bindParam) vs. no rows inserted.
My original answer (below) indicates modifications to the two tests so that they perform equivalent functions. This would be more valid comparison of bindParam vs not bindParam.
ORIGINAL ANSWER
For improved performance, call prepare and bindParam functions one time, before entering the loop. (It's not necessary to call prepare on the same SQL statement multiple times.)
$sql = 'INSERT INTO `test` (id, t1, dt, t2, t3) VALUES(:1, :2, NOW(), :3, :4)';
$ins = $dbh->prepare($sql);
$id = 0;
$t1 = 0;
$t2 = 0;
$t3 = 0;
$ins->bindParam(':1', $id, PDO::PARAM_INT);
$ins->bindParam(':2', $t1, PDO::PARAM_STR);
$ins->bindParam(':3', $t2, PDO::PARAM_STR);
$ins->bindParam(':4', $t3, PDO::PARAM_STR);
for ($i = 0; $i < 50; $i++) {
$id = rand(1000000, 9999999);
$t1 = string_random(240);
$t2 = string_random(240);
$t3 = string_random(1000);
$ins->execute();
}
If we were using bindValue instead of bindParam, we would need to do the bindValue immediately before the execute, each time.
For the second test (not using bindParam), the code is vulnerable to SQL Injection, unlike the first test. To make this second test equivalent to the first, we'd need to ensure that the values are properly escaped so they are safe for inclusion in the SQL text.
for ($i = 0; $i < 50; $i++) {
$id = rand(1000000, 9999999);
$t1 = string_random(240);
$t2 = string_random(240);
$t3 = string_random(1000);
$sql = "INSERT INTO `test` (id, t1, dt, t2, t3) VALUES ( "
. $dbh->quote( $id ) . ", "
. $dbh->quote( $t1 ) . ", "
. "NOW(), "
. $dbh->quote( $t2 ) . ", "
. $dbh->quote( $t3 ) . ")";
$ins = $dbh->prepare($sql);
$ins->execute();
}
Note that the quote function does more than just put quotes around values. It also "escapes" single quotes included within the text, and other characters that would be interpreted by MySQL. (In the original second test, we don't see single quotes around the values, so it looks like MySQL would have evaluated those in a numeric context. We'd expect a lot of those rows would have "0" in the t1, t2 and t3 columns.
I'd be interested in seeing the comparison in timing of these two (modified) tests.

Related

Avoiding code repetition for MySQL queries (multiple while)

I have the following code:
<?php
include_once "connect.php";
$question_01 = mysqli_real_escape_string($con, $_POST['question_01']);
// $question_02 - $question_09 go here...
$question_10 = mysqli_real_escape_string($con, $_POST['question_10']);
$i = 0;
$array_sum=[];
while ($i < 10){
$i++;
$sql = "SELECT * FROM parteners WHERE question_no = 1 AND answer_variant = '$question_01'";
$result = mysqli_query($con, $sql);
$final_array_1 = array();
while ($row = mysqli_fetch_array($result, MYSQLI_NUM))
{
$final_array_1 = $row;
$array_sum = array_map(function () {
return array_sum(func_get_args());
}, $array_sum, $final_array_1);
}
}
print_r($final_array_1);
As you can see, I need to repeat the code for each $question_##. Is there a smarter way of doing this other than repeating the code? I'm not only concerned about turning everything into a code spaghetti but also about the efficiency of the operations as in loading times.
Let me know if you need clarification.
Update: Basically it should increase the value of "question_no" in the query until it reaches 10 and pick the corresponding $_POST value for each question.
There are two ways, variable variables or arrays. I'd suggest arrays as they are less prone to throwing errors everywhere.
<?php
include_once "connect.php";
$questions = array();
$questions[1] = mysqli_real_escape_string($con, $_POST['question_01']);
// $question_02 - $question_09 go here...
$questions[10] = mysqli_real_escape_string($con, $_POST['question_10']);
$i = 0;
$array_sum=[];
while ($i < 10){
$i++;
$sql = "SELECT * FROM parteners WHERE question_no = $i AND answer_variant = '".$questions[$i]."'";
$result = mysqli_query($con, $sql);
$final_array_1 = array();
while ($row = mysqli_fetch_array($result, MYSQLI_NUM))
{
$final_array_1 = $row;
$array_sum = array_map(function () {
return array_sum(func_get_args());
}, $array_sum, $final_array_1);
}
}
print_r($final_array_1);
EDIT: The reason I used an array instead of just straight up using the POST variable in the while loop is so there is room before you run anything for validation (ensuring your question array contains 10 posted values etc)
I would build one SQL-Statement which contains all questions and anwsers and do the rest with programming logic. SQL-Queries in a loop are a bad idea, because you have to do a lot of overhead for getting a task done, which the database server can do better. Also you should use prepared statements for performance and security.
$query = "SELECT * FROM parteners WHERE (question_no = 1 AND answer_variant = ?) OR (question_no = 2 AND answer_variant = ?) OR (question_no = 3 AND answer_variant = ?) OR (question_no = 4 AND answer_variant = ?) OR (question_no = 5 AND answer_variant = ?) OR (question_no = 6 AND answer_variant = ?) OR (question_no = 7 AND answer_variant = ?) OR (question_no = 8 AND answer_variant = ?) OR (question_no = 9 AND answer_variant = ?) OR (question_no = 10 AND answer_variant = ?)"
$stmt = myqli_prepare($query);
mysqli_stmt_bind_param($stmt, 'ssssssssss', $question_01, $question_02, $question_03,.....);
mysqli_stmt_execute($stmt);
$result = mysqli_stmt_get_result($stmt);
First, to make your code modern and efficient, you should be using PHP
Data Objects, or PDO for short. You will have access to prepared
statements, which are made exactly for this: you build a query
"template" and execute with different data, very efficiently and secure.
The loop is the proper way to do it. Also, your $questions array is a
bit unecessary since you can retrieve data from $_POST right inside
your loop. But if you want to use it, there is no need to "escape" the
string for the database, since it's handled by PDO. So you can build
your array in a easier way:
$questions = [
$_POST['question_01'],
$_POST['question_02'],
$_POST['question_03'],
# ...
$_POST['question_10'],
];
Your loop with PDO:
$dbh = ... # create your database handle, connect to it
$st = $dbh->prepare("
SELECT * FROM parteners
WHERE question_no = ? AND answer_variant = ?;
");
foreach (range(1, 10) as $i) {
$result = $st->execute([ $i, $questions[$i-1] ]);
# or, to build directly
$result = $st->execute([
$i, $_POST[ sprintf("question_%02d", $i) ]
]);
$final_array[] = $result->fetchAll(PDO::FETCH_NUM);
}
print_r($final_array);

var_dump shows null, but value is returned from query

Value is returned from query for $taxonomy_id , but var_dump($taxonomy_id) shows null.
$stmt = $db->query("SELECT rt.taxonomy_id FROM request_taxonomy rt LEFT JOIN request_aspects ra ON ra.aspect_id = rt.request_aspects_id
WHERE rt.requests_id = $requestID and rt.sort_order=$old_sort_order");
$taxonomy_id = $stmt->fetchColumn();
for($i=0;$i<$trcount;$i++)
{
$stmt = $db->prepare("INSERT INTO request_meta (requests_id,request_taxonomy_id,meta_value,staff_id) VALUES($requestID,$taxonomy_id,?,93)");
$stmt->bindValue(1, $_REQUEST['paraname'.$i]);
$stmt->execute();
$taxonomy_id+=6;
}
Due to which $taxonomy_id+=6; is not working , despite all the efforts I have put in to make it work.
$taxonomy_id++ works, but I want to add to add a constant number to it.
try this
$stmt = $db->prepare("INSERT INTO request_meta (requests_id,request_taxonomy_id,meta_value,staff_id) "
. "VALUES($requestID,?,?,93)");
for ($i = 0; $i < $trcount; $i++) {
$stmt->execute([$taxonomy_id, $_REQUEST['paraname' . $i]]);
$taxonomy_id += 6;
}
From PHP Manual: PDOStatement::fetchColumn() will "Returns a single column from the next row of a result set or FALSE if there are no more rows."
So, if it return FALSE (boolean), then you cannot to use incrementing operator (+=).

How to insert or update multiple rows - PDO

I have a small problem with the request to the base of the PDO.
I want to insert or update fields in one request. I want insert a few records to DB, but if there are already 'file_name' fields with the selected name then just to update the other fields. Below paste my php code.
For example: I adding 5 records id_product (eg. 1) and various other fields.
Example fields recived from the $_post:
(id_product, alt, file_name, main_photo)
1, xxx, 1_1, 0;
1, xxx, 1_2, 0;
1, xxx, 1_3, 1;
my PHP code:
$query = "INSERT INTO product_image (id_product, alt, file_name, main_photo) VALUES ";
$part = array_fill(0, count($_POST['file_name']), "(?, ?, ?, ?)");
$query .= implode(",", $part);
$query .= " ON DUPLICATE KEY UPDATE alt=VALUES(alt), main_photo=VALUES(main_photo)";
$stmt = $this->_db->prepare($query);
$j = 1;
$im_null = 0;
for ($i = 0; $i < count($_POST['file_name']); $i++) {
$stmt->bindParam($j++, $_POST['id_product'], \PDO::PARAM_INT);
$stmt->bindParam($j++, $_POST['alt'][$i], \PDO::PARAM_STR);
$stmt->bindParam($j++, $profile->base64_to_jpeg($_POST['file_name'][$i], APP_ROOT . '/uploads/products/' . $_POST['id_product'] . '_' . $_POST['file_nr'][$i]), \PDO::PARAM_STR);
($_POST['main_photo'][$i] == 1) ? $stmt->bindParam($j++, $_POST['main_photo'][$i], \PDO::PARAM_INT) : $stmt->bindParam($j++, $im_null);
}
$stmt->execute();
In this query inserts works good, but doesn't update, which is the second part of the request.
Move $j = 1; inside loop as it is you keep on incrementing $j.
$im_null = 0;
for ($i = 0; $i < count($_POST['file_name']); $i++) {
$j = 1;

PDO SQL single query, multiple rows and values

I have this array JSON POST request to a PHP file.
Array
(
[user_id] => 1
[date] => 2014-12-05
[time] => 12:00
[description] => lol
[friends] => "12","9"
[PHPSESSID] => 5ae7c3e6339c528e7804020dd0f0cdbb
)
I try to add the values (12 | 1) and (9 | 1) to a mysql table with a single sql query
Table:
u_id | f_id
1 | 12
1 | 9
What I have so far:
$friendarray = $_POST['Friends'];
foreach( $friends as $friendsarray ) {
$values[] = "(" . $u_id . "," . $friendsarray . ")";
}
$query = "INSERT INTO up2_friends_to_users (u_id , f_id ) VALUES ".implode(',',$values);
$stmt = $db->prepare($query);
$result = $stmt->execute();
As you see this is not working at all. I try to achieve something like this:
$query_params = array(
':u_id' => $_POST['user_id'],
':f_id' => $friendid,
And then would like to send it like this:
$stmt = $db->prepare($query);
$result = $stmt->execute($query_params);
Is it possible to create a single query with multiple rows like this?
Answer thanks to RobP:
$friendsarray = explode(',',$_POST['friends']);
$placeholders = [];
for($i=0, $len=count($friendsarray); $i < $len; $i++) {
$placeholders[i] .= "(:u_id".$i.", :f_id".$i.")"; // entries like "(:u_id0, :f_id0)"
}
$query = "INSERT INTO up2_friends_to_users (u_id , f_id ) VALUES ".implode(",", $placeholders);
$stmt = $db->prepare($query);
for($i=0, $len=count($placeholders); $i < $len; $i++) {
$stmt->bindParam(':u_id'.$i, $_POST['user_id']);
$nextFriend = $friendsarray[$i];
$stmt->bindParam(':f_id'.$i,trim($nextFriend,'"'));
}
$result = $stmt->execute();
Now f_id is always null.
I agree the best strategy is to use a single query as you were trying to do. This will be much faster for long lists, especially if you don't wrap all the individual inserts into a single commit. This should work:
$friendarray = $_POST['Friends'];
$placeholders = [];
$user_id = $_POST[`user_id`];
for($i=0, $len=count($friendarray); $i < $len; $i++) {
$placeholders[$i] = "(:u_id".$i.", :f_id".$i.")"; // entries like "(:u_id0, :f_id0)"
}
$query = "INSERT INTO up2_friends_to_users (u_id , f_id ) VALUES ".implode(",", $placeholders);
$stmt = $db->prepare($query);
for($i=0, $len=count($placeholders); $i < $len; $i++) {
// each binding must use a separate variable, not an array element
$stmt->bindParam(':u_id'.$i, $user_id);
// use your favorite escape function on the value here
$nextFriend = $db->real_escape_string($friendarray[$i]);
$stmt->bindValue(':f_id'.$i, $nextFriend);
}
EDIT: learned something new from Only variables can be passed by reference - php. Can't pass array elements to bindParam as second parameter! Workaround posted above.
Do this:
$query = "INSERT INTO up2_friends_to_users (u_id , f_id ) VALUES (:u_id, :f_id)";
$stmt = $db->prepare($query);
$stmt->bindParam(':u_id', $_POST['user_id'];
$stmt->bindParam(':f_id', $friendid);
foreach ($_POST['Friends'] as $friendid) {
$stmt->execute();
};
bindParam binds to a reference, so every time you execute the query it will use the value of $friendid from the current iteration of the loop.
Maybe, something like this (using question mark parameters)?
$values = array();
foreach ($_POST['Friends'] as $friendid) {
$values[] = $u_id;
$values[] = $friendid;
}
$conn = new \PDO($dsn, $user, $password);
$query = 'INSERT INTO up2_friends_to_users (u_id , f_id ) VALUES '
. trim(str_repeat('(?, ?),', count($values / 2)), ',');
$conn->prepare($query)->execute($values);

How to insert the array value inside for loop in php

Here I am having array value inside the for loop, and insert query outside the for loop.
Need to know how to connect the array value inside the insert query
Here My code is
$start = php2MySqlTime(js2PhpTime($st));
$count= (strtotime($et) - strtotime($st)) /60;
$count1 = $count/30; //echo $count1;
for($i=0;$i<=$count1;$i++){
$start = date("Y-m-d H:i:s",strtotime("+30 minutes",strtotime($start))).',';
echo $start;
}
$sql = "insert into `jqcalendar` (`list_id`,`totaltime`, `isalldayevent`)
values ('"
.$list_id."', '".$start."', '".mysql_real_escape_string($ade)."'
)";
By this code it's inserting only one value in array, But I need full array values to be inserted
Try this..Its just example to show you logic
$qry = 'INSERT INTO table (FirstName, LastName) VALUES ';
for($i=0;$i<=$count1;$i++){
$qry .= "($value['firstname'],$value['lastname']), ";
}
for ($i =0; $i< count($date); $i++ )
{
$data = array(
'date' => $date[$i]
);
$rs =$this->db->insert('table_name', $data);
}
Use :
$start = "";
for($i=0;$i<=$count1;$i++){
$start .= date("Y-m-d H:i:s",strtotime("+30 minutes",strtotime($start))).',';
echo $start;
}
$sql = "insert into `jqcalendar` (`list_id`,`totaltime`, `isalldayevent`)
values ('"
.$list_id."', '".$start."', '".mysql_real_escape_string($ade)."'
)";

Categories