I've locked entire tables before in MySQL but what I need to do is lock a specific row. More specifically, when my PHP script executes, I need it to Lock a specific row so it cannot be read from (or written to), run the rest of the code, then unlock the row when it's finished.
I know I need to use InnoDB for this, but I've been unable to find how to do this. I'm not sure if it requires transactions or not, and if so, how to use them for row level locking.
Update
Perhaps I'm thinking of this problem the wrong way. From the reading I've done, and been provided with, InnoDB tables auto lock rows when read/written, which I get. My concern though is that I want to introduce a delay in my PHP code via a sleep. I want any and all read attempts on the row that was previously read from to be locked until that sleep finishes, and the script finishes the rest of it's functions.
If I just run:
$result = $mysqli->prepare('...');
$result->bind_param('...', $...);
$result->execute();
$result->bind_result($...);
$result->fetch();
sleep('15');
//More script execution
$result->close();
Will that prevent other MySQL queries from accessing that selected row until I close the connection?
MySQL uses only table-level locking from MyISAM tables.For row-leve locking you would need to switch to innoDB.
Please see the documentation which tells about the lock functioning in mysql.
Related
We want to prevent some concurrency issues in a database (we use event sourcing and want to insert an event to the event log in case the event is a valid one).
The problem is that we need to check if a certain operation is allowed (requiring a SELECT query and some checks in php) and then run a INSERT query to actually perform the operation.
Now, we can simply LOCK the entire table, do our checks and if they succeed, then insert (and remove the lock).
The problem with this is that it locks the entire table, which is overkill (there will be lots of queries on this table). What I would like to do is to lock all queries that want to do this select-insert operation for a specific object_id, but allow queries for all other object_id's to continue as if there is no lock.
I searched a bit but couldn't find a lock attribute command. There seems to be a lock row command in innoDB, but it's not really what we want (I think I'm not 100% sure what it does).
We can of course try to manually handle the locks (check if there exists some column with object_id in some seperate lock table and wait untill there is none), but that feels a bit fishy and error prone.
So, here's the actual question: is it possible to lock a table for a specific value of a column (object_id)?
It would be awesome if the lock only held for the specific SELECT-INSERT queries, and not for standalone SELECT's, but that doesn't matter that much for now.
Consider manual arbitrary locks with GET_LOCK();
Choose a name specific to the rows you want locking. e.g. 'xxx_event_id_y'. Where 'xxx' is a string specific to the procedure and table and 'y' is the event id.
Call SELECT GET_LOCK('xxx_event_id_y',30) to lock the name 'xxx_event_id_y'.. it will return 1 and set the lock if the name becomes available, or return 0 if the lock is not available after 30 seconds (the second parameter is the timeout).
Use DO RELEASE_LOCK('xxx_event_id_y') when you are finished.
Be aware; You will have to use the same names in each transaction that you want to wait and calling GET_LOCK() again in a transaction will release the previously set lock.
GET_LOCK() docs
I actually use this method to lock our application cache too (even when it doesn't use the DB), so it has scope outside the database as well.
Migrate tables to innodb if not already done, and use transactions.
I've seen many posts explaining the usage of Select FOR UPDATE and how to lock a row, however I haven't been able to find any that explain what occurs when the code tries to read a row that's locked.
For instance. Say I use the following:
$con->autocommit(FALSE);
$ps = $con->prepare( "SELECT 1 FROM event WHERE row_id = 100 FOR UPDATE");
$ps->execute();
...
//do something if lock successful
...
$mysqli->commit();
In this case, how do I determine if my lock was successful? What is the best way to handle a scenario when the row is locked already?
Sorry if this is described somewhere, but all I seem to find are the 'happy path' explanations out there.
In this case, how do I determine if my lock was successful? What is the best way to handle a scenario when the row is locked already?
If the row you are trying to lock is already locked - the mysql server will not return any response for this row. It will wait², until the locking transaction is either commited or rolled back.
(Obviously: if the row has been deleted already, your SELECT will return an empty result set and not lock anything)
After that, it will return the latest value, commited by the transaction that was holding the lock.
Regular Select Statements will not care about the lock and return the current value, ignoring that there is a uncommited change.
So, in other words: your code will only be executed WHEN the lock is successfull. (Otherwhise waiting² until the prior lock is released)
Note, that using FOR UPDATE will also block any transactional SELECTS for the time beeing locked - If you do not want this, you should use LOCK IN SHARE MODE instead. This would allow transactional selects to proceed with the current value, while just blocking any update or delete statement.
² the query will return an error, after the time defined with innodb_lock_wait_timeout http://dev.mysql.com/doc/refman/5.5/en/innodb-parameters.html#sysvar_innodb_lock_wait_timeout
It then will return ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
In other words: That's the point where your attempt to acquire a lock fails.
Sidenode: This kind of locking is just suitable to ensure data-integrity. (I.e. that no referenced row is deleted while you are inserting something that references this row).
Once the lock is released any blocked (or better call it delayed) delete statement will be executed, maybe deleting the row you just inserted due to Cascading on the row on which you just held the lock to ensure integrity.
If you want to create a system to avoid 2 users modifying the same data at the same time, you should do this at an application level and look at pessimistic vs optimistic locking approches, because it is no good idea to keep transactions running for a long period of time. (I think in PHP your database connections are automatically closed after each request anyway, causing an implicit commit on any running transaction)
My system creates a lot of transactions as it has many users and a lot of data which is checked on a daily basis and renewed.
Somehow at a certain moment (i am not sure if it is the backup which did it) there is a LOCKED on queries. And Somehow they are never returned. Is this the deadlock?
The database is not returning anything to the code either, so I can't check if it's locked or not. Also, this causes other queries to be stopped and pile up and my server runs out of connections...
any idea's on this?
It may be caused by several issues. Most popular is MyISAM table lock. Just run this quesry: SHOW STATUS LIKE 'Table%';. Post it here. If Table_locks_waited is big (e.g. more than 0.5% of Table_locks_immediate) and you are using MyISAM switch to InnoDB table engine.
If your database is not very big, changing engine is pretty fast and transparent.
Note, that all your locked queries are "write" queries. That's because MyISAM has long running selects that lock tables. Moreover, selects can cause some kind of deadlock. Quotation from docs:
MySQL grants table write locks as follows:
If there are no locks on the table, put a write lock on it.
Otherwise, put the lock request in the write lock queue.
MySQL grants table read locks as follows:
If there are no write locks on the table, put a read lock on it.
Otherwise, put the lock request in the read lock queue.
Don't forget to tune innodb_* params!
If you don't want to switch to InnoDB (why?!), you can tune concurrent_insert parameter (try "2") in your my.cnf.
Btw, I see a lot of sleeping connections. Do you have persistent connections? If "yes", do you close them properly?
I have something similar to an online chat client. When one user sends a message it will be stored in a MySQL database table with id=currentID+1. I have another client long polling and waiting for message 'id=currentID+1'.
After this exchange, that row is never used again.
Do I need a lock in this case? The thing I'm worried about is that the reading PHP side will be able to see the row and read its values before the writing PHP side finishes creating the row.
MySQL won't make the row available until it's done reading (it automatically acquires a table lock in the case of MyISAM, or a row lock in the case of INNODB. So no, you should be ok so long as you're only inserting the row (and not later calling updates on it, etc)...
Writes in MySQL are atomic. Other queries cannot "see" the row until it is completely written, assuming you're using a single INSERT statement to do this.
the new row will only be seen by the select query after its inserted.
inserts are atomic
and yes myisam imploys table level locking, while innodb imploys row level locking.
and set of statements in a transaction are considered atmoic, there effect is not visible to any read until the transaction is committed, u can do a select shared to see uncommitted data.
I have a simple setup of a set of writers and a set of readers working with a MySQL ISAM table. The writers are only inserting rows while the readers are only checking for new rows.
OK, so I know that I don't need a lock in this situation, since I'm not modifying existing rows. However my Writers are accessing one more table that does need a lock. I piece of information seems irrelevant except for the following limitation stated in the MySQL documentation:
A session that requires locks must
acquire all the locks that it needs in
a single LOCK TABLES statement. While
the locks thus obtained are held, the
session can access only the locked
tables. For example, in the following
sequence of statements, an error
occurs for the attempt to access t2
because it was not locked in the LOCK
TABLES statement:
So to access the table I want to insert rows into, I NEED to lock it, which is causing me performance problems. Any suggestions of how to get around this?
Typically you lock and unlock immediately around the queries which need locking. The documentation is simply stating that for any set of queries run while you have a lock, all tables involved must be locked. You can unlock as soon as you're done and touch any other tables.
Also consider that InnoDB supports row-level locking, which is often preferable to table-locking for performance since other queries on other rows will not be locked out for reading while you're also writing.