i have a code like this
reserve.php
$r=mysql_query("select count(*) from ticket");
$rec=mysql_fetch_array($r);
if ($rec[0]==0)
{
insert into ticket values .....
}
i have 1 ticket only.
two users request reserve.php.
"a" user request reserve.php and available ticket is 0 . but before insert, for "b" user available ticket is 0 yet. so two users reserve ticket.
table is Innodb.
how to prevent this? transaction , lock table or what?
In these situations I usually just use an UPDATE statement and check how many records were affected (mysql_affected_rows) by the update.
UPDATE tickets SET ticket_count=ticket_count-1 WHERE ticket_id=123 AND ticket_count>0
If someone else decremented the counter first, then no update occurs and you don't give the user a ticket. Autocommit should be enabled so the update is a self-contained "transaction".
Alternatively, you can change your SELECT to be SELECT ... FOR UPDATE
http://dev.mysql.com/doc/refman/5.0/en/innodb-locking-reads.html
I would do it in a single transaction, without a roundtrip back to the application level - both SELECT count() .... and INSERT INTO being sent in a single command from PHP and embedded into a TRANSACTION with EXCLUSIVE LOCK
Related
I'm writing an online game, there is a section named send troops. When two or more users on one account try to send one movement the troops get doubled.
I want to get a live version of the row from mysql and prevent any read, write, update anything on that row untill I finish.
Is it actually possible? Because I sae only select for update and lock in share mode in innodb reference.
Any help is appericiated.
BEGIN;
SELECT ... FROM t ... WHERE ... FOR UPDATE;
...
UPDATE t ...;
COMMIT;
Others can read the rows from t, but they will either be delayed or deadlocked if they try to modify the row(s) touched by the SELECT.
Do you really need to prevent all reads for a given row? Please explain your scenario further.
Please find mysql document below in order to lock a particular row in innodb:
https://dev.mysql.com/doc/refman/5.5/en/innodb-locking.html
I have one table that is read at the same time by different threads.
Each thread must select 100 rows, execute some tasks on each row (unrelated to the database) then they must delete the selected row from the table.
rows are selected using this query:
SELECT id FROM table_name FOR UPDATE;
My question is: How can I ignore (or skip) rows that were previously locked using a select statement in MySQL ?
I typically create a process_id column that is default NULL and then have each thread use a unique identifier to do the following:
UPDATE table_name SET process_id = #{process.id} WHERE process_id IS NULL LIMIT 100;
SELECT id FROM table_name WHERE process_id = #{process.id} FOR UPDATE;
That ensures that each thread selects a unique set of rows from the table.
Hope this helps.
Even though it is not the best solution, as there is no way that I know to ignore locked rows, I select a random one and try to obtain a lock.
START TRANSACTION;
SET #v1 =(SELECT myId FROM tests.table WHERE status is NULL LIMIT 1);
SELECT * FROM tests.table WHERE myId=#v1 FOR UPDATE; #<- lock
Setting a small timeout for the transaction, if that row is locked the transaction is aborted and I try another one. If I obtain the lock, I process it. If (bad luck) that row was locked, it is processed and the lock is released before my timeout, I then select a row that has already been 'processed'! However, I check a field that my processes set (e.g. status): if the other process transaction ended OK, that field tells me that work has already been done and I do not process that row again.
Every other possible solution without transactions (e.g. setting another field if the row has no status and ... etc.) can easily provide race conditions and missed processes (e.g. one thread abruptly dies, the allocated data is still tagged, while a transaction expires; ref. comment here
Hope it helps
Is there a way in MySQL and PHP to allow only the person performing an update to view information about a particular record?
For example, if one user loads the page they are presented with a record which must be updated, until that user finishes updating this record, any other user accessing this page should not be able to view this particular record.
You'll have to manually lock the row (by adding a IsLocked column) when someone requests the edit page, since the connection to the database is lost as soon as the PHP script execution ends (despite pooling et al, script execution is stopped so you cannot unlock from the same thread again since that connection may go to another script).
Don't forget to create a kind of unlocking script, initiated by cron for example, to unlock rows that have been locked for more than a given amount of time.
I don't recommend adding any column to data table in question. I'd rather create special locks table to hold the information:
create table locks (
tablename varchar,
primarykey int,
userid int,
locktime datetime);
Then take the following principles into consideration:
each PHP request is a standalone mysql connection, that's why solutions like SELECT ... FOR UPDATE won't work and that's why you need to keep userid of a person - who actually did first request and performed the lock
every access to locks table must lock the table as a whole (using MySQL's LOCK statement) to avoid concurrency in locking the same row
if there is no way to know, whether particular uses has abandoned editing the row by closing the window with record - then either locktime timeout must be short or you should provide some ping (i.e. AJAX) mechanism that would reset locktime as long as user is working on locked record
user can save changes to record as long as he/she owns the lock and locktime did not expire
tablename and primarykey are of course samples and you should adjust them to your needs :-)
add a "locked" column to the table, and once a user calls the edit form, set the "locked" db value to the user_id, and after save set it back to false/null.
In your view action, check the locked value
You can either add a field named like locked where you set a status. Maybe you also add a field like lockedtime where you save a timestamp how long the lock is active. That depends on your needs.
There are also possibilities to do this native. Like
SELECT * FROM table WHERE primarykey = x FOR UPDATE;
Does the following code really disable concurrent execution?
LOCK TABLES codes WRITE;
INSERT INTO codes VALUES ();
SELECT mid FROM codes ORDER BY expired DESC LIMIT 0,1;
UNLOCK TABLES;
The PHP will execute SQL. The PHP file will be requested by many users via HTTP. Would it really execute in isolation for every users?
mid is something which has to be unique for every user so I think I should use MySQL locks to achieve that.
If you have a table with an auto incremented key and you use mysql_insert_id just after insertion, it is guaranteed to be unique and it won't mix user threads (it fetches the ID on the connection you give). No need to lock the table.
http://php.net/manual/en/function.mysql-insert-id.php
I need to do two updates to rows but I need to make sure they are done together and that no other query from another user could interfere with them. I know about SELECT...FOR UPDATE but I imagine after the first update it will of course be unlocked which means someone could interfere with the second update. If someone else updates that row first, the update will work but will mess up the data. Is there anyway to ensure that the two updates happen how they are supposed to? I have been told about transactions but as far as I know they are only good for making sure the two updates actually happen and not whether they happen "together," unless I am mistaken and the rows will be locked until the transaction is committed?
Here are the queries:
SELECT z FROM table WHERE id='$id'
UPDATE table SET x=x+2 WHERE x>z
UPDATE table SET y=y+2 WHERE y>z
I made a mistake and didn't give full information. That was my fault. I have updated the queries. The issue I have is that z can be updated as well. If z is updated after the SELECT but before the other two updates, the data can get messed up. Does doing the transaction BEGIN/COMMIT work for that?
Learn about TRANSACTION
http://dev.mysql.com/doc/refman/5.0/en/commit.html
[... connect ...]
mysql_query("BEGIN");
$query1 = mysql_query('UPDATE table SET x=x+2 WHERE x>y');
$query2 = mysql_query('UPDATE table SET y=y+2 WHERE y>y');
if($query1 && $query2) {
mysql_query("COMMIT");
echo 'Save Done. All UPDATES done.';
} else {
mysql_query("ROLLBACK");
echo 'Error Save. All UPDATES reverted, and not done.';
}
There are various levels of transaction, but basically as per ACID properties you should expect that within a given transaction, all reads and updates are performed consistently meaning it will be kept in a valid state, but more importantly a transaction is isolated in that work being done in another transaction (thread) will not interfere with your transaction (your grouping of select & update SQL statements): this allows you to take a broad assumption that you are the only thread of execution within the system allowing you to commit that group of work (atomically) or roll it all back.
Each database may handle the semantics differently (some may lock rows or columns, some may re-order, some may serialize) but that's the beauty of a declarative database interface: you worry about the work you want to get done.
As stated, on MySQL InnoDB is transactional and will support what is mentioned above so ensure your tables are organized with InnoDB, other non-transactional engines (e.g. MyISAM) are not transactional and thus will force you to manage those transactional semantics (locking) manually.
One approach would be to lock the entire table:
LOCK TABLE `table` WRITE;
SELECT z FROM `table` WHERE id='$id';
UPDATE `table` SET x=x+2 WHERE x>z;
UPDATE `table` SET y=y+2 WHERE y>z;
UNLOCK TABLES;
This will prevent other sessions from writing, and reading, from the table table during the SELECTs and UPDATEs.
Whether this is an appropriate solution does depend on how appropriate it is for sessions to wait to read or write from the table.