Two transactions that touch the same document do not queue: the second fails at once with WriteConflict, code 112, labeled TransientTransactionError. One that runs past transactionLifetimeLimitSeconds is aborted by the server, and its next operation fails with NoSuchTransaction, code 251, also transient.
s1.startTransaction(); s2.startTransaction(); // two clients, same document
await ca.updateOne({ _id: 'A' }, { $inc: { bal: 1 } }, { session: s1 });
try { await cb.updateOne({ _id: 'A' }, { $inc: { bal: 2 } }, { session: s2 }); }
catch (e) { console.log(e.code, e.codeName, JSON.stringify(e.errorLabels),
e.hasErrorLabel('TransientTransactionError')); }
await admin.command({ setParameter: 1, transactionLifetimeLimitSeconds: 3 });
const s = a.startSession(); s.startTransaction();
await c.insertOne({ step: 1 }, { session: s });
await new Promise(r => setTimeout(r, 6000)); // longer than the limit
try { await c.insertOne({ step: 2 }, { session: s }); }
catch (e) { console.log(e.code, e.codeName, e.message.slice(0, 44)); }
console.log('docs left:', await c.countDocuments());112 WriteConflict ["TransientTransactionError"] true
251 NoSuchTransaction Transaction with { txnNumber: 1 } has b
docs left: 0The expired transaction left nothing behind. Both errors are transient, so the answer is to retry the whole transaction, which withTransaction does: it retries on TransientTransactionError and retries the commit on UnknownTransactionCommitResult, the label hand-rolled loops miss. Your part is an idempotent callback.
| Limit | Default and parameter |
|---|---|
| Transaction runtime | 60 s, transactionLifetimeLimitSeconds |
| Lock acquisition wait | 5 ms, maxTransactionLockRequestTimeoutMillis |
| Size of one oplog entry | 16 MB, fixed by the BSON limit |
| Total transaction size | WiredTiger cache, TransactionTooLargeForCache |
Since MongoDB 4.2 a transaction writes one oplog entry per operation rather than one for everything, so the old 16 MB ceiling on a whole transaction is gone; each entry still must fit in 16 MB. The real ceiling is the WiredTiger cache, which holds every uncommitted change until commit. The cost is measurable: 2,000 documents inserted one per transaction took 30,178 ms against 939 ms as plain inserts, and 786 ms inside one transaction. The commit dominates, so batch related work together and never wrap a single write in one.