#9109·turso

MVCC does not reload planner statistics after ANALYZE

Author: jussisaurioCreated Sep 17, 2026Updated Sep 17, 2026
Labelsmvccoptimizer

Problem

In MVCC mode, ANALYZE writes sqlite_stat1 but does not refresh the planner statistics on the same connection.

Later statements use default row estimates. This behavior can select a more expensive query plan.

WAL mode reloads the statistics after the same ANALYZE statement.

Reproduction

sql
PRAGMA journal_mode = mvcc;
CREATE TABLE t(id INTEGER PRIMARY KEY, value INTEGER);

WITH RECURSIVE seq(n) AS (
    VALUES(1)
    UNION ALL
    SELECT n + 1 FROM seq WHERE n < 100
)
INSERT INTO t SELECT n, n FROM seq;

ANALYZE;

SELECT tbl, idx, stat FROM sqlite_stat1;
EXPLAIN QUERY PLAN FORMAT=JSON SELECT * FROM t;

sqlite_stat1 contains the correct row count:

t||100

The plan still uses the default estimate:

json
"rows_per_input": 1000000,
"output_rows": 1000000

Without MVCC, the same statements use the new statistic:

json
"rows_per_input": 100,
"output_rows": 100

Cause

Statement::step() calls refresh_analyze_stats() after ANALYZE finishes.

The refresh returns immediately when the connection has TransactionState::Write. The MVCC connection still has this state at that point.

As a result, schema.analyze_stats keeps its old values.

Relevant code: core/stats.rs

Expected behavior

After an autocommit ANALYZE statement, the planner must use the new statistics for the next statement on the same connection.

MVCC and WAL must report the 100-row estimate in this example.

Suggested test

Add an MVCC SQL test that runs ANALYZE and checks the row estimate in the next plan.