Changes for version 0.22 - 2026-10-16

  • Faster hot paths. Client CPU per query against MariaDB / Pg: insert -18% / -15%, update -16% / -6%, reads -2% to -9%. SQLite in memory (the library's own cost): update -32%, insert -28%, delete -27%, val -23%, upsert -19%, hash -16%. Same on perl 5.40 and 5.44; see dev/benchmarks-0.21-0.22.md. DBIx::Fast now adds ~2 us of CPU per query over plain DBI with a prepared handle (~11 us on MariaDB/Pg):
    • the SQL role keeps the connector in a field (no accessor call)
    • separate caches for consumed handles (no FETCH('Active') per call) and for handles given to the caller (query/exec/ execute_prepare keep the check)
    • Connector::dbh checks Active every 100 calls; any DB error and any disconnect() on the handle force the check on the next call (the time-based ping check still runs on every call)
    • CRUD writes: inline execute path; a cached, validated column set with plain values is not revalidated (never for names holding the cache key's NUL separator); last_id read inline
  • compile_val / compile_hash / compile_all / compile_insert: prepared operations for loops (compile_insert about half the time of insert)
  • Fix: rare SIGSEGV at exit (a few % of runs, seen with DBD::SQLite): the statement cache (0.20) lives outside the DBI handle, so global destruction could free a cached statement after its connection and DBD::SQLite finalized it on a closed db. An END block now drops the cached statements while the connections are open
  • on_slow_query: the report carries caller ("file:line" of the application code that ran the statement)
  • on_slow_query => { explain => 1 }: the plan of a slow SELECT in $q->{explain} (EXPLAIN / EXPLAIN QUERY PLAN on the same handle; skipped with a result set open, inside a Pg transaction, and on Pg with redact)
  • on_slow_query => { redact => 0 | 1 | sub ($sql, @binds) {...} }: its own bind masking (defaults to errors_redact as before)
  • page(..., count => 'window'): the total with COUNT(*) OVER () in the page query, one pass instead of two (LIKE '%x%' ordered by an unindexed column, 200k rows: MariaDB 226 -> 114 ms, Pg 68 -> 33 ms)
  • count_by($table, $column, \%where): { value => rows } with one GROUP BY instead of a count() per value
  • sql_stats => 1 | N: executions, total and max time per statement (placeholder lists collapsed: IN (?, ...), multi-row VALUES), bounded (500 distinct by default, the rest under "(other)"); sql_stats(top, order), reset_sql_stats()
  • scope_start / scope_end: per-statement counts of a unit of work (a request, a job); on_repeated_query => { n, cb } fires when one statement runs n times in a scope (N+1), with the caller of the n-th run
  • Slow-statement reporting moved out of the per-statement observer (fewer fields bound per observed statement)
  • DBIx::Fast->metrics_merge(@texts): one Prometheus exposition from several workers' metrics_text (documented recipe for preforking servers, where a scrape reaches a single worker)
  • Docs: metrics/on_slow_query cost ~3 us per statement (timing + no cached-statement fast path), not 1-1.5 us

Modules

DBI fast & easy
Base class for DBIx::Fast components
Query result cache with TTL and tag invalidation
Redis backend for DBIx::Fast's result cache
Query proxy that caches results
DBIx::Fast connection manager fork safe
Unified output layer for the profiler subsystem
Abstract base class for database driver profiles
MariaDB/MySQL profiling and diagnostics
PostgreSQL profiling and diagnostics
SQLite profiling stub
MySQL profiling and diagnostics
Query profiling and statistics for DBIx::Fast
Lightweight row iterator for query results
Query and CRUD methods of DBIx::Fast
Schema introspection for DBIx::Fast
a DBIx::Fast with canned answers, for unit tests
Transaction management for DBIx::Fast
Typed exceptions for DBIx::Fast