Why doesn't LOCK TABLES [table] WRITE prevent table reads? - php

According to http://dev.mysql.com/doc/refman/5.0/en/lock-tables.html if I lock a table for writing in mysql, no-one else should have access until it's unlocked. I wrote this script, loaded either as script.php or script.php?l=1 depending on what you want to do:
if ($_GET['l'])
{
mysqli_query("LOCK TABLES mytable WRITE");
sleep(10);
mysqli_query("UNLOCK TABLES");
}
else
{
$res=mysqli_query("SELECT * FROM mytable");
// Print Result
}
If I load script.php?l=1 in one browser window then, while it's sleeping, I should be able to load script.php in another window and it should wait until script.php?l=1 is finished, right?
Thing is, script.php loads right away, even though script.php?l=1 has a write lock. If I try to insert in script.php then it does wait, but why is the SELECT allowed?
Note: I am not looking for a discussion on whether to use LOCK TABLES or not. In fact I am probably going to go with a transaction, I am investigating that now, right now I just want to understand why the above doesn't work.

This happens because of query caching. There is a cache result available that doesn't 'affect' the lock, so the results are returned.
This can be avoided by adding the "SQL_NO_CACHE" keyword to the select:
SELECT SQL_NO_CACHE * FROM mytable

The point of LOCK is so that other sessions do not modify the table while you are using it during your specific session.
The reason that you are able to perform the SELECT query is because that's still considered part of the same MySQL session, even if you open up a new window.

Related

Mysql unable to update a row, when multiple selects are in process or taking too much time

I have a table called Settings which has only one row. The settings are very important in all the cases for my program, The Settings is been read by 200 to 300 users every second. I haven't used any caching yet. I cannot update the settings table for a value like Limit. Change the limit from 5 -10 Or anything from an API.
Ex: Limit Products 5 - 10. The update query runs forever.
From the Workbench, I can update the record, But from Admin Panel through API it's not updating or take too much time. Table - InnoDB
1. Already Tried Locking With Read - Write.
2. Transaction.
3. Made a View of the table and Tried to update the table, the Same Issue remains.
4. The Update query is fine from Workbench, But through an API. It runs all day.
Is there anyway, I can lock the read operations on the table and update the table. I have only one row in a table.
Any help would be highly appreciated, Thanks in advance.
This sounds like a really good use case for using query cache.
The query cache stores the text of a SELECT statement together with the corresponding result that was sent to the client. If an identical statement is received later, the server retrieves the results from the query cache rather than parsing and executing the statement again. The query cache is shared among sessions, so a result set generated by one client can be sent in response to the same query issued by another client.
The query cache can be useful in an environment where you have tables that do not change very often and for which the server receives many identical queries.
To enable the query cache, you can run:
SET GLOBAL query_cache_size = 1000000;
And then edit your mysql config file (typically /etc/my.cnf or /etc/mysql/my.cnf):
query_cache_size=1000000
query_cache_type=2
query_cache_limit=100000
And then for your query you can change it to:
SELECT SQL_CACHE * FROM your_table;
And that should make it so you are able to update the table (as it won't be constantly locked).
You would need to restart the server.
As an alternative, you could implement cacheing in your PHP application. I would use something like memcached, but as a very simplistic solution you could do something like:
$settings = json_decode(file_get_contents("/path/to/settings.json"), true);
$minute = intval(date('i'));
if (isset($settings['minute']) && $settings['minute'] !== $minute) {
$settings = get_settings_from_mysql();
$settings['minute'] = intval(date('i'));
file_put_contents("/path/to/settings.json", json_encode($settings), LOCK_EX);
}
Are the queries being run in the context of transactions with say a transaction isolation level for repeatable read? It sounds like the update isn't able to complete due to a lock on the table, in which case caching isn't likely to be able to help you, as on a write the cache will be purged. More information on repeatable reads can be found at https://www.percona.com/blog/2012/08/28/differences-between-read-committed-and-repeatable-read-transaction-isolation-levels/.

PHP - Query on page with many requests

I have around 700 - 800 visitors at all time on my home page (according to analytics) and a lot of hits in general. However, I wish to show live statistics of my users and other stuff on my homepage. I therefore have this:
$stmt = $dbh->prepare("
SELECT
count(*) as totalusers,
sum(cashedout) cashedout,
(SELECT sum(value) FROM xeon_stats_clicks
WHERE typ='1') AS totalclicks
FROM users
");
$stmt->execute();
$stats=$stmt->fetch();
Which I then use as $stats["totalusers"] etc.
table.users have `22210` rows, with index on `id, username, cashedout`, `table.xeon_stats_clicks` have index on `value` and `typ`
However, whenever I enable above query my website instantly becomes very slow. As soon as I disable it, the load time drastically falls.
How else can this be done?
You should not do it that way. You will eventually exhuast your precious DB resources, as you now are experiencing. The good way is to run a separate cronjob in 30 secs or 1 min interval, and then write the result down to a file :
file_put_contents('stats.txt', $stats["totalusers"]);
and then on your mainpage
<span>current users :
<b><? echo file_get_contents('stats.txt');?></b>
</span>
The beauty is, that the HTTP server will cache this file, so until stats.txt is changed, a copy will be upfront in cache too.
Example, save / load JSON by file :
$test = array('test' => 'qwerty');
file_put_contents('test.txt', json_encode($test));
echo json_decode(file_get_contents('test.txt'))->test;
will output qwerty. Replace $test with $stats, as in comment
echo json_decode(file_get_contents('stats.txt'))->totalclicks;
From what I can tell, there is nothing about this query that is specific to any user on the site. So if you have this query being executed for every user that makes a request, you are making thousands of identical queries.
You could do a sort of caching like so:
Create a table that basically looks like the output of this query.
Make a PHP script that just executes this query and updates the aforementioned table with the lastest result.
Execute this PHP script as a cron job every minute to update the stats.
Then the query that gets run for every request can be real simple, like:
SELECT totalusers cashedout, totalclicks FROM stats_table
From the query, I can't see any real reason to use a sub-query in there as it doesn't use any of the data in the users table, and it's likely that that is slowing it down - if memory serves me right it will query that xeon_clicks table once for every row in your users table (which is a lot of rows by the looks of things).
Try doing it as two separate queries rather than one.

mysql 'FOR UPDATE' command not working correctly

I have two php page, page1.php & page2.php
page1.php
execute_query('START TRANSACTION');
$res =execute_query('SELECT * FROM table WHERE id = 1 FOR UPDATE');
sleep(20);
print $res->first_name;
execute_query('COMMIT');
print"\n OK";
page2.php
$res =execute_query('SELECT * FROM table WHERE id = 1');
print $res->first_name;
I executing both pages almost same time
So according to the mysql 'FOR UPDATE' condition,the result in page2.php will display only after the execution of page1.php (ie after display 'OK' in page1.php), because both page reading same row.
But what is happening is,
page2.php suddenly display the result, even before completing the execution of page1.php
May i know whats wrong with ' FOR UPDATE' command.?
I'm assuming that the table is InnoDB (not MyISAM or MEMORY).
You are using a SELECT within a transaction. I don't know your isolation level, but I guess that your transactions are not blocking each other.
See this page for details: http://dev.mysql.com/doc/refman/5.5/en/set-transaction.html
EDIT:
I'm going to explain better this concept, as requested. The isolation level is a session/global variable which determines the way the transactions are performed. Some isolation levels block other transactions when they try to modify the same row, but some isolation levels don't.
For example, if you used UNCOMMITTED, it doesn't block anything, because you access the actual version of the rows (which may become obsolete before the transaction ends). The other SELECT (page2) only reads the table, so it doesn't have to wait that the first transaction ends.
SERIALIZABLE is much more safe. It is not the default because it is the slowest isolation level. If you are using it, make sure that FOR UPDATE still makes sense for you.
I Think your SELECT FOR UPDATE is inside BEGIN TRANSACTION, so it will not lock the record until COMMIT statement reached , and you delayed execution with sleep(20). so page2.php will be execute.

How to block a piece of code for multiple processes

As I have installed cron run the same script every minute. At the same time a script is executed several times.
There is such a part:
$query = mysql_query("select distinct `task_id` from tasks_pending where `checked`='0' and `taken`='0' limit 50");
Then, the obtained values ​​set "taken = 1"
Since several processes are executed at the same time the request Return the same data for different processes. Is it possible to somehow disable this part of her time to can perform only one process?
Sorry for bad English.
Use SELECT FOR UPDATE and it will block another process to select the same rows, before they are updated
You might want to lock the table before doing anything with it, and unlock it afterwards using the [UN]LOCK TABLES statements.
Otherwise, you could use a SELECT FOR UPDATE query within a transaction scope.

PHP, mysqli, and table locks?

I have a database table where I need to pull a row, test user input for a match, then update the row to identify the user that made the match. Should a race condition occur, I need to ensure that the first user's update is not overwritten by another user.
To accomplish this I intend to:
1. Read row
2. Lock table
3. Read row again and compare to original row
4. If rows match update, otherwise do nothing (another user has already updated the row)
Based on information I found on Google, I expected the lock table statement to block until a lock was aquired. I set up a little test script in PHP that would stall for 10 seconds to allow me time to manually create a race condition.
// attempt to increment the victor number
$aData["round_id"] = $DATABASE["round_id"];
// routine to execute a SELECT on database (ommited for brevity)
$aRound = $oRound->getInfo($aData);
echo "Initial Round Data:";
print_r($aRound);
echo "Locking...";
echo $oRound->lock();
echo "Stalling to allow for conflict...";
sleep(10);
echo "Awake...";
$aLockedRound = $oRound->getInfo($aData);
if($aRound["victor_nation"] == $aLockedRound["victor_nation"]){
$aData["victor_nation"] = $aRound["victor_nation"] + 1;
$oRound->update($aData);
echo "Incremented Victor Nation";
}
where the lock routine is defined as
function lock(){
global $oDatabase;
$iReturn = 0;
// lock the table
$iReturn = $oDatabase->m_oConnection->query("LOCK TABLES round WRITE");
return $iReturn;
}
Above, $oDatabase->m_oConnection is a mysqli connection that I use to execute prepared statements on the database.
When I run my test script I kick off the first user and wait for "Stalling to allow for conflict..." , then start a second script. On the second script I expected it to block at "Locking...", however, the second script also continues to "Stalling to allow for conflict...".
Since the LOCK statment doesn't appear to be blocking, nor returning any indicator of acquiring the lock (return value is echoed and blank), it's unclear to me that I'm actually acquiring a lock. Even if I am, I'm not sure how to proceed.
Any pointers?
Troubleshooting: You can test for table lock success by trying to work with another table that is not locked. If you obtained the lock, trying to write to a table that was not included in the lock statement should generate an error.
You may want to consider an alternative solution. Instead of locking, perform an update that includes the changed elements as part of the where clause. If the data that you are changing has changed since you read it, the update will "fail" and return zero rows modified. This eliminates the table lock, and all the messy horrors that may come with it, including deadlocks.

Categories