跪拜 Guibai
← All articles
Database

Session State Leaked Through a WHERE Clause Made Inventory Queries Flake Out

By 一只牛博 ·
Read original on juejin.cn ↗ Google Translate ↗ Alt translation

Functions with side effects inside a WHERE clause create a dependency on evaluation order that SQL engines do not guarantee. Combined with connection pooling, the resulting bugs are intermittent and nearly impossible to reproduce without methodical connection-lifecycle testing.

Summary

An inventory batch lookup would sometimes return rows and sometimes return nothing, despite identical code and data. The query placed two functions in the WHERE clause: one to write a batch number into session state via `set_config`, and another to read it back via `current_setting`. The writer assumed the setter would run first, but the getter appeared earlier in the clause and executed first on a fresh connection, reading an empty variable.

Worse, connection pooling masked the bug. A reused connection that had already run a prior query with the same batch number would carry stale session state, making the broken query appear to work. Only by disconnecting and reconnecting could the empty-result failure be reproduced reliably. The execution plan confirmed that both conditions were folded into a single filter expression, but the plan alone cannot prove the database will always honor the written order.

The fix separates the state-setting call from the read query into two distinct statements. A more durable solution eliminates session-variable passing entirely and passes the batch number as a direct parameter in the WHERE clause.

Takeaways
SQL does not guarantee left-to-right evaluation of WHERE clause conditions; relying on it is a bug waiting to happen.
Functions that modify session state, write logs, or change data should never appear in a WHERE clause.
Connection pooling can hide state-leak bugs because a reused connection may carry stale session variables from a previous request.
Disconnecting and reconnecting is a reliable way to expose session-state dependencies that survive across queries on the same connection.
Splitting state-setting and querying into two separate statements removes the evaluation-order gamble.
Declaring pure read functions as IMMUTABLE or STABLE signals intent to both the optimizer and future maintainers.
Conclusions

The bug survived in production because connection pooling turned it into a probabilistic failure — the same broken SQL would succeed or fail depending on which connection the pool handed out, making it look like a caching or data issue.

The execution plan showing conditions in written order is a red herring: it describes what happened on that run, not what the optimizer is allowed to do. Trusting it as proof of guaranteed order is a common diagnostic mistake.

The deeper design smell is using session-scoped variables to pass parameters that were already available in application memory. The session state was a workaround that introduced coupling between queries and connection lifecycle.

Concepts & terms
set_config / current_setting (PostgreSQL/KES)
Functions that write and read key-value pairs scoped to the current database session. Values persist for the life of the connection and are invisible to other connections, making them a common but risky way to pass state between queries.
IMMUTABLE / STABLE function volatility categories
Labels in PostgreSQL and KES that tell the optimizer whether a function can modify data or depends on state. IMMUTABLE functions always return the same output for the same input and cannot modify the database; STABLE functions cannot modify the database but may return different results within a single table scan. Both signal that the function has no side effects.
Source: juejin.cn ↗ Google Translate ↗ Backup ↗