You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
JDBCStorage.write() replays a rolled-back transaction, and bounds the replays by MAX_RETRIES and by a 10 s
wall-clock window. Neither bounds an attempt, and an attempt is not short: the lock wait that precedes a
conflict is charged to the attempt that hit it, and is unbounded on three of the four engines here.
Dialect.lockTimeoutSql already carries the statement for all four engines, but it is only ever issued on the
stamp session — never on a transaction connection:
So SQL Server, PostgreSQL and Oracle wait indefinitely for a row lock inside an attempt, and only MySQL bounds
it — with innodb_lock_wait_timeout, 50 s by default.
Why it matters
Everything the retry loop had to grow to work around #903 exists only because the attempt is unbounded:
the Conflict classification (PROMPT vs AFTER_LOCK_WAIT), and the per-hop most-specific walk that keeps it;
MYSQL_LOCK_WAIT_TIMEOUT, read driver-keyed only to tell a late conflict from a prompt one;
grantedPastTheWindow — the first replay of a prompt conflict, taken although the window is already spent,
because no window survives a wait nothing bounds.
Bound the wait inside the attempt and every one of them goes away: each attempt becomes shorter than the window, RETRY_WINDOW_NANOS governs both classes on its own, and the grant, the enum and the vendor number can be
deleted. The diff gets smaller, not bigger.
It also settles the two shapes the current design still leaves open, both raised in review of #904:
The grant is keyed to attempt==1, which is a proxy. The invariant behind it — "no clock can bound a wait
that nothing else bounds" — holds on attempt 2 as well. So the JDBC backend: the give-up window of the transaction replay disarms it for the deadlock it was written for #903 shape survives one attempt later: a
SQL Server write whose attempt 1 conflicts promptly at 2 s (granted) and whose attempt 2 waits 12 s on a row
lock before being picked as the victim is refused at 14 s < 10 s, by a clock a lock wait spent. Widening the
grant to every attempt is the wrong answer — it leaves MAX_RETRIES as the only real cap.
A MySQL lock wait timeout provably never replays at the stock innodb_lock_wait_timeout=50: it is
reported at ~50 s, past the window on the first check, and gets no grant. Master behaved the same way, and
one bounded wait does beat two — but it means MySQL deployments get no write retry for the conflict class
they see most. With a lock bound on the attempt, a tuned-down timeout stops being the exception.
What it needs
Not a one-line change:
SQL Server converts the bounded wait into error 1222, "Lock request time out period exceeded", SQLState HY000. isConflict does not match it today, so issuing SET LOCK_TIMEOUT without classifying 1222 in the
same change turns a retryable conflict into a hard failure.
PostgreSQL reports lock_timeout as 55P03 (lock_not_available), also unmatched today.
The bound has to be a configuration property, not a constant: it changes the failure every deployment sees.
Each engine needs container coverage of the new failure, since none of it is reachable without a real lock.
Related
Follow-up to #904 (#903), requested in review: #904 (review)
The last section of #877 covers the same statement from the query-timeout side.
Describe the bug
JDBCStorage.write()replays a rolled-back transaction, and bounds the replays byMAX_RETRIESand by a 10 swall-clock window. Neither bounds an attempt, and an attempt is not short: the lock wait that precedes a
conflict is charged to the attempt that hit it, and is unbounded on three of the four engines here.
Dialect.lockTimeoutSqlalready carries the statement for all four engines, but it is only ever issued on thestamp session — never on a transaction connection:
So SQL Server, PostgreSQL and Oracle wait indefinitely for a row lock inside an attempt, and only MySQL bounds
it — with
innodb_lock_wait_timeout, 50 s by default.Why it matters
Everything the retry loop had to grow to work around #903 exists only because the attempt is unbounded:
Conflictclassification (PROMPTvsAFTER_LOCK_WAIT), and the per-hop most-specific walk that keeps it;MYSQL_LOCK_WAIT_TIMEOUT, read driver-keyed only to tell a late conflict from a prompt one;grantedPastTheWindow— the first replay of a prompt conflict, taken although the window is already spent,because no window survives a wait nothing bounds.
Bound the wait inside the attempt and every one of them goes away: each attempt becomes shorter than the window,
RETRY_WINDOW_NANOSgoverns both classes on its own, and the grant, the enum and the vendor number can bedeleted. The diff gets smaller, not bigger.
It also settles the two shapes the current design still leaves open, both raised in review of #904:
attempt==1, which is a proxy. The invariant behind it — "no clock can bound a waitthat nothing else bounds" — holds on attempt 2 as well. So the JDBC backend: the give-up window of the transaction replay disarms it for the deadlock it was written for #903 shape survives one attempt later: a
SQL Server write whose attempt 1 conflicts promptly at 2 s (granted) and whose attempt 2 waits 12 s on a row
lock before being picked as the victim is refused at
14 s < 10 s, by a clock a lock wait spent. Widening thegrant to every attempt is the wrong answer — it leaves
MAX_RETRIESas the only real cap.innodb_lock_wait_timeout=50: it isreported at ~50 s, past the window on the first check, and gets no grant. Master behaved the same way, and
one bounded wait does beat two — but it means MySQL deployments get no write retry for the conflict class
they see most. With a lock bound on the attempt, a tuned-down timeout stops being the exception.
What it needs
Not a one-line change:
HY000.isConflictdoes not match it today, so issuingSET LOCK_TIMEOUTwithout classifying 1222 in thesame change turns a retryable conflict into a hard failure.
lock_timeoutas55P03(lock_not_available), also unmatched today.Related
Follow-up to #904 (#903), requested in review:
#904 (review)
The last section of #877 covers the same statement from the query-timeout side.