Bug #120562 InnoDB crashes with assertion failure when UPDATE touches a table with many secondary indexes
Submitted: 28 May 12:53
Reporter: Li Zeyan Email Updates:
Status: Open Impact on me:
None 
Category:MySQL Server: InnoDB storage engine Severity:S1 (Critical)
Version:9.6.0 OS:Linux
Assigned to: CPU Architecture:Any

[28 May 12:53] Li Zeyan
Description:
MySQL 9.6.0 crashes with a SIGABRT when executing an UPDATE statement on a table that has 31 or more secondary indexes, where the indexes include a mix of:
  - Regular column indexes
  - Indexes on STORED generated columns
  - Indexes on VIRTUAL generated columns
  - Functional indexes (expression indexes) referencing VIRTUAL generated columns

The crash occurs at ha_innodb.cc:10024 with the assertion:
    buf <= (byte *)original_upd_buff + buff_len

This indicates that InnoDB's internal update buffer (upd_buff) overflows during row update. When a row is updated, InnoDB must update entries in all secondary indexes. The buffer is pre-allocated with a fixed size (buff_len), but when the number of indexes exceeds a threshold (30 in this case), the total space required for all index entry updates exceeds the pre-allocated buffer size, causing the assertion failure and subsequent server crash.

The crash is deterministic and reproducible. With exactly 30 indexes, the UPDATE succeeds. Adding any single additional index (bringing the total to 31) triggers the crash.

Shell output:
ERROR 2013 (HY000) at line 1: Lost connection to MySQL server during query EXIT CODE: 1

Error log output: (abbreviated)
    [ERROR] [MY-013183] [InnoDB] Assertion failure: ha_innodb.cc:10024:buf <=(byte *)original_upd_buff + buff_len thread XXXXX
    InnoDB: If you get repeated assertion failures or crashes, even
    InnoDB: immediately after the mysqld startup, there may be
    mysqld got signal 6 ;

How to repeat:
DROP DATABASE IF EXISTS test_crash;
CREATE DATABASE test_crash;
USE test_crash;

CREATE TABLE idx_t0 (
  c0 int NOT NULL,
  gen_col_a bigint GENERATED ALWAYS AS (abs(c0)) VIRTUAL,
  gen_col_b bigint GENERATED ALWAYS AS ((c0 % 100)) VIRTUAL,
  gen_col_c bigint GENERATED ALWAYS AS (abs(c0)) VIRTUAL,
  gen_col_d bigint GENERATED ALWAYS AS ((c0 * 2)) STORED,
  gen_col_e bigint GENERATED ALWAYS AS ((c0 * 2)) STORED,
  -- Indexes 1-10: basic indexes on c0, gen_col_d
  KEY k1 (c0), KEY k2 (gen_col_d), KEY k3 (c0), KEY k4 (gen_col_d),
  KEY k5 (c0 DESC), KEY k6 (c0), KEY k7 (gen_col_d), KEY k8 (c0),
  KEY k9 (c0), KEY k10 (c0) USING BTREE,
  -- Indexes 11-20: indexes on virtual gen_col_c, gen_col_b, functional indexes
  KEY k11 (gen_col_c) USING BTREE,
  KEY k12 ((gen_col_b * 2)),
  KEY k13 (gen_col_c, c0 DESC),
  KEY k14 (gen_col_b), KEY k15 (gen_col_b),
  KEY k16 ((ifnull(c0,0))),
  KEY k17 (gen_col_b),
  KEY k18 ((gen_col_b + 1)),
  KEY k19 (gen_col_c),
  KEY k20 (gen_col_c, c0),
  -- Indexes 21-30: more duplicate indexes
  KEY k21 (gen_col_b),
  KEY k22 ((gen_col_d * 2)),
  KEY k23 (gen_col_c), KEY k24 (gen_col_c),
  KEY k25 ((c0 * 2)),
  KEY k26 (c0, gen_col_a),
  KEY k27 (gen_col_a, c0),
  KEY k28 (gen_col_c),
  KEY k29 (gen_col_b DESC),
  KEY k30 (gen_col_b, c0 DESC),
  -- Index 31: THE ONE THAT TRIGGERS THE CRASH
  -- (removing this index makes the UPDATE survive)
  KEY k31 ((ifnull(gen_col_c,0)))
) ENGINE=InnoDB;

INSERT INTO idx_t0 (c0) VALUES (2758),(2758),(2758),(2758),(2758);

-- This UPDATE crashes MySQL 9.6.0 (Lost connection / ha_innodb.cc:10024 assertion)
UPDATE idx_t0 SET c0 = 2096364476 WHERE c0 = 2758 LIMIT 5;

Suggested fix:
In ha_innodb.cc around line 10024, the pre-allocated update buffer (upd_buff) size
(buff_len) is computed based on the number and types of indexes on the table. The current buffer size calculation underestimates the space needed when the table has
many indexes involving generated columns (both STORED and VIRTUAL) and functional
indexes. The buff_len calculation should be revised to account for the additional
index entries introduced by functional indexes on generated columns, which may
require more space than a plain column index entry. Specifically, when computing
the update buffer size, the code should consider that each functional index on a
VIRTUAL generated column may add extra entry overhead beyond what a standard
secondary index entry would require, and should also ensure the buffer grows
proportionally to the total number of secondary indexes rather than using a fixed
upper bound.