question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Support SELECT FOR UPDATE / UPDLOCK (pessimistic concurrency)

See original GitHub issue

I want to garantee that only one Api Request (Database Transaction) can modify an entity at a given time. this could be done by selecting a row with “SELECT FOR UPDATE” inside a transaction.

Something like:

context.Books
                        .Where(b => b.Id == 1)
                        .SelectForUpdate(skipLocked: true)
                        .FirstOrDefault();

Would generate something like:

                SELECT *
                FROM books b
                WHERE b.id = 1
                FOR UPDATE SKIP LOCKED
                LIMIT 1";

I think that EF itself does not need to track at any level that the entity was selected with a table hint, it only need to be able to express the SQL.

Issue Analytics

  • State:open
  • Created 2 years ago
  • Reactions:26
  • Comments:6 (4 by maintainers)

github_iconTop GitHub Comments

23reactions
rojicommented, Dec 14, 2021

Cross-database investigation on SELECT FOR UPDATE

The FOR UPDATE clause makes the database lock rows which are selected by the query; other transactions which query a locked row with FOR UPDATE (or UPDATE it) will wait until the first transaction ends.

Comparison to optimistic concurrency:

  • This removes the need to handle optimistic concurrency exceptions and retry, simplifying user code significantly.
  • No need for a concurrency token, and no need to select it back when updating.
  • If two transactions select rows in reverse order, a deadlock occurs, causing an error. This is similar to the deadlock that can happen with UPDATE (docs). This can be mitigated by using the same ordering, or if not feasible, catching the deadlock exception and retrying.
  • If transactions are long (not recommended), then competing transactions may wait for a long time. With optimistic concurrency this does not occur.
  • In disconnected scenarios, selection and update typically cannot occur in the same transaction. Unless the database row is selected a second time just before update, SELECT FOR UPDATE isn’t relevant.

PostgreSQL

Documentation

DROP TABLE IF EXISTS foo;
CREATE TABLE foo (bar INT);
INSERT INTO foo VALUES (1), (2), (3), (4), (5);
CREATE TABLE foo2 (bar INT);
INSERT INTO foo2 VALUES (1), (2), (3), (4), (5);

-- Simple, top-level scenario
SELECT * FROM foo FOR UPDATE;

-- On a specific table with JOIN:
SELECT * FROM foo
JOIN foo2 ON foo2.bar = foo.bar FOR UPDATE;

-- Inside a subquery:
SELECT * FROM (
    SELECT * FROM foo FOR UPDATE
) x;
  • Instead of blocking on locked rows, you can specify NOWAIT (to get an error) or SKIP LOCKED (to skip locked rows altogether).
  • Weaker lock strengths are available, e.g. SELECT ... FOR SHARE which blocks updates but allows other FOR SHARE locks (read-write lock).

SQL Server

No SELECT FOR UPDATE syntax - the UPDLOCK table hint is used instead (documentation);

CREATE TABLE foo (bar INT);
INSERT INTO foo VALUES (1), (2), (3), (4), (5);
CREATE TABLE foo2 (bar INT);
INSERT INTO foo2 VALUES (1), (2), (3), (4), (5);

-- Simple, top-level scenario
SELECT * FROM foo WITH (UPDLOCK);

-- On a specific table with JOIN:
SELECT * FROM foo
JOIN foo2 WITH (UPDLOCK) ON foo2.bar = foo.bar;

-- Inside a subquery:
SELECT * FROM (
    SELECT * FROM foo (UPDLOCK)
) x;

MariaDB

MariaDB documentation MySQL documentation

SQL: identical to PostgreSQL above

  • Also supports IN SHARE MODE for read/write locking, like PostgreSQL FOR SHARE.

SQLite

Not supported (no concurrency/row locking)

Oracle

Documentation

Firebird

Documentation (some interaction with WITH LOCK feature)


Based on the above, we could consider doing the following:

  • Building on table annotations, we could introduce a relational-level ForUpdate().
  • This would be generated as FOR UPDATE by default, with SQL Server overriding to generate UPDLOCK instead.
  • For Include, the metadata extension could be specified inside the Include (i.e. context.Blogs.Include(b => b.Posts).ForUpdate()). This may require additional work in #26858.
  • PostgreSQL, MariaDB would have an additional ForShare() and other operators.

/cc @maumar

4reactions
rojicommented, Apr 24, 2023

Another use-case: atomically update something with ExecuteUpdate. This can be done today with optimistic locking, but that requires retrying when the update fails; that means some sort of random delay + backoff strategy is needed, where pessimistic locking can solve this much more easily.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Managing concurrency when using SELECT-UPDATE ...
I'm pretty sure that adding a WITH (UPDLOCK, HOLDLOCK) to the SELECT will do the trick. The SERIALIZABLE transaction isolation level would seems ......
Read more >
c# - Pessimistic locking in EF code first
The answer is: it is not supported yet in Entity Framework. ... This lock is compatible with SELECT but not with UPDATE or...
Read more >
SELECT FOR UPDATE - 4Js
Microsoft™ SQL SERVER allows individual and exclusive row locking by using the (UPDLOCK) hint after the table names in the FROM clause: SELECT...
Read more >
Need help: Locking hint (UPDLOCK)
If I use the locking hint UPDLOCK in the SELECT statement within a ... values and then hits the "update" key, then that...
Read more >
Transaction locking and row versioning guide - SQL Server
Lost updates occur when two or more transactions select the same row and then update the row based on the value originally selected....
Read more >

github_iconTop Related Medium Post

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found