Skip to content

Explicit transaction COMMIT retry can leave snapshot statistics row count stale #20007

Description

@SkyFan2002

Summary

When an explicit transaction inserts rows into a Fuse table and COMMIT has to retry because another session committed the same table first, the final table data is correct, but system.statistics.stats_row_count can remain stale.

This is observable with SQL: after the retried commit, count(*) and system.statistics.actual_row_count report 115 rows, while system.statistics.stats_row_count still reports 105 rows.

Reproduction

Observed on a local standalone debug build:

databend-query v1.2.919-nightly-9ee8544ffd(rust-1.94.0-nightly-2026-06-14T09:04:25.972757091Z)

Setup:

DROP DATABASE IF EXISTS txn_stats_repro;
CREATE DATABASE txn_stats_repro;
USE txn_stats_repro;
SET enable_table_snapshot_stats = 1;
CREATE TABLE t(a INT);
INSERT INTO t SELECT number::int FROM numbers(100);
ANALYZE TABLE t;

SELECT column_name, stats_row_count, actual_row_count, distinct_count, min, max
FROM system.statistics
WHERE database = 'txn_stats_repro' AND table = 't'
ORDER BY column_name;

Baseline output:

column_name  stats_row_count  actual_row_count  distinct_count  min  max
a            100              100               100             0    99

Session A:

USE txn_stats_repro;
SET enable_table_snapshot_stats = 1;
BEGIN;
INSERT INTO t SELECT (1000 + number)::int FROM numbers(10);
SELECT count(*) AS count_seen_in_a_before_commit FROM t;

Session A sees the uncommitted insert:

count_seen_in_a_before_commit
110

Session B:

USE txn_stats_repro;
SET enable_table_snapshot_stats = 1;
INSERT INTO t SELECT (2000 + number)::int FROM numbers(5);

SELECT count(*) AS count_seen_in_b_after_commit FROM t;
SELECT column_name, stats_row_count, actual_row_count, distinct_count, min, max
FROM system.statistics
WHERE database = 'txn_stats_repro' AND table = 't'
ORDER BY column_name;

Session B output:

count_seen_in_b_after_commit
105

column_name  stats_row_count  actual_row_count  distinct_count  min  max
a            105              105               105             0    2004

Back in Session A:

COMMIT;
SELECT count(*) AS count_after_a_commit FROM t;
SELECT column_name, stats_row_count, actual_row_count, distinct_count, min, max
FROM system.statistics
WHERE database = 'txn_stats_repro' AND table = 't'
ORDER BY column_name;

Actual output:

count_after_a_commit
115

column_name  stats_row_count  actual_row_count  distinct_count  min  max
a            105              115               115             0    2004

Expected output:

column_name  stats_row_count  actual_row_count
a            115              115

Notes

The query log file shows Session A's COMMIT going through the retry path:

retry::commit: Computing segments diff for table 25 (base: 1 segments, txn: 2 segments)
retry::commit: try_rebuild_req: update_failed_tbls=[... statistics: TableStatistics { number_of_rows: 105, ... }]

After the commit, table metadata has the correct table row count (TableStatistics { number_of_rows: 115, ... }), but snapshot statistics row count remains stale and is exposed by system.statistics.stats_row_count.

A likely cause is that the retry path only reads inserted rows from the multi-table-insert-specific transaction buffer:

  • src/query/storages/fuse/src/retry/commit.rs: try_rebuild_req reads ctx.txn_mgr().multi_table_insert_rows() and only updates additional_stats_meta.row_count / HLL when that map contains rows for the table.
  • src/query/storages/common/session/src/transaction.rs: multi_table_insert_rows is only populated through add_multi_table_insert_rows.
  • Current callers of add_multi_table_insert_rows are in the multi-table insert commit path, so ordinary INSERT inside an explicit transaction has no entry even though its transaction-generated CommitMeta already contains insert_rows and HLL data.

This makes the explicit transaction retry path depend on a multi-table-insert-only side channel for snapshot statistics. The retry merge should use the transaction-generated commit/snapshot metadata as the source of truth for the inserted rows and column statistics, not only TxnManager::multi_table_insert_rows.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions