java-topology/defects/mysql/patch/mysql-0005-row0sel-template-col-hashmap.md

2.8 KiB
Raw Blame History

UNDF: UNDF-2026-000001226

mysql-0005 — row_sel_store_mysql_rec: O(T×C) std::find per row on secondary index scan

Status

PATCHED

MOAD

MOAD-0001 (CWE-407)

Severity

HIGH (hot query execution path, per-row, secondary index covering scans)

Speedup

50× at T=50 template fields, C=50 secondary index columns

Language

C++

File

storage/innobase/row/row0sel.cc, function row_sel_store_mysql_rec(), line ~3029

Description

row_sel_store_mysql_rec() is called once per row fetched from a secondary index covering scan. When clust_templ_for_sec is true (reading clustered index columns via a secondary index join), it builds a std::vector<const dict_col_t *> template_col from the secondary index fields. Then, for each of T MySQL template fields it does:

it = std::find(template_col.begin(), template_col.end(), col);

template_col has C entries (one per secondary index column). Each std::find is O(C). The outer loop runs T times per row fetched, giving O(T × C) pointer comparisons per row.

For a covering index query with 50 SELECT columns (T=50) and a secondary index with 50 columns (C=50), this is 2,500 pointer comparisons per row. Over 1,000,000 rows: 2.5 billion comparisons instead of 50 million with an unordered_map.

Hot path

row_sel_store_mysql_rec() is on the critical path for every SELECT that uses a secondary index covering scan or index merge. Covering index scans are a common MySQL optimization for analytics queries and are explicitly promoted by EXPLAIN.

Complexity table

T×C (fields × sec-cols) Defective ops per row Fixed ops per row Ratio
10×10 100 10 10×
20×20 400 20 20×
50×50 2,500 50 50×
100×100 10,000 100 100×

Fix

Replace std::vector<const dict_col_t *> template_col with std::unordered_map<const dict_col_t *, ulint> template_col_map that maps each column pointer to its sec_field_no. The map is built once per call (same O(C) as before), and each lookup is O(1) instead of O(C).

// Before: O(T × C) total
std::vector<const dict_col_t *> template_col;
// ... build template_col with push_back ...
it = std::find(template_col.begin(), template_col.end(), col);
sec_field_no = it - template_col.begin();

// After: O(T + C) total
std::unordered_map<const dict_col_t *, ulint> template_col_map;
// ... build with template_col_map[col] = i ...
auto map_it = template_col_map.find(col);
if (map_it == template_col_map.end()) continue;
sec_field_no = map_it->second;

Patch file

See mysql-0005-row0sel-template-col-hashmap.patch