From 93cd3e3ccc9c427da5c9e7136cb32665231073c8 Mon Sep 17 00:00:00 2001 From: bb7133 Date: Wed, 6 May 2026 14:25:44 -0700 Subject: [PATCH 1/2] executor: fix INSERT regression for tables without generated columns (#68129) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When PR #67917 hoisted MutRowFromDatums(row) outside the generated-column evaluation loop to avoid O(G×C) allocations, the call became unconditional. For tables with no generated columns (the common case — e.g. YCSB usertable), MutRowFromDatums is now called on every INSERT row, allocating a full Chunk with N Column objects and copying all datums, all for zero benefit since the loop body never executes. This is the root cause of the ~20% YCSB Workload A regression reported in issue #68129. Fix: guard the MutRowFromDatums call with an early return when gCols is empty, restoring zero overhead for the common case while preserving the O(G×C)→O(C) optimization for tables that actually have generated columns. Also change the gCols slice initializer from make([]*table.Column, 0) to var gCols []*table.Column to avoid the small slice header allocation when no generated columns are present. Fixes #68129 --- pkg/executor/insert_common.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/pkg/executor/insert_common.go b/pkg/executor/insert_common.go index c0b9e68a580a8..bc3310933dc00 100644 --- a/pkg/executor/insert_common.go +++ b/pkg/executor/insert_common.go @@ -673,7 +673,7 @@ func (e *InsertValues) fillColValue( func (e *InsertValues) fillRow(ctx context.Context, row []types.Datum, hasValue []bool, rowIdx int) ( []types.Datum, error, ) { - gCols := make([]*table.Column, 0) + var gCols []*table.Column tCols := e.Table.Cols() if e.hasExtraHandle { col := &table.Column{} @@ -712,6 +712,11 @@ func (e *InsertValues) fillRow(ctx context.Context, row []types.Datum, hasValue } } + // Fast path: no generated columns, nothing more to do. + if len(gCols) == 0 { + return row, nil + } + sctx := e.Ctx() evalCtx := sctx.GetExprCtx().GetEvalCtx() sc := sctx.GetSessionVars().StmtCtx From 09434bad81c3bb0d4d02ccb7cb9adf31c8b7fc2c Mon Sep 17 00:00:00 2001 From: bb7133 Date: Wed, 6 May 2026 16:24:13 -0700 Subject: [PATCH 2/2] executor: add BenchmarkInsertYCSBLike to cover no-generated-column INSERT path Adds a benchmark reproducing the regression from #68129: an 11-column table (1 VARCHAR PK + 10 VARCHAR fields, no generated columns) modelling the YCSB usertable schema. This exercises the MutRowFromDatums fast-path guard added in the fix and will catch any future regression on plain tables. --- pkg/executor/bench_gencol_test.go | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/pkg/executor/bench_gencol_test.go b/pkg/executor/bench_gencol_test.go index edbeed6350f4b..45d9163b27013 100644 --- a/pkg/executor/bench_gencol_test.go +++ b/pkg/executor/bench_gencol_test.go @@ -63,6 +63,31 @@ func BenchmarkInsertWideTableWithGC(b *testing.B) { } } +// BenchmarkInsertYCSBLike benchmarks INSERT into an 11-column table with no generated columns, +// modeling the YCSB usertable schema (1 VARCHAR PK + 10 VARCHAR value fields). +// This is the regression scenario from issue #68129: MutRowFromDatums is called +// unconditionally even when gCols is empty, adding unnecessary allocation per row. +func BenchmarkInsertYCSBLike(b *testing.B) { + store := testkit.CreateMockStore(b) + tk := testkit.NewTestKit(b, store) + tk.MustExec("use test") + + defs := []string{"YCSB_KEY VARCHAR(255) PRIMARY KEY"} + vals := []string{"'user1000'"} + for i := 0; i < 10; i++ { + defs = append(defs, fmt.Sprintf("FIELD%d VARCHAR(100)", i)) + vals = append(vals, fmt.Sprintf("'value%d'", i)) + } + tk.MustExec("CREATE TABLE usertable (" + strings.Join(defs, ", ") + ")") + + // Use INSERT IGNORE so repeated runs don't fail on the duplicate PK. + insertSQL := "INSERT IGNORE INTO usertable VALUES (" + strings.Join(vals, ", ") + ")" + b.ResetTimer() + for i := 0; i < b.N; i++ { + tk.MustExec(insertSQL) + } +} + // BenchmarkInsertWideTableWithNonLiteralGC benchmarks INSERT into a table with 150 virtual // generated columns that reference a real column (GENERATED ALWAYS AS (LOWER(name)) VIRTUAL). // This exercises the non-constant GC path where each GC must read the base column value.