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:
- Raw mysql2 on a pool:
getConnection → beginTransaction → INSERT →
commit → release, counting innodb_trx at each step. Zero open
transactions throughout, on both runtimes.
- A drizzle read-only
db.transaction() — no leak.
- 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.
Summary
A write transaction on a pooled mysql2 connection commits its data but the
transaction is never ended: it stays
RUNNINGininformation_schema.innodb_trxholding row locks, and the connection returns to the pool still inside it.
Subsequent writes block on those locks and fail with
Query timed out; theservice degrades to unusable within a handful of operations.
Perry
0.5.1519from source, plus #9314 and the fix in #9319. drizzle-orm overmysql2pools, MySQL 8.0.46 on the same host. Node 26.8.1 on identical codedoes 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:
— one transaction left open holding three row locks, and the next one already
waiting on it. The owning connection shows as
Sleepin the processlist, sonothing 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:
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:
getConnection→beginTransaction→INSERT→commit→release, countinginnodb_trxat each step. Zero opentransactions throughout, on both runtimes.
db.transaction()— no leak.where inArray(...)andfor…ofover the result — all correct, andbootstrap-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.