Skip to content

CONCEPT Cited by 1 source

Tainted connection

Definition

A tainted connection is a pooled database connection whose per-session state has been mutated by a caller such that returning it unmodified to the pool would leak that state to unrelated subsequent callers.

The canonical taint source is a SQL SET statement: SET @@session.unique_checks = 0, SET time_zone = '+00:00', SET character_set_client = utf8mb4. These modify a session-level system setting that any subsequent query on the same connection will observe — including queries from a different caller that later borrows the connection from the pool. Other taint sources include open transactions, prepared-statement handles, temporary tables, user-defined variables (SET @x = 1), and session-scoped locks.

Why it matters

Every connection pool's correctness invariant is that connections are fungible — any caller can borrow any connection and get the same semantics. A tainted connection breaks that invariant. A pool that returns tainted connections silently changes the behaviour of unrelated callers: query results, commit semantics, encoding, time-zone interpretation all diverge unpredictably.

Once a connection is tainted, the pool has three options:

  1. Restore the setting on return (expensive, error-prone, not all settings can be cleanly restored — some have no "reset" primitive).
  2. Close the connection (defeats the pool — every taint costs a new ~50 ms MySQL SSL handshake).
  3. Don't return it — hold the connection dedicated to the caller's session for the session's duration. Becomes a reserved connection, exits the pool, counts against MySQL's max_connections ceiling forever.

Option 3 is the default in Vitess pre-v15 and in PgBouncer session mode. It is safe but pool-bypassing — the number of reserved connections can grow unbounded under workloads that taint frequently, triggering concepts/connection-pool-exhaustion.

Worked example (Vitess)

In Vitess's pre-v7.0 era, connections were never tainted because the proxy dropped all client SET statements at the protocol layer (Source: sources/2026-04-21-planetscale-connection-pooling-in-vitess). This preserved pool fungibility but broke ORM compatibility — ORMs issue SET statements at connection open and expect the settings to stick.

In Vitess v7.0 (when the proxy started speaking the MySQL wire protocol for broad ORM compatibility), SET statements were honoured, which meant connections got tainted. Vitess took option 3 — reserve the connection for the session. Subsequent optimisations layered on:

  • No-op elision: before tainting, check select 0 from dual where @@var != value; if the SET is a no-op (setting is already at the desired value), skip reservation entirely.
  • SET_VAR hint rewriting: for MySQL 8.0 + SET_VAR-eligible variables, rewrite every subsequent query in the session to carry the variable as a query-scoped optimizer hint. The variable applies per query, the connection is never tainted, reservation is avoided.
  • Settings pool (v15): instead of avoiding taint, manage it — see patterns/settings-aware-connection-pool.

Relationship to the reservation ladder

The taint problem's solutions form a ladder of increasing sophistication:

Mechanism Taint handling Pool cost
Drop SET statements Never taint (refuse to honour SETs) None (but ORM-incompatible)
Restore on return Untaint by resetting to defaults CPU + round-trip per return
Close on taint Untaint by closing Full SSL handshake per taint
Reserve on taint Hold tainted connection dedicated to session Pool bypass; MySQL max_connections pressure
SET_VAR rewrite (per-query hint) Avoid taint by applying setting per query Per-query hint-serialisation cost
Settings-aware pool Index pool by settings profile; match on borrow Bookkeeping + occasional setting-application on borrow

The Vitess trajectory climbed this ladder from row 1 (pre-v7) → row 4 (v7) → rows 4+5 (mitigated v7–v14) → row 6 (v15 settings pool).

Not unique to MySQL

The problem is general to any session-stateful database. PostgreSQL has analogous session state (SET search_path, SET timezone, prepared statements, advisory locks, SET ROLE) and PgBouncer documents three pooling modes expressing different taint-tolerance stances:

  • Session pooling — one connection per client for the session's lifetime. Taint-tolerant because the pool doesn't rotate connections across callers. Equivalent to reservation for the whole session.
  • Transaction pooling — connection returned to the pool at transaction boundary. Breaks if callers rely on between-transaction session state (prepared statements, SET, temp tables).
  • Statement pooling — connection returned after each statement. Breaks on any multi-statement session state, including transactions.

PgBouncer's modes are just named points on the taint-tolerance axis; Vitess's settings pool is a more sophisticated point on the same axis (keep pool rotation, but index by taint profile).

Seen in

  • sources/2026-04-21-planetscale-connection-pooling-in-vitesscanonical first wiki framing of the tainted-connection problem as the structural driver of Vitess's three-era connection-pooling-design evolution. Harshit Gangal (Vitess core maintainer): "when using connection pooling, all connections in the pool share the same settings. Any modifications made to a connection will render it unusable for other requests, as it becomes 'tainted'. Therefore, either settings changes made to a connection must be restored to their original values or the connection should be closed after the operation is complete to ensure the stability of the connection pool." First wiki citation of the "tainted" vocabulary and the three-option decision structure (restore / close / reserve).
Last updated · 347 distilled / 1,201 read