Skip to content

mysql2/drizzle: a transaction does not pin its pooled connection — statements scatter, breaking atomicity #9330

Description

@proggeramlug

Summary

A write transaction on a pooled mysql2 connection commits its data but the
transaction is never ended: it stays RUNNING in information_schema.innodb_trx
holding row locks, and the connection returns to the pool still inside it.
Subsequent writes block on those locks and fail with Query timed out; the
service degrades to unusable within a handful of operations.

Perry 0.5.1519 from source, plus #9314 and the fix in #9319. drizzle-orm over
mysql2 pools, MySQL 8.0.46 on the same host. Node 26.8.1 on identical code
does not leak.

What it looks like in a real service

A registration endpoint (db.transaction() → insert org, user, membership,
roles, consent) returns 201 and the rows persist correctly. But afterwards:

mysql> SELECT trx_id, trx_state, TIMESTAMPDIFF(SECOND, trx_started, NOW()) AS age_s, trx_rows_locked
       FROM information_schema.innodb_trx;
+--------+-----------+-------+------------------+
| 182982 | RUNNING   |    11 |                3 |
| 182991 | LOCK WAIT |    11 |                1 |
+--------+-----------+-------+------------------+

— one transaction left open holding three row locks, and the next one already
waiting on it. The owning connection shows as Sleep in the processlist, so
nothing is executing; the transaction is simply never committed or rolled back
from the server's point of view.

After a few requests the pool is full of such connections and every write dies
with:

insert into `orgMemberships` (…) values (default, ?, ?, ?, ?)
params: [ 13, 19, '2026-08-31 22:05:40.269', '2026-08-31 22:05:40.269' ]
cause: Error: Query timed out

It is not request-driven. Ten seconds after a fresh start, with zero
traffic, the process already shows 2 open transactions and its scheduler loop
has never recorded a tick — the scheduler's own write transactions leak the
same way and block it.

Parameterless reads keep working throughout, so a healthcheck stays green:
{"ok":true,"db":true,…} while nothing that writes can proceed.

What I could NOT reduce

Three attempts that do not reproduce it — all behave identically to node:

  1. Raw mysql2 on a pool: getConnectionbeginTransactionINSERT
    commitrelease, counting innodb_trx at each step. Zero open
    transactions throughout, on both runtimes.
  2. A drizzle read-only db.transaction() — no leak.
  3. Drizzle inserts and selects inside a transaction, including
    where inArray(...) and for…of over the result — all correct, and
    bootstrap-admin (no transaction) works perfectly.

So it needs a write transaction with real lock acquisition, and probably the
drizzle wrapper rather than the driver directly. I have a service that
reproduces it 100% of the time and am happy to instrument it, test a patch, or
run any query you want against it.

Why it matters

The data is written correctly, the endpoint returns success, and the
healthcheck stays green — so nothing signals the problem until the next write
times out. On a platform where those writes are auctions and payments, a
transaction that holds locks indefinitely after "succeeding" is worse than one
that fails.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions