Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Have "select for update" block on nonrexisting rows

Tags:

we have some persistent data in an application, that is queried from a server and then stored in a database so we can keep track of additional information. Because we do not want to query when an object is used in the memory we do an select for update so that other threads that want to get the same data will be blocked.

I am not sure how select for update handles non-existing rows. If the row does not exist and another thread tries to do another select for update on the same row, will this thread be blocked until the other transaction finishes or will it also get an empty result set? If it does only get an empty result set is there any way to make it block as well, for example by inserting the missing row immediately?

EDIT:

Because there was a remark, that we might lock too much, here some more details on the concrete usage in our case. In reduced pseudocode our programm flow looks like this:

d = queue.fetch(); r = SELECT * FROM table WHERE key = d.key() FOR UPDATE; if r.empty() then   r = get_data_from_somewhere_else();  new_r = process_stuff( r );   if Data was present then    update row to new_r else    insert new_r 

This code is run in multiple thread and the data that is fetched from the queue might be concerning the same row in the database (hence the lock). However if multiple threads are using data that needs the same row, then these threads need to be sequentialized (order does not matter). However this sequentialization fails, if the row is not present, because we do not get a lock.

EDIT:

For now I have the following solution, which seems like an ugly hack to me.

select the data for update if zero rows match then   insert some dummy data   // this will block if multiple transactions try to insert   if insertion failed then     // somebody beat us at the race     select the data for update  do processing  if data was changed then    update the old or dummy data else    rollback the whole transaction 

I am neither 100% sure however that this actually solves the problem, nor does this solution seem good style. So if anybody has to offer something more usable this would be great.

like image 212
LiKao Avatar asked Aug 05 '11 11:08

LiKao


People also ask

Does select for update block select?

FOR UPDATE on a non-existent record does not block other transactions.

Does update query locks the row?

Locks are held on index entries, so if a table is not well indexed for an UPDATE query, then many rows will be locked. Note that InnoDB does not create S locks for normal SELECT queries. It uses row versioning, not row level locking for consistent snapshots.

How does select for update work?

The SELECT FOR UPDATE statement is used to order transactions by controlling concurrent access to one or more rows of a table. It works by locking the rows returned by a selection query, such that other transactions trying to access those rows are forced to wait for the transaction that locked the rows to finish.

Does select for update block read Oracle?

A SELECT FOR UPDATE locks and reads the row. If thread A has locked the row and thread B tries to lock it, thread B will block until thread A releases its lock. So if both A and B are doing a SELECT FOR UPDATE , thread B will wait until A is done.


2 Answers

I am not sure how select for update handles non-existing rows.

It doesn't.

The best you can do is to use an advisory lock if you know something unique about the new row. (Use hashtext() if needed, and the table's oid to lock it.)

The next best thing is a table lock.

That being said, your question makes it sound like you're locking way more than you should. Only lock rows when you actually need to, i.e. write operations.

like image 69
Denis de Bernardy Avatar answered Dec 31 '22 23:12

Denis de Bernardy


Example solution (i haven't found better :/)

Thread A:

BEGIN; SELECT pg_advisory_xact_lock(42); -- database semaphore arbitrary ID SELECT * FROM t WHERE id = 1; DELETE FROM t WHERE id = 1; INSERT INTO t (id, value) VALUES (1, 'thread A'); SELECT 1 FROM pg_sleep(10); -- only for race condition simulation COMMIT; 

Thread B:

BEGIN; SELECT pg_advisory_xact_lock(42); -- database semaphore arbitrary ID SELECT * FROM t WHERE id = 1; DELETE FROM t WHERE id = 1; INSERT INTO t (id, value) VALUES (1, 'thread B'); SELECT 1 FROM pg_sleep(10); -- only for race condition simulation COMMIT; 

Causes always correct order of transactions execution.

like image 21
lukyer Avatar answered Dec 31 '22 23:12

lukyer