Skip to content

[#747] fix flaky MS SQL MERGE upsert PRIMARY KEY violation with WITH (HOLDLOCK)#750

Merged
vharseko merged 1 commit into
OpenIdentityPlatform:masterfrom
vharseko:issue-747-mssql-merge-holdlock
Jul 18, 2026
Merged

[#747] fix flaky MS SQL MERGE upsert PRIMARY KEY violation with WITH (HOLDLOCK)#750
vharseko merged 1 commit into
OpenIdentityPlatform:masterfrom
vharseko:issue-747-mssql-merge-holdlock

Conversation

@vharseko

@vharseko vharseko commented Jul 18, 2026

Copy link
Copy Markdown
Member

Summary

Fixes #747.

The JDBC backend implements the SQL Server upsert via MERGE ... WHEN NOT MATCHED THEN INSERT (Storage.java). Under READ_COMMITTED (the isolation level connections use), SQL Server's MERGE is not atomic: two concurrent sessions can both evaluate NOT MATCHED for the same key and both attempt the INSERT, producing a duplicate PRIMARY KEY error. This is documented SQL Server behavior.

This is the race that intermittently fails PluggableBackendImplTestCase.test_issue_496_2, which drives 8 threads doing txn.update() on the same key. The failure window is the initial insert (before the row exists), which is why it is timing-dependent and flaky.

Fix

Add the WITH (HOLDLOCK) (SERIALIZABLE range-lock) table hint to the MERGE target:

merge into <table> WITH (HOLDLOCK) old using (...) ...

HOLDLOCK takes a key-range lock via the unique index on h (the MS SQL dialect's PRIMARY KEY is on h), making the existence check and the insert atomic with respect to other sessions and serializing concurrent upserts of the same key. This eliminates the duplicate-key race. Postgres (ON CONFLICT), MySQL (ON DUPLICATE KEY), Oracle and ANSI dialects are unaffected.

… KEY violation with WITH (HOLDLOCK)

The JDBC backend performs the SQL Server upsert via MERGE ... WHEN NOT MATCHED
THEN INSERT. Under READ_COMMITTED, SQL Server's MERGE is not atomic: two
concurrent sessions can both evaluate NOT MATCHED for the same key and both
attempt the INSERT, producing a duplicate PRIMARY KEY error. This is the race
that intermittently fails PluggableBackendImplTestCase.test_issue_496_2 (8
threads upserting the same key).

Add the WITH (HOLDLOCK) (SERIALIZABLE range-lock) table hint to the MERGE
target so the existence check and the insert are atomic with respect to other
sessions, serializing concurrent upserts of the same key.
@vharseko
vharseko requested a review from maximthomas July 18, 2026 06:27
@vharseko vharseko added bug concurrency Thread-safety / race-condition bugs jdbc tests Test suites: fixing, enabling, un-disabling java Pull requests that update java code labels Jul 18, 2026
@vharseko vharseko changed the title Issue #747: fix flaky MS SQL MERGE upsert PRIMARY KEY violation with WITH (HOLDLOCK) [#747] fix flaky MS SQL MERGE upsert PRIMARY KEY violation with WITH (HOLDLOCK) Jul 18, 2026
@vharseko
vharseko merged commit b297c2f into OpenIdentityPlatform:master Jul 18, 2026
17 checks passed
@vharseko
vharseko deleted the issue-747-mssql-merge-holdlock branch July 18, 2026 16:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug concurrency Thread-safety / race-condition bugs java Pull requests that update java code jdbc tests Test suites: fixing, enabling, un-disabling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Flaky test: test_issue_496_2 fails on MS SQL with PRIMARY KEY violation (non-atomic MERGE upsert)

2 participants