Slow Performance within While Loop when Updating MYSQL Table - php

The following code runs incredibly slowly when performing a WHILE LOOP using data from table product and updating another table stock_figures within the same database.
The code loops through each row in product taking the value from product_id and wholesale_price and then performs some calculations on the product table before updating the stock_figures table with the values.
I'd be grateful of any suggestions which would improve the performance of my queries.
PHP WHILE LOOP
<?
// Retrieve data from database
$loop = " SELECT product_id, wholesale_price FROM product";
$query= mysql_query($loop);
while($rows=mysql_fetch_assoc($query))
{
$row = mysql_fetch_row($query);
$id = $row[0];
$price = $row[1];
?>
QUERIES WITHIN WHILE LOOP
<?
$bawtry_stock = "
SELECT product_id,
( kids_uk_j_105 + kids_c_17 + kids_c_18 + kids_c_19 + ... etc )
AS SUM FROM product WHERE product_id = '$id'";
$result_bawtry = mysql_query($bawtry_stock) or die (mysql_error());
$line = mysql_fetch_row($result_bawtry);
$bawtry = $line[1];
$chain_stock = "
SELECT product_id,
(quantity_c_size_26_chain + quantity_c_size_28_chain + quantity_c_size_30_chain +
... etc )
AS SUM FROM product WHERE product_id = '$id'";
$result_chain = mysql_query($chain_stock) or die (mysql_error());
$line = mysql_fetch_row($result_chain);
$chain = $line[1];
/*
* Declare the total value of all pairs from Bawtry, Chain
*/
$totalpairs = $chain + $bawtry;
/*
* Insert values for stock to write to databse
* Total stock for Bawtry, Chain
* Total value of stock for Bawtry, Chain
*
*/
$bawtry_value = (float)($bawtry * $price);
$chain_value = (float)($chain * $price);
$total_value = (float)($price * ($bawtry + $chain));
$sql2="
UPDATE stock_figures SET
bawtry_stock='$bawtry',
chain_stock='$chain',
totalstock='$totalpairs',
bawtry_value='$bawtry_value',
chain_value='$chain_value',
totalvalue='$total_value'
WHERE id='$id'";
$result2=mysql_query($sql2) or die (mysql_error());
?>
// close while loop
<? } ?>
UPDATED CODE
$sql = "SELECT product_id, wholesale_price,
(kids_uk_j_105 + kids_c_17 + kids_c_18 + kids_c_19 + kids_c_20 + kids_c_21 +
... )
AS bawtry,
(quantity_c_size_26_chain + quantity_c_size_28_chain + quantity_c_size_30_chain +
... )
AS chain from product";
$result = mysql_query($sql) or die (mysql_error());
while ($line=mysql_fetch_assoc($result))
{
$id = $line['product_id'];
$price = $line['wholesale_price'];
$bawtry = $line['bawtry'];
$chain = $line['chain'];
/*
* Declare the total value of all pairs from Bawtry, Chain
*/
$totalpairs = $chain + $bawtry;
/*
* Insert values for stock to write to database
* Total stock for Bawtry, Chain
* Total value of stock for Bawtry, Chain
*
*/
$bawtry_value = (float)($bawtry * $price);
$chain_value = (float)($chain * $price);
$total_value = (float)($price * ($bawtry + $chain));
$sql2="
UPDATE stock_figures SET
bawtry_stock='$bawtry',
chain_stock='$chain',
totalstock='$totalpairs',
bawtry_value='$bawtry_value',
chain_value='$chain_value',
totalvalue='$total_value'
WHERE id='$id'";
$result2=mysql_query($sql2) or die (mysql_error());
However, it's still taking an absolute age to complete. It seems to run really fast when I comment out the UPDATE statement at the end. Obviously this needs to remain in the code, so I'll probably run the whole thing as a cronjob.
Unless any further improvements can be suggested?

It seems you doing a lot of wasted selects.
You first select some data from table products, then for each row you select again from the same table. Twice. Then finally inserting this into another table, stock_figures.
And the only operation you are doing is adding lots of figures together.
All of this can be done in a single query.
select product_id,
whole_sale_price,
sum(kids_uk_j_105,
kids_c_17,
...) as bawtry,
sum(quantity_c_size_26_chain,
quantity_c_size_28_chain,
...) as chain
from products;
If this still is taking lots of time you need to check some server settings and also number of rows
Every write you make is a transaction and depending on your ACID-level it might be slow to do commits. Change innodb-flush-log-at-trx-commit to 2 will speed up writes.
You are doing a full table scan on products-table. I guess this is intended but if that table is big reading it will take a while, and writing all those rows back to stock_figures is going to take even longer.
Consider another approach. For each write (insert, update or delete) to products have a trigger update the corresponding row in stock_figures. Not only will it eliminate the batch job, it will also make stock_figures be correct at any given time.

The first thing is:
$row = mysql_fetch_row($query);
$id = $row[0];
$price = $row[1];
I don't know if it does work for you, but you already take $rows in your while condition so probably you should change it into:
$id = $rows['product_id'];
$price = $row['wholesale_price'];
Then the next 2 queries you can combine info:
SELECT product_id,
( kids_uk_j_105 + kids_c_17 + kids_c_18 + kids_c_19 + ... etc )
AS `SUM` FROM product WHERE product_id = '$id'
UNION ALL
SELECT product_id,
(quantity_c_size_26_chain + quantity_c_size_28_chain + quantity_c_size_30_chain +
... etc )
AS `SUM` FROM product WHERE product_id = '$id'
or even:
SELECT product_id,
( kids_uk_j_105 + kids_c_17 + kids_c_18 + kids_c_19 + ... etc )
AS `SUM1`,
(quantity_c_size_26_chain + quantity_c_size_28_chain + quantity_c_size_30_chain +
... etc )
AS `SUM2`
FROM product WHERE product_id = '$id'
because those 2 queries are run on the same table.
But in fact you can use just one query to get everything about your products as Andreas Wederbrand pointed in his answer.
But there are more problems:
You use old mysql_ function instead of mysqli_ or PDO and your code is vulnerable to SQL Injection
For each product you run 2 extra queries (select with union all if you go my way and update).
I don't know how many products you have, but if you have for example 1000 products or 10000 products you cannot expect it will be very fast. In that case you should somehow run your script in cron or refresh the page and do the job for small amount of products (for example for 10 or 100 at one time)
You should also consider if your database structure is the best one. Usually using many columns as you here kids_uk_j_105, kids_c_17, kids_c_18 is not the best choice.
I hope you have set key primary_id at product_id column as least.

When executing many SQL commands, parsing them takes some time. You can reduce this overhead by using http://php.net/manual/en/mysqli.quickstart.prepared-statements.php
How much you gain, depends on case.
Prepared statements are also good for security reasons.
This answer does not void other answers here. Try to gain efficiency by reducing number of queries, analyzing their work, merging them if possible etc.

Related

update all rows in table with different data php mysql

I have an application where investment profit should be calculated on daily basis, which I intend to do using a cron job.
Presently, based on the rate a user has and the amount he possess, I have written a statement to calculate the profit as
$rate = $row_cron['rate'];
$amount = $row_cron['amount'];
$usernamex = $row_cron['Username'];
$check = $rate * $amount / 100;
$sql = "UPDATE users
SET invest = invest + $check
WHERE status = 'member'";
to fetch my records I have also created a recordset of data using
mysql_select_db($database_emirate, $emirate);
$query_cron = "SELECT * FROM users";
$cron = mysql_query($query_cron, $emirate) or die(mysql_error());
$row_cron = mysql_fetch_assoc($cron);
$totalRows_cron = mysql_num_rows($cron);
I have used this query to get all users in my table
Now the above code snippets only returns the first record in my table to be updated.
What I am trying to achieve is to return all records in my table so as to calculate and update their records at the same time, based on their various column data below
$rate = $row_cron['rate'];
$amount = $row_cron['amount'];
please help
Simply use pure SQL as you are updating the same table with calculated column. No loops are needed. Just run one query in PHP.
UPDATE `users`
SET invest = invest + (rate * amount / 100)
WHERE status = 'member'
Do note: SQL allows arithmetic calculations and some RDBMS's like MySQL even maintains mathematical and statistical functions.

Fast select by category and price range in large mysql table

I need to select a random product from large MySQL table(~9000000 rows, 4.5GiB) with specific category ID and price range.
The solution I am using now takes about 30 seconds to complete request.
$query = mysqli_query($db, "SELECT MAX(id) FROM `products` WHERE category = $cat AND price >= $price_min AND price <= $price_max");
$f = mysqli_fetch_array($query);
$max_id = $f[0];
$random_id = rand(0, $max_id);
mysqli_free_result($query);
$query = mysqli_query($db, "SELECT * FROM `products` id=$random_id LIMIT 1");
$product = mysqli_fetch_assoc($query);
Is it possible to optimize the request?
To get some speed, you'll need to look into making sure category has an index.
But you can cut out a query if you use this:
$query = mysqli_query(
$db,
"
SELECT *
FROM `products`
WHERE
`category` = $cat
AND `price` BETWEEN $price_min AND $price_max
ORDER BY RAND()
LIMIT 1
"
);
$product = mysqli_fetch_assoc($query);
Hope this helps.
Use EXPLAIN to see the execution plan for the query. Make sure that MySQL is making use of an appropriate index to satisfy the query.
Reference: https://dev.mysql.com/doc/refman/5.5/en/using-explain.html
An appropriate index for this query would have category as a leading column, followed by price, and including the id column.
As an example:
CREATE INDEX products_IX1 ON products (category, price, id);
With that index in place, we'd expect the EXPLAIN output to show that MySQL is performing a range scan operation on the index, using the first two columns (as indicated by key_len column.) We also expect the EXPLAIN output to show "Using index" in the Extra column, since the index is a "covering" index for the query.
NOTE
This is not related to the question you asked, but...
The code example is vulnerable to SQL Injection. Potentially unsafe values that are incorporated into the text of a SQL statement must be properly escaped.
http://php.net/manual/en/mysqli.real-escape-string.php
A better pattern is to make use of prepared statements with bind placeholders.

How to handle/optimize thousands of different to executed SELECT queries?

I need to synchronize specific information between two databases (one mysql, the other a remote hosted SQL Server database) for thousands of rows. When I execute this php file it gets stuck/timeouts after several minutes I guess, so I wonder how I can fix this issue and maybe also optimize the way of "synchronizing" it.
What the code needs to do:
Basically I want to get for every row (= one account) in my database which gets updated - two specific pieces of information (= 2 SELECT queries) from another SQL Server database. Therefore I use a foreach loop which creates 2 SQL queries for each row and afterwards I update those information into 2 columns of this row. We talk about ~10k Rows which needs to run thru this foreach loop.
My idea which may help?
I have heard about things like PDO Transactions which should collect all those queries and sending them afterwards in a package of all SELECT queries, but I have no idea whether I use them correctly or whether they even help in such cases.
This is my current code, which is timing out after few minutes:
// DBH => MSSQL DB | DB => MySQL DB
$dbh->beginTransaction();
// Get all referral IDs which needs to be updated:
$listAccounts = "SELECT * FROM Gifting WHERE refsCompleted <= 100 ORDER BY idGifting ASC";
$ps_listAccounts = $db->prepare($listAccounts);
$ps_listAccounts->execute();
foreach($ps_listAccounts as $row) {
$refid=$row['refId'];
// Refsinserted
$refsInserted = "SELECT count(username) as done FROM accounts WHERE referral='$refid'";
$ps_refsInserted = $dbh->prepare($refsInserted);
$ps_refsInserted->execute();
$row = $ps_refsInserted->fetch();
$refsInserted = $row['done'];
// Refscompleted
$refsCompleted = "SELECT count(username) as done FROM accounts WHERE referral='$refid' AND finished=1";
$ps_refsCompleted = $dbh->prepare($refsCompleted);
$ps_refsCompleted->execute();
$row2 = $ps_refsCompleted->fetch();
$refsCompleted = $row2['done'];
// Update fields for local order db
$updateGifting = "UPDATE Gifting SET refsInserted = :refsInserted, refsCompleted = :refsCompleted WHERE refId = :refId";
$ps_updateGifting = $db->prepare($updateGifting);
$ps_updateGifting->bindParam(':refsInserted', $refsInserted);
$ps_updateGifting->bindParam(':refsCompleted', $refsCompleted);
$ps_updateGifting->bindParam(':refId', $refid);
$ps_updateGifting->execute();
echo "$refid: $refsInserted Refs inserted / $refsCompleted Refs completed<br>";
}
$dbh->commit();
You can do all of that in one query with a correlated sub-query:
UPDATE Gifting
SET
refsInserted=(SELECT COUNT(USERNAME)
FROM accounts
WHERE referral=Gifting.refId),
refsCompleted=(SELECT COUNT(USERNAME)
FROM accounts
WHERE referral=Gifting.refId
AND finished=1)
A correlated sub-query is essentially using a sub-query (query within a query) that references the parent query. So notice that in each of the sub-queries I am referencing the Gifting.refId column in the where clause of each sub-query. While this isn't the best for performance because each of those sub-queries still has to run independent of the other queries, it would perform much better (and likely as good as you are going to get) than what you have there.
Edit:
And just for reference. I don't know if a transaction will help here at all. Typically they are used when you have several queries that depend on each other and to give you a way to rollback if one fails. For example, banking transactions. You don't want the balance to deduct some amount until a purchase has been inserted. And if the purchase fails inserting for some reason, you want to rollback the change to the balance. So when inserting a purchase, you start a transaction, run the update balance query and the insert purchase query and only if both go in correctly and have been validated do you commit to save.
Edit2:
If I were doing this, without doing an export/import this is what I would do. This makes a few assumptions though. First is that you are using a mssql 2008 or newer and second is that the referral id is always a number. I'm also using a temp table that I insert numbers into because you can insert multiple rows easily with a single query and then run a single update query to update the gifting table. This temp table follows the structure CREATE TABLE tempTable (refId int, done int, total int).
//get list of referral accounts
//if you are using one column, only query for one column
$listAccounts = "SELECT DISTINCT refId FROM Gifting WHERE refsCompleted <= 100 ORDER BY idGifting ASC";
$ps_listAccounts = $db->prepare($listAccounts);
$ps_listAccounts->execute();
//loop over and get list of refIds from above.
$refIds = array();
foreach($ps_listAccounts as $row){
$refIds[] = $row['refId'];
}
if(count($refIds) > 0){
//implode into string for use in query below
$refIds = implode(',',$refIds);
//select out total count
$totalCount = "SELECT referral, COUNT(username) AS cnt FROM accounts WHERE referral IN ($refIds) GROUP BY referral";
$ps_totalCounts = $dbh->prepare($totalCount);
$ps_totalCounts->execute();
//add to array of counts
$counts = array();
//loop over total counts
foreach($ps_totalCounts as $row){
//if referral id not found, add it
if(!isset($counts[$row['referral']])){
$counts[$row['referral']] = array('total'=>0,'done'=>0);
}
//add to count
$counts[$row['referral']]['total'] += $row['cnt'];
}
$doneCount = "SELECT referral, COUNT(username) AS cnt FROM accounts WHERE finished=1 AND referral IN ($refIds) GROUP BY referral";
$ps_doneCounts = $dbh->prepare($doneCount);
$ps_doneCounts->execute();
//loop over total counts
foreach($ps_totalCounts as $row){
//if referral id not found, add it
if(!isset($counts[$row['referral']])){
$counts[$row['referral']] = array('total'=>0,'done'=>0);
}
//add to count
$counts[$row['referral']]['done'] += $row['cnt'];
}
//now loop over counts and generate insert queries to a temp table.
//I suggest using a temp table because you can insert multiple rows
//in one query and then the update is one query.
$sqlInsertList = array();
foreach($count as $refId=>$count){
$sqlInsertList[] = "({$refId}, {$count['done']}, {$count['total']})";
}
//clear out the temp table first so we are only inserting new rows
$truncSql = "TRUNCATE TABLE tempTable";
$ps_trunc = $db->prepare($truncSql);
$ps_trunc->execute();
//make insert sql with multiple insert rows
$insertSql = "INSERT INTO tempTable (refId, done, total) VALUES ".implode(',',$sqlInsertList);
//prepare sql for insert into mssql
$ps_insert = $db->prepare($insertSql);
$ps_insert->execute();
//sql to update existing rows
$updateSql = "UPDATE Gifting
SET refsInserted=(SELECT total FROM tempTable WHERE refId=Gifting.refId),
refsCompleted=(SELECT done FROM tempTable WHERE refId=Gifting.refId)
WHERE refId IN (SELECT refId FROM tempTable)
AND refsCompleted <= 100";
$ps_update = $db->prepare($updateSql);
$ps_update->execute();
} else {
echo "There were no reference ids found from \$dbh";
}

Adding auction bids to MySQL table

I am currently working on a simple auction site. I am storing bids in their own MySQL table called 'bids'. I am wondering what is the best way of ensuring that two of the same bids are not submitted at the exact same time.
My current strategy for verifying that the bid submitted is in fact the highest bid is to do the following (as an example):
$sql = "SELECT * FROM bids WHERE amount >= '".$bidamount."'";
$result = mysql_query($sql);
if(mysql_num_rows($result) == 0) {
$sql = "INSERT INTO bids SET amount = '".$bidamount."'";
mysql_query($sql);
$bidid = mysql_insert_id();
}
The problem with the above set of queries is that between the time the SELECT query is run and the INSERT query is run, another user could insert the same bid.
Is there some way to lock the table during the SELECT that would prevent this double-bidding from occurring? My main concern with locking tables for such a purpose would be performance problems when you have a lot of people bidding at once.
You may want to make conditional insert, like:
$amount = intval($amount);
$query = "
INSERT INTO
bids(amount)
SELECT
{$amount}
FROM
(SELECT 1) tmp_tbl
WHERE NOT EXISTS(
SELECT * FROM bids WHERE amount >= {$amount}
)
";
and check for affected (inserted) rows.

Php Fastest way to iterate through an array and update mysql?

I wrote a product price/stock update script for Magento. I load the csv into an array and then iterate through it. The current code takes around 10 minutes to complete for 5,000 products, is there a faster way to do this? I've already bypassed Magento's API as that was extremely slow and switched to updating the database directly since its not many tables and its faster. Using timers to record the time, it takes about 10 minutes for the foreach loop and two minutes for the reindexALL
$con = mysql_connect("localhost","root","");
$selected = mysql_select_db("magento",$con);
$processes = Mage::getSingleton('index/indexer')->getProcessesCollection();
$processes->walk('setMode', array(Mage_Index_Model_Process::MODE_MANUAL));
$processes->walk('save');
foreach($all_rows as $final)
{
$sql = mysql_query("SELECT entity_id from catalog_product_entity where sku = '".$final[ITEM]."'");
if ($row = mysql_fetch_array($sql)) {
//update price
$pricenew = $final['PRICE'] + ($final['PRICE']*.30);
mysql_query("UPDATE catalog_product_entity_decimal SET value = '$pricenew' where attribute_id = 75 AND entity_id = '".$row[entity_id]."' ");
//update retail price
$retailprice = $final['RETAIL'];
mysql_query("UPDATE catalog_product_entity_decimal SET value = '$retailprice' where attribute_id = 120 AND entity_id = '".$row[entity_id]."' ");
//update stock quantity and is in stock
$stockquantity = $final['QTY'];
$stockquantity = number_format($stockquantity, 4, '.', '');
mysql_query("UPDATE cataloginventory_stock_item SET qty = '$stockquantity', SET is_in_stock = 1 where product_id = '".$row[entity_id]."' ");
}
$processes->walk('reindexAll');
$processes->walk('setMode', array(Mage_Index_Model_Process::MODE_REAL_TIME));
$processes->walk('save');
mysql_close($con);
If your table catalog_product_entity_decimal has index, that covers id (obviously it is) - then you have no other ways to speed it up. Since the slowest thing here is physical changing of the value.
Probably you can put a WHERE clause to to avoid of updating the price to the same value.
Other thoughts:
While most people look at performance optimizations for SELECT statements, UPDATE and DELETE statements are often overlooked. These can benefit from the principles of analyzing the Query Execution Plan (QEP). You can only run an EXPLAIN on a SELECT statement, however it’s possible to rewrite an UPDATE or DELETE statement to perform like a SELECT statement.
To optimize an UPDATE, look at the WHERE clause. If you are using the PRIMARY KEY, no further analysis is necessary. If you are not, it is of benefit to rewrite your UPDATE statement as a SELECT statement and obtain a QEP as previously detailed to ensure optimal indexes are used. For example:
UPDATE t
SET c1 = ‘x’, c2 = ‘y’, c3 = 100
WHERE c1 = ‘x’
AND d = CURDATE()
You can rewrite this UPDATE statement as a SELECT statement for using EXPLAIN:
EXPLAIN SELECT c1, c2, c3 FROM t WHERE c1 = ‘x’ AND d = CURDATE()
You should now apply the same principles as you would when optimizing SELECT statements.

Categories