Bug #121317 Multi-valued index in an index_merge (sort_union) plan crashes mysqld in check_unique_fields()
Submitted: 18 Sep 13:49
Reporter: Alexander Olekhnovich (OCA) Email Updates:
Status: Open Impact on me:
None 
Category:MySQL Server: Optimizer Severity:S1 (Critical)
Version:8.4.11,9.7.1 OS:Linux (Fedora 42, kernel 6.17.12)
Assigned to: CPU Architecture:ARM (aarch64, 8 cores, 31 GiB RAM)
Tags: crash, index_merge, multi-valued-index

[18 Sep 13:49] Alexander Olekhnovich
Description:
mysqld crashes with SIGSEGV on a SELECT whose plan is an index_merge (sort_union) where one of the merged indexes is a multi-valued index on a JSON column.
100% reproducible with the test case below on the official 8.4.11 and 9.7.1 binaries.

On a debug build of the mysql-8.4.11 tag it does not reach the segfault, it hits an assertion first:
mysqld: sql/handler.cc:3281: int handler::ha_index_read_map(uchar*, const uchar*, key_part_map, ha_rkey_function): Assertion `inited == INDEX' failed.

Bug #99234 and bug #100416 end up in the same place, ha_index_read_map() and calculate_key_len() below the dedup check, but they get there through semijoin duplicate weedout rather than index_merge. Both were told they are duplicates of an internal bug, #99234 with 8.0.20 named as the fix version, #100416 without a version, and both were left in Can't repeat. Might be related, might not.

How to repeat:
Short version:
```
CREATE DATABASE mvibug;
USE mvibug;

CREATE TABLE t1 (
  id VARCHAR(191) NOT NULL PRIMARY KEY,
  j  JSON DEFAULT NULL,
  KEY mvi ((CAST(j->'$[*]' AS CHAR(64) ARRAY)))
) ENGINE=InnoDB CHARACTER SET utf8mb4;

CREATE TABLE t2 (
  id  INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
  tid VARCHAR(191) DEFAULT NULL,
  c   VARCHAR(16) DEFAULT NULL,
  KEY k (tid)
) ENGINE=InnoDB CHARACTER SET utf8mb4;

INSERT INTO t1 VALUES ('a','["x"]'), ('b','["y"]'), ('c','["x","x","x"]');
INSERT INTO t2 (tid, c) VALUES ('a','4'), ('b','4'), ('c','4');

SELECT /*+ INDEX_MERGE(t1 mvi, PRIMARY) */ t1.id
  FROM t1 JOIN t2 ON t1.id = t2.tid
 WHERE t2.c = '4' AND ('x' MEMBER OF (t1.j->'$[*]') OR t1.id = 'b')
 ORDER BY t1.id;
```

What I expected: 3 rows, a / b / c. Replacing the hint with
/*+ NO_INDEX_MERGE(t1) */ on the same data returns exactly that.
What actually happens: mysqld dies with SIGSEGV.

Same crash with no optimizer hints at all: the optimizer picks
sort_union(mvi,PRIMARY) on its own once the table is large enough.
Load takes ~20 seconds. 

```
DROP DATABASE IF EXISTS mvibig; CREATE DATABASE mvibig; USE mvibig;
CREATE TABLE t (
  id          varchar(191) NOT NULL,
  fixed_keys  json DEFAULT NULL,
  status      int DEFAULT NULL,
  create_time datetime DEFAULT NULL,
  PRIMARY KEY (id),
  KEY mvi ((cast(fixed_keys -> '$[*]' as char(64) array)))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE u (
  id  int NOT NULL AUTO_INCREMENT,
  tid varchar(191) DEFAULT NULL,
  c   varchar(16)  DEFAULT NULL,
  PRIMARY KEY (id),
  KEY k (tid)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
SET SESSION cte_max_recursion_depth = 1000000;
INSERT INTO t (id, fixed_keys, status, create_time)
WITH RECURSIVE n(x) AS (SELECT 1 UNION ALL SELECT x+1 FROM n WHERE x < 50000)
SELECT CONCAT('ID-', LPAD(x, 180, '0')),
       JSON_ARRAY('KEY_A','KEY_A','KEY_A','FILLER_A','FILLER_B'),
       x % 5, NOW() - INTERVAL x SECOND FROM n;
INSERT INTO t (id, fixed_keys, status, create_time)
WITH RECURSIVE n(x) AS (SELECT 1 UNION ALL SELECT x+1 FROM n WHERE x < 150000)
SELECT CONCAT('FILL-', LPAD(x, 178, '0')),
       JSON_ARRAY(CONCAT('OTHER_', x % 997), 'NOPE_A', 'NOPE_B'),
       x % 5, NOW() - INTERVAL x SECOND FROM n;
INSERT INTO u (tid, c) SELECT id, '4' FROM t WHERE id LIKE 'ID-%';
ANALYZE TABLE t;
SELECT 'DATA LOADED' AS stage;

SELECT * FROM t
  INNER JOIN u ON t.id = u.tid
 WHERE u.c = '4'
   AND ( JSON_OVERLAPS(t.fixed_keys->'$[*]',
           '["OTHER_0","OTHER_1","OTHER_2","OTHER_3","OTHER_4","OTHER_5","OTHER_6","OTHER_7","OTHER_8","OTHER_9","OTHER_10","OTHER_11","OTHER_12","OTHER_13","OTHER_14","OTHER_15","OTHER_16","OTHER_17","OTHER_18","OTHER_19"]')
         OR t.id IN ('ID-000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001',
                     'ID-000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002',
                     'ID-000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003') )
 ORDER BY t.fixed_keys ASC, t.status DESC, t.create_time DESC
 LIMIT 1;
```

8.4.11 Release version stack trace:
```
2026-09-18T12:28:52Z UTC - mysqld got signal 11 ;
Signal SIGSEGV (Address not mapped to object) at address 0xfffffffffffffffb
Most likely, you have hit a bug, but this error can also be caused by malfunctioning hardware.
BuildID[sha1]=d4f776a6f914965996c6865d98fcd85d85b3e66d
Thread pointer: 0xffff34012930
Attempting backtrace. You can use the following information to find out
where mysqld died. If you see no messages after this, something went
terribly wrong...
stack_bottom = ffffa41e5600 thread_stack 0x100000
 #0 0x1664607 _Z19handle_fatal_signaliP9siginfo_tPv at mysql-8.4.11/sql/signal_handler.cc:401
 #1 0x1664607 _Z19handle_fatal_signaliP9siginfo_tPv at mysql-8.4.11/sql/signal_handler.cc:391
 #2 0xffffb628e92f <unknown>
 #3 0xc7f2ac _Z18row_search_no_mvccPh15page_cur_mode_tP14row_prebuilt_tmm at mysql-8.4.11/storage/innobase/row/row0sel.cc:4142
 #4 0xb35bd7 _ZN11ha_innobase10index_readEPhPKhj16ha_rkey_function at mysql-8.4.11/storage/innobase/handler/ha_innodb.cc:10555
 #5 0xb62edb _ZN7handler14index_read_mapEPhPKhm16ha_rkey_function at mysql-8.4.11/sql/handler.h:5621
 #6 0xb62edb _ZN7handler17ha_index_read_mapEPhPKhm16ha_rkey_function at mysql-8.4.11/sql/handler.cc:3287
 #7 0xcd204b _Z19check_unique_fieldsP5TABLE at mysql-8.4.11/sql/sql_executor.cc:4142
 #8 0x12432df _ZN16Unique_on_insert10unique_addEPv at mysql-8.4.11/sql/uniques.cc:976
 #9 0xc9377b _ZN7handler21multi_range_read_nextEPPc at mysql-8.4.11/sql/handler.cc:6561
 #10 0xc93107 _ZN11ha_innobase21multi_range_read_nextEPPc at mysql-8.4.11/storage/innobase/handler/ha_innodb.cc:23722
 #11 0xc93107 _ZN7handler24ha_multi_range_read_nextEPPc at mysql-8.4.11/sql/handler.cc:6491
 #12 0xc93107 _ZN22IndexRangeScanIterator4ReadEv at mysql-8.4.11/sql/range_optimizer/index_range_scan.cc:374
 #13 0xcf1bdf _ZN16HashJoinIterator24ReadRowFromProbeIteratorEv at mysql-8.4.11/sql/iterators/hash_join_iterator.cc:737
 #14 0xcf1ad3 _ZN16HashJoinIterator4ReadEv at mysql-8.4.11/sql/iterators/hash_join_iterator.cc:1138
 #15 0xc727ab _ZN14FilterIterator4ReadEv at mysql-8.4.11/sql/iterators/composite_iterators.cc:93
 #16 0xceecb7 _ZN17StreamingIterator4ReadEv at mysql-8.4.11/sql/iterators/composite_iterators.cc:3697
 #17 0xcded5f read_all_rows at mysql-8.4.11/sql/filesort.cc:975
 #18 0xcded5f _Z8filesortP3THDP8FilesortP11RowIteratormyP13Filesort_infoP11Sort_resultPy at mysql-8.4.11/sql/filesort.cc:505
 #19 0xcde757 _ZN15SortingIterator6DoSortEv at mysql-8.4.11/sql/iterators/sorting_iterator.cc:531
 #20 0xcde253 _ZN15SortingIterator4InitEv at mysql-8.4.11/sql/iterators/sorting_iterator.cc:444
 #21 0xc5069b _ZN16Query_expression20ExecuteIteratorQueryEP3THD at mysql-8.4.11/sql/sql_union.cc:1783
 #22 0xc71a57 _ZN16Query_expression7executeEP3THD at mysql-8.4.11/sql/sql_union.cc:1846
 #23 0xc71a57 _ZN11Sql_cmd_dml13execute_innerEP3THD at mysql-8.4.11/sql/sql_select.cc:1064
 #24 0xbc723f _ZN11Sql_cmd_dml7executeEP3THD at mysql-8.4.11/sql/sql_select.cc:782
 #25 0xaa26fb _Z21mysql_execute_commandP3THDb at mysql-8.4.11/sql/sql_parse.cc:4739
 #26 0xc541d7 _Z20dispatch_sql_commandP3THDP12Parser_state at mysql-8.4.11/sql/sql_parse.cc:5406
 #27 0xc5141f _Z16dispatch_commandP3THDPK8COM_DATA19enum_server_command at mysql-8.4.11/sql/sql_parse.cc:2136
 #28 0xc4dd0f _Z10do_commandP3THD at mysql-8.4.11/sql/sql_parse.cc:1465
 #29 0xc361ef handle_connection at mysql-8.4.11/sql/conn_handler/connection_handler_per_thread.cc:304
 #30 0xeaaedf pfs_spawn_thread at mysql-8.4.11/storage/perfschema/pfs.cc:3067
 #31 0xffffb485dfe3 <unknown>
 #32 0xffffb48c928b <unknown>
 #33 0xffffffffffffffff <unknown>

Trying to get some variables.
Some pointers may be invalid and cause the dump to abort.
Query (ffff34dd22c0): SELECT /*+ INDEX_MERGE(t1 mvi, PRIMARY) */ t1.id   FROM t1 JOIN t2 ON t1.id = t2.tid  WHERE t2.c = '4' AND ('x' MEMBER OF (t1.j->'$[*]') OR t1.id = 'b')  ORDER BY t1.id
Connection ID (thread ID): 12
Status: NOT_KILLED
```

8.4.11 Debug stack trace:
```
mysqld: src-8.4.11/sql/handler.cc:3281: int handler::ha_index_read_map(uchar*, const uchar*, key_part_map, ha_rkey_function): Assertion `inited == INDEX' failed.
2026-09-18T12:51:31Z UTC - mysqld got signal 6 ;
Most likely, you have hit a bug, but this error can also be caused by malfunctioning hardware.
BuildID[sha1]=13d72cadb1adb4b95025b58294548b318fc747fb
Thread pointer: 0xffff18001050
Attempting backtrace. You can use the following information to find out
where mysqld died. If you see no messages after this, something went
terribly wrong...
stack_bottom = ffffa478e420 thread_stack 0x100000
 #0 0x3537373 print_fatal_signal at src-8.4.11/sql/signal_handler.cc:319
 #1 0x353759f _Z19handle_fatal_signaliP9siginfo_tPv at src-8.4.11/sql/signal_handler.cc:401
 #2 0xffffbce9e92f <unknown>
 #3 0xffffbb50fdbc <unknown>
 #4 0xffffbb4bba3b <unknown>
 #5 0xffffbb4a5a07 <unknown>
 #6 0xffffbb4b443f <unknown>
 #7 0x371d4cb _ZN7handler17ha_index_read_mapEPhPKhm16ha_rkey_function at src-8.4.11/sql/handler.cc:3281
 #8 0x32782e3 _Z19check_unique_fieldsP5TABLE at src-8.4.11/sql/sql_executor.cc:4142
 #9 0x34aef93 _ZN16Unique_on_insert10unique_addEPv at src-8.4.11/sql/uniques.cc:976
 #10 0x372c5c3 _ZN7handler18filter_dup_recordsEv at src-8.4.11/sql/handler.cc:8564
 #11 0x37268af _ZN7handler21multi_range_read_nextEPPc at src-8.4.11/sql/handler.cc:6561
 #12 0x37278ef _ZN10DsMrr_impl10dsmrr_nextEPPc at src-8.4.11/sql/handler.cc:6904
 #13 0x4a9b8ab _ZN11ha_innobase21multi_range_read_nextEPPc at src-8.4.11/storage/innobase/handler/ha_innodb.cc:23722
 #14 0x372651b _ZN7handler24ha_multi_range_read_nextEPPc at src-8.4.11/sql/handler.cc:6491
 #15 0x3a44113 _ZN22IndexRangeScanIterator4ReadEv at src-8.4.11/sql/range_optimizer/index_range_scan.cc:374
 #16 0x3a4287b _ZN18IndexMergeIterator4ReadEv at src-8.4.11/sql/range_optimizer/index_merge.cc:238
 #17 0x3b4bc03 _ZN16HashJoinIterator24ReadRowFromProbeIteratorEv at src-8.4.11/sql/iterators/hash_join_iterator.cc:737
 #18 0x3b4cb8f _ZN16HashJoinIterator4ReadEv at src-8.4.11/sql/iterators/hash_join_iterator.cc:1138
 #19 0x3b2debf _ZN14FilterIterator4ReadEv at src-8.4.11/sql/iterators/composite_iterators.cc:93
 #20 0x3b3264b _ZN17StreamingIterator4ReadEv at src-8.4.11/sql/iterators/composite_iterators.cc:3697
 #21 0x36fb0e3 read_all_rows at src-8.4.11/sql/filesort.cc:975
 #22 0x36f9ba7 _Z8filesortP3THDP8FilesortP11RowIteratormyP13Filesort_infoP11Sort_resultPy at src-8.4.11/sql/filesort.cc:505
 #23 0x38f41d7 _ZN15SortingIterator6DoSortEv at src-8.4.11/sql/iterators/sorting_iterator.cc:531
 #24 0x38f38c3 _ZN15SortingIterator4InitEv at src-8.4.11/sql/iterators/sorting_iterator.cc:444
 #25 0x3429673 _ZN16Query_expression20ExecuteIteratorQueryEP3THD at src-8.4.11/sql/sql_union.cc:1783
 #26 0x3429a2b _ZN16Query_expression7executeEP3THD at src-8.4.11/sql/sql_union.cc:1846
 #27 0x33795bb _ZN11Sql_cmd_dml13execute_innerEP3THD at src-8.4.11/sql/sql_select.cc:1064
 #28 0x337893b _ZN11Sql_cmd_dml7executeEP3THD at src-8.4.11/sql/sql_select.cc:782
 #29 0x32f37b3 _Z21mysql_execute_commandP3THDb at src-8.4.11/sql/sql_parse.cc:4739
 #30 0x32f5ce7 _Z20dispatch_sql_commandP3THDP12Parser_state at src-8.4.11/sql/sql_parse.cc:5406
 #31 0x32ec0f7 _Z16dispatch_commandP3THDPK8COM_DATA19enum_server_command at src-8.4.11/sql/sql_parse.cc:2136
 #32 0x32ea1bb _Z10do_commandP3THD at src-8.4.11/sql/sql_parse.cc:1465
 #33 0x3522833 handle_connection at src-8.4.11/sql/conn_handler/connection_handler_per_thread.cc:304
 #34 0x56aebbf pfs_spawn_thread at src-8.4.11/storage/perfschema/pfs.cc:3067
 #35 0xffffbb50dfe3 <unknown>
 #36 0xffffbb57928b <unknown>
 #37 0xffffffffffffffff <unknown>

Trying to get some variables.
Some pointers may be invalid and cause the dump to abort.
Query (ffff18ab3000): SELECT /*+ INDEX_MERGE(t1 mvi, PRIMARY) */ t1.id   FROM t1 JOIN t2 ON t1.id = t2.tid  WHERE t2.c = '4' AND ('x' MEMBER OF (t1.j->'$[*]') OR t1.id = 'b')  ORDER BY t1.id
Connection ID (thread ID): 8
Status: NOT_KILLED
```

Suggested fix:
```
--- a/sql/handler.cc
+++ b/sql/handler.cc
@@ int handler::ha_index_end() {
   inited = NONE;
   end_range = nullptr;
   m_record_buffer = nullptr;
-  if (m_unique) m_unique->reset(false);
   return index_end();
 }
```

It looks like the filter's lifetime is already handled elsewhere. Every place I can find that releases it is an iterator destructor going through ha_extra(HA_EXTRA_DISABLE_UNIQUE_RECORD_FILTER), which frees it properly: ~IndexRangeScanIterator, ~IndexMergeIterator, ~ReverseIndexRangeScanIterator, ~RefIterator and
~RefOrNullIterator. ha_index_end() seems to be the only place that tears it down outside a destructor, and the end of one index scan is not the end of the filter.

It also passes reset(false), which closes the filter temp table's index without reopening it, where the enable path passes reset(true). The only other reset(false) is in cleanup(), and both of its callers, the DISABLE branch of ha_extra() and ha_close(), drop the filter and null m_unique right after, so a closed index does not matter there.

I removed the call on top of the mysql-8.4.11 tag, rebuilt, and the crash is gone. Deduplication still looks correct.