1
0
mirror of https://github.com/postgres/postgres.git synced 2025-07-28 23:42:10 +03:00

Implement SKIP LOCKED for row-level locks

This clause changes the behavior of SELECT locking clauses in the
presence of locked rows: instead of causing a process to block waiting
for the locks held by other processes (or raise an error, with NOWAIT),
SKIP LOCKED makes the new reader skip over such rows.  While this is not
appropriate behavior for general purposes, there are some cases in which
it is useful, such as queue-like tables.

Catalog version bumped because this patch changes the representation of
stored rules.

Reviewed by Craig Ringer (based on a previous attempt at an
implementation by Simon Riggs, who also provided input on the syntax
used in the current patch), David Rowley, and Álvaro Herrera.

Author: Thomas Munro
This commit is contained in:
Alvaro Herrera
2014-10-07 17:23:34 -03:00
parent c421efd213
commit df630b0dd5
38 changed files with 907 additions and 121 deletions

View File

@ -0,0 +1,49 @@
Parsed test spec with 2 sessions
starting permutation: s1a s2a s2b s1b s2c
step s1a: SELECT * FROM queue ORDER BY id FOR SHARE SKIP LOCKED LIMIT 1;
id data status
1 foo NEW
step s2a: SELECT * FROM queue ORDER BY id FOR SHARE SKIP LOCKED LIMIT 1;
id data status
1 foo NEW
step s2b: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
id data status
2 bar NEW
step s1b: COMMIT;
step s2c: COMMIT;
starting permutation: s2a s1a s2b s1b s2c
step s2a: SELECT * FROM queue ORDER BY id FOR SHARE SKIP LOCKED LIMIT 1;
id data status
1 foo NEW
step s1a: SELECT * FROM queue ORDER BY id FOR SHARE SKIP LOCKED LIMIT 1;
id data status
1 foo NEW
step s2b: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
id data status
2 bar NEW
step s1b: COMMIT;
step s2c: COMMIT;
starting permutation: s2a s2b s1a s1b s2c
step s2a: SELECT * FROM queue ORDER BY id FOR SHARE SKIP LOCKED LIMIT 1;
id data status
1 foo NEW
step s2b: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
id data status
1 foo NEW
step s1a: SELECT * FROM queue ORDER BY id FOR SHARE SKIP LOCKED LIMIT 1;
id data status
2 bar NEW
step s1b: COMMIT;
step s2c: COMMIT;

View File

@ -0,0 +1,19 @@
Parsed test spec with 3 sessions
starting permutation: s1a s2a s3a s1b s2b s3b
step s1a: SELECT * FROM queue ORDER BY id FOR UPDATE LIMIT 1;
id data status
1 foo NEW
step s2a: SELECT * FROM queue ORDER BY id FOR UPDATE LIMIT 1; <waiting ...>
step s3a: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
id data status
2 bar NEW
step s1b: COMMIT;
step s2a: <... completed>
id data status
1 foo NEW
step s2b: COMMIT;
step s3b: COMMIT;

View File

@ -0,0 +1,21 @@
Parsed test spec with 2 sessions
starting permutation: s2a s1a s2b s2c s2d s2e s1b s2f
step s2a: SELECT pg_advisory_lock(0);
pg_advisory_lock
step s1a: SELECT * FROM foo WHERE pg_advisory_lock(0) IS NOT NULL ORDER BY id LIMIT 1 FOR UPDATE SKIP LOCKED; <waiting ...>
step s2b: UPDATE foo SET data = data WHERE id = 1;
step s2c: BEGIN;
step s2d: UPDATE foo SET data = data WHERE id = 1;
step s2e: SELECT pg_advisory_unlock(0);
pg_advisory_unlock
t
step s1a: <... completed>
id data
2 x
step s1b: COMMIT;
step s2f: COMMIT;

View File

@ -0,0 +1,19 @@
Parsed test spec with 2 sessions
starting permutation: s2a s1a s2b s2c s2d s2e s1b s2f
step s2a: SELECT pg_advisory_lock(0);
pg_advisory_lock
step s1a: SELECT * FROM foo WHERE pg_advisory_lock(0) IS NOT NULL ORDER BY id LIMIT 1 FOR UPDATE SKIP LOCKED; <waiting ...>
step s2b: UPDATE foo SET data = data WHERE id = 1;
step s2c: BEGIN;
step s2d: UPDATE foo SET data = data WHERE id = 1;
step s2e: SELECT pg_advisory_unlock(0);
pg_advisory_unlock
t
step s1a: <... completed>
error in steps s2e s1a: ERROR: could not serialize access due to concurrent update
step s1b: COMMIT;
step s2f: COMMIT;

View File

@ -0,0 +1,401 @@
Parsed test spec with 2 sessions
starting permutation: s1a s1b s1c s2a s2b s2c
step s1a: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
id data status
1 foo NEW
step s1b: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
id data status
1 foo NEW
step s1c: COMMIT;
step s2a: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
id data status
1 foo NEW
step s2b: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
id data status
1 foo NEW
step s2c: COMMIT;
starting permutation: s1a s1b s2a s1c s2b s2c
step s1a: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
id data status
1 foo NEW
step s1b: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
id data status
1 foo NEW
step s2a: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
id data status
2 bar NEW
step s1c: COMMIT;
step s2b: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
id data status
1 foo NEW
step s2c: COMMIT;
starting permutation: s1a s1b s2a s2b s1c s2c
step s1a: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
id data status
1 foo NEW
step s1b: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
id data status
1 foo NEW
step s2a: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
id data status
2 bar NEW
step s2b: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
id data status
2 bar NEW
step s1c: COMMIT;
step s2c: COMMIT;
starting permutation: s1a s1b s2a s2b s2c s1c
step s1a: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
id data status
1 foo NEW
step s1b: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
id data status
1 foo NEW
step s2a: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
id data status
2 bar NEW
step s2b: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
id data status
2 bar NEW
step s2c: COMMIT;
step s1c: COMMIT;
starting permutation: s1a s2a s1b s1c s2b s2c
step s1a: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
id data status
1 foo NEW
step s2a: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
id data status
2 bar NEW
step s1b: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
id data status
1 foo NEW
step s1c: COMMIT;
step s2b: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
id data status
1 foo NEW
step s2c: COMMIT;
starting permutation: s1a s2a s1b s2b s1c s2c
step s1a: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
id data status
1 foo NEW
step s2a: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
id data status
2 bar NEW
step s1b: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
id data status
1 foo NEW
step s2b: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
id data status
2 bar NEW
step s1c: COMMIT;
step s2c: COMMIT;
starting permutation: s1a s2a s1b s2b s2c s1c
step s1a: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
id data status
1 foo NEW
step s2a: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
id data status
2 bar NEW
step s1b: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
id data status
1 foo NEW
step s2b: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
id data status
2 bar NEW
step s2c: COMMIT;
step s1c: COMMIT;
starting permutation: s1a s2a s2b s1b s1c s2c
step s1a: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
id data status
1 foo NEW
step s2a: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
id data status
2 bar NEW
step s2b: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
id data status
2 bar NEW
step s1b: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
id data status
1 foo NEW
step s1c: COMMIT;
step s2c: COMMIT;
starting permutation: s1a s2a s2b s1b s2c s1c
step s1a: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
id data status
1 foo NEW
step s2a: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
id data status
2 bar NEW
step s2b: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
id data status
2 bar NEW
step s1b: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
id data status
1 foo NEW
step s2c: COMMIT;
step s1c: COMMIT;
starting permutation: s1a s2a s2b s2c s1b s1c
step s1a: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
id data status
1 foo NEW
step s2a: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
id data status
2 bar NEW
step s2b: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
id data status
2 bar NEW
step s2c: COMMIT;
step s1b: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
id data status
1 foo NEW
step s1c: COMMIT;
starting permutation: s2a s1a s1b s1c s2b s2c
step s2a: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
id data status
1 foo NEW
step s1a: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
id data status
2 bar NEW
step s1b: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
id data status
2 bar NEW
step s1c: COMMIT;
step s2b: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
id data status
1 foo NEW
step s2c: COMMIT;
starting permutation: s2a s1a s1b s2b s1c s2c
step s2a: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
id data status
1 foo NEW
step s1a: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
id data status
2 bar NEW
step s1b: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
id data status
2 bar NEW
step s2b: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
id data status
1 foo NEW
step s1c: COMMIT;
step s2c: COMMIT;
starting permutation: s2a s1a s1b s2b s2c s1c
step s2a: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
id data status
1 foo NEW
step s1a: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
id data status
2 bar NEW
step s1b: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
id data status
2 bar NEW
step s2b: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
id data status
1 foo NEW
step s2c: COMMIT;
step s1c: COMMIT;
starting permutation: s2a s1a s2b s1b s1c s2c
step s2a: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
id data status
1 foo NEW
step s1a: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
id data status
2 bar NEW
step s2b: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
id data status
1 foo NEW
step s1b: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
id data status
2 bar NEW
step s1c: COMMIT;
step s2c: COMMIT;
starting permutation: s2a s1a s2b s1b s2c s1c
step s2a: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
id data status
1 foo NEW
step s1a: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
id data status
2 bar NEW
step s2b: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
id data status
1 foo NEW
step s1b: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
id data status
2 bar NEW
step s2c: COMMIT;
step s1c: COMMIT;
starting permutation: s2a s1a s2b s2c s1b s1c
step s2a: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
id data status
1 foo NEW
step s1a: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
id data status
2 bar NEW
step s2b: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
id data status
1 foo NEW
step s2c: COMMIT;
step s1b: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
id data status
1 foo NEW
step s1c: COMMIT;
starting permutation: s2a s2b s1a s1b s1c s2c
step s2a: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
id data status
1 foo NEW
step s2b: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
id data status
1 foo NEW
step s1a: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
id data status
2 bar NEW
step s1b: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
id data status
2 bar NEW
step s1c: COMMIT;
step s2c: COMMIT;
starting permutation: s2a s2b s1a s1b s2c s1c
step s2a: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
id data status
1 foo NEW
step s2b: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
id data status
1 foo NEW
step s1a: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
id data status
2 bar NEW
step s1b: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
id data status
2 bar NEW
step s2c: COMMIT;
step s1c: COMMIT;
starting permutation: s2a s2b s1a s2c s1b s1c
step s2a: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
id data status
1 foo NEW
step s2b: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
id data status
1 foo NEW
step s1a: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
id data status
2 bar NEW
step s2c: COMMIT;
step s1b: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
id data status
1 foo NEW
step s1c: COMMIT;
starting permutation: s2a s2b s2c s1a s1b s1c
step s2a: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
id data status
1 foo NEW
step s2b: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
id data status
1 foo NEW
step s2c: COMMIT;
step s1a: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
id data status
1 foo NEW
step s1b: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
id data status
1 foo NEW
step s1c: COMMIT;

View File

@ -27,6 +27,10 @@ test: nowait-2
test: nowait-3
test: nowait-4
test: nowait-5
test: skip-locked
test: skip-locked-2
test: skip-locked-3
test: skip-locked-4
test: drop-index-concurrently-1
test: alter-table-1
test: timeouts

View File

@ -27,4 +27,9 @@ step "s2d" { UPDATE foo SET data = data; }
step "s2e" { SELECT pg_advisory_unlock(0); }
step "s2f" { COMMIT; }
# s1 takes a snapshot but then waits on an advisory lock, then s2
# updates the row in one transaction, then again in another without
# committing, before allowing s1 to proceed to try to lock a row;
# because it has a snapshot that sees the older version, we reach the
# waiting code in EvalPlanQualFetch which ereports when in NOWAIT mode.
permutation "s2a" "s1a" "s2b" "s2c" "s2d" "s2e" "s1b" "s2f"

View File

@ -0,0 +1,41 @@
# Test SKIP LOCKED with multixact locks.
setup
{
CREATE TABLE queue (
id int PRIMARY KEY,
data text NOT NULL,
status text NOT NULL
);
INSERT INTO queue VALUES (1, 'foo', 'NEW'), (2, 'bar', 'NEW');
}
teardown
{
DROP TABLE queue;
}
session "s1"
setup { BEGIN; }
step "s1a" { SELECT * FROM queue ORDER BY id FOR SHARE SKIP LOCKED LIMIT 1; }
step "s1b" { COMMIT; }
session "s2"
setup { BEGIN; }
step "s2a" { SELECT * FROM queue ORDER BY id FOR SHARE SKIP LOCKED LIMIT 1; }
step "s2b" { SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1; }
step "s2c" { COMMIT; }
# s1 and s2 both get SHARE lock, creating a multixact lock, then s2
# tries to update to UPDATE but skips the record because it can't
# acquire a multixact lock
permutation "s1a" "s2a" "s2b" "s1b" "s2c"
# the same but with the SHARE locks acquired in a different order, so
# s2 again skips because it can't acquired a multixact lock
permutation "s2a" "s1a" "s2b" "s1b" "s2c"
# s2 acquires SHARE then UPDATE, then s1 tries to acquire SHARE but
# can't so skips the first record because it can't acquire a regular
# lock
permutation "s2a" "s2b" "s1a" "s1b" "s2c"

View File

@ -0,0 +1,36 @@
# Test SKIP LOCKED with tuple locks.
setup
{
CREATE TABLE queue (
id int PRIMARY KEY,
data text NOT NULL,
status text NOT NULL
);
INSERT INTO queue VALUES (1, 'foo', 'NEW'), (2, 'bar', 'NEW');
}
teardown
{
DROP TABLE queue;
}
session "s1"
setup { BEGIN; }
step "s1a" { SELECT * FROM queue ORDER BY id FOR UPDATE LIMIT 1; }
step "s1b" { COMMIT; }
session "s2"
setup { BEGIN; }
step "s2a" { SELECT * FROM queue ORDER BY id FOR UPDATE LIMIT 1; }
step "s2b" { COMMIT; }
session "s3"
setup { BEGIN; }
step "s3a" { SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1; }
step "s3b" { COMMIT; }
# s3 skips to the second record because it can't obtain the tuple lock
# (s2 holds the tuple lock because it is next in line to obtain the
# row lock, and s1 holds the row lock)
permutation "s1a" "s2a" "s3a" "s1b" "s2b" "s3b"

View File

@ -0,0 +1,36 @@
# Test SKIP LOCKED with an updated tuple chain.
setup
{
CREATE TABLE foo (
id int PRIMARY KEY,
data text NOT NULL
);
INSERT INTO foo VALUES (1, 'x'), (2, 'x');
}
teardown
{
DROP TABLE foo;
}
session "s1"
setup { BEGIN; }
step "s1a" { SELECT * FROM foo WHERE pg_advisory_lock(0) IS NOT NULL ORDER BY id LIMIT 1 FOR UPDATE SKIP LOCKED; }
step "s1b" { COMMIT; }
session "s2"
step "s2a" { SELECT pg_advisory_lock(0); }
step "s2b" { UPDATE foo SET data = data WHERE id = 1; }
step "s2c" { BEGIN; }
step "s2d" { UPDATE foo SET data = data WHERE id = 1; }
step "s2e" { SELECT pg_advisory_unlock(0); }
step "s2f" { COMMIT; }
# s1 takes a snapshot but then waits on an advisory lock, then s2
# updates the row in one transaction, then again in another without
# committing, before allowing s1 to proceed to try to lock a row;
# because it has a snapshot that sees the older version, we reach the
# waiting code in EvalPlanQualFetch which skips rows when in SKIP
# LOCKED mode, so s1 sees the second row
permutation "s2a" "s1a" "s2b" "s2c" "s2d" "s2e" "s1b" "s2f"

View File

@ -0,0 +1,28 @@
# Test SKIP LOCKED when regular row locks can't be acquired.
setup
{
CREATE TABLE queue (
id int PRIMARY KEY,
data text NOT NULL,
status text NOT NULL
);
INSERT INTO queue VALUES (1, 'foo', 'NEW'), (2, 'bar', 'NEW');
}
teardown
{
DROP TABLE queue;
}
session "s1"
setup { BEGIN; }
step "s1a" { SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1; }
step "s1b" { SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1; }
step "s1c" { COMMIT; }
session "s2"
setup { BEGIN; }
step "s2a" { SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1; }
step "s2b" { SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1; }
step "s2c" { COMMIT; }