2.8 KiB
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