SQLite User Forum

Write locking and subqueries
Login

Write locking and subqueries

(1) By anonymous on 2022-03-22 12:46:59 [source]

Hi, I have a question regarding locking and concurrency:


If I perform an UPDATE with a nested SELECT, will the write lock for the UPDATE be acquired before or after the SELECT statement has been executed?

Example: 

UPDATE Info SET Timestamp = 1234 WHERE rowid IN (SELECT rowid FROM Info WHERE Timestamp=NULL LIMIT 10);

Thanks in advance!

(2) By Richard Hipp (drh) on 2022-03-22 13:51:39 in reply to 1 [link] [source]

That depends on what you mean by "the write lock for the UPDATE".

Could you perhaps explain better what you are trying to do and why you are worried about "write locks"?

(3) By anonymous on 2022-03-22 13:57:23 in reply to 2 [link] [source]

I am worried that a connection on another thread, trying to perform the same query would result in a race condition, where the two threads updated the same rows. Could this happen, or does the UPDATE prevent other threads from selecting the same rows?

(4) By Larry Brasfield (larrybr) on 2022-03-22 14:16:35 in reply to 3 [link] [source]

The statement will be executed within a transaction, either one you explicitly create or one that is automatically created when no explicit transaction is created. Your worry, while prudent on general principles, is not warranted with respect to SQLite's operation.

Short answer(s): No. Yes.

(5) By Richard Hipp (drh) on 2022-03-22 14:20:38 in reply to 3 [link] [source]

The race condition that you are worried about cannot happen. The reads and writes for a single operation all happen within the same transaction. SQLite uses SERIALIZABLE isolation, so there are no "dirty reads".

There is a race in the sense that two or more threads might try to update the same row at the same time. But if that happens, all but one of those threads will fail to commit and only a single update will end up being written into the database.

(6) By Rowan Worth (sqweek) on 2022-03-22 14:43:01 in reply to 1 [link] [source]

The RESERVED write lock is obtained when a database page is changed. I'm not sure of the exact sequence of events, but it's not important because both the UPDATE and SELECT happen in the context of the same transaction (which will be an implicit transaction if you haven't already started one).

Changes made within a transaction are not visible outside the connection making the change, and connection handles should not be shared between threads. I think the scenario you're worried about is this:

T1: executes SELECT and grabs first 10 rows without timestamps
T2: executes SELECT and grabs same 10 rows
T1: obtains write lock
T2: waits for write lock
T1: fills out Timestamp in the 10 rows
T1: commits transaction and releases write lock
T2: obtains write lock
T2: overwrites Timestamp in the same 10 rows
T2: commits transaction and releases write lock

However this cannot actually happen because:

  1. sqlite supports only one concurrent write transaction, and thus waiting for the write lock when another thread already has it makes no sense -- the second thread must roll back/restart their transaction
  2. sqlite cannot safely update the database file while any read transactions are in progress, because the file may be temporarily inconsistent while updated pages are being committed and any readers exposed to that would encounter unpredictable results. So the writer must wait for read transactions to finish before committing

ie. after losing the race to the RESERVED write lock, thread 2 would have to abort its current transaction to allow thread 1 to COMMIT, and upon restarting the transaction it would execute a new SELECT. At least this is how it works in rollback journal mode. WAL mode influences (2) but I'm not sure it changes the story in any meaningful way.

(7) By anonymous on 2022-03-22 14:58:50 in reply to 6 [link] [source]

Thanks for all your replies, big help!