Skip to content

Commit 5ccd6d4

Browse files
author
Dag Wanvik
committed
Bug#36652610 UNIONs are O(N^2) [1/2 noclose]
The space allocation during parsing/contextualization/flattening of the parse tree for a large number of similar set operations[*] is not linear in N, where N is the number of operations (e.g. UNION ALL as shown in the bug report). The problem lies within merge_descendants. [*] It could be any set operations, both DISTINCT and ALL, except for INTERSECT ALL which isn't flattened. Contextualization is currently based on recursive dives on the parse tree, and a large number of e.g. UNION ALL have a parse tree looking like UNION ALL / \ UNION ALL query : \ : query : UNION ALL / \ query query The arrays holding the children (Query_term_set_op::m_children) on each level will grow (and then be discarded) as we merge descendants from the bottom up, ending up with UNION ALL | ----------- | | ... | 1 2 N+1 On the way there we create N m_children arrays containing SELECT UNION ALL-> [query-1 query-2] SELECT UNION ALL-> [query-1 query-2 query-3] : SELECT UNION ALL-> [query-1 query-2 query-3 .. query-N+1] the current logic appends the children of the lower set operation to the list of children to the next level up set operation. This means we get N arrays with an average of N/2 elements, which is not linear. Solution: avoid this malign pattern by checking if the lower UNION ALL has more childen than the upper: in that case add any upper's child(ren) to the front of the lower UNION ALL's child list, then using copy (std::move) that array the current array for the higher level UNION ALL. That way we grow only a single array (moved up the levels), which grows from 2 to N+1 elements after merge_descendants is done. Note that in the repro case, the number of children in the upper set operation ("setop" in the code) is always zero, so we have no need to add any elements to the front of the m_children array. The new block for that level will be appended later in the loop for (Query_term *elt : ql.m_elts) {..} Change-Id: I3658c27de3f9e20237f530ae2832f5aab80c9927
1 parent 1b72ae2 commit 5ccd6d4

File tree

3 files changed

+30
-7
lines changed

3 files changed

+30
-7
lines changed

include/mem_root_deque.h

+1
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,7 @@ class mem_root_deque {
152152
: m_blocks(other.m_blocks),
153153
m_begin_idx(other.m_begin_idx),
154154
m_end_idx(other.m_end_idx),
155+
m_capacity(other.m_capacity),
155156
m_root(other.m_root) {
156157
other.m_blocks = nullptr;
157158
other.m_begin_idx = other.m_end_idx = other.m_capacity = 0;

sql/parse_tree_nodes.cc

+28-7
Original file line numberDiff line numberDiff line change
@@ -1491,6 +1491,30 @@ static Surrounding_context qt2sc(Query_term_type qtt) {
14911491
return SC_TOP;
14921492
}
14931493

1494+
/// Append the children of 'lower' to those of 'setop'. To avoid excessive
1495+
/// space usage of a large number of similar set ops: instead of the "obvious"
1496+
/// method of copying the operands of the child's array to the parent's array,
1497+
/// we may use the (possibly much) larger lower setop's child array as a basis,
1498+
/// inserting setop's childen at the front and then std::move'ing that (now
1499+
/// discarded) array to be setop's child array. This makes the space
1500+
/// allocation ~= linear instead of O(N*N/2) in the worst case (N: # of set
1501+
/// operations).
1502+
void PT_set_operation::merge_children(Query_term_set_op *setop,
1503+
Query_term_set_op *lower) {
1504+
if (lower->m_children.size() <= setop->m_children.size()) {
1505+
for (auto child : lower->m_children) {
1506+
setop->m_children.push_back(child);
1507+
}
1508+
} else {
1509+
const size_t lim = setop->m_children.size() - 1;
1510+
for (size_t i = 0; i < setop->m_children.size(); ++i) {
1511+
lower->m_children.push_front(setop->m_children[lim - i]);
1512+
}
1513+
setop->m_children = std::move(lower->m_children);
1514+
// lower->m_children is now empty.
1515+
}
1516+
}
1517+
14941518
/**
14951519
Possibly merge lower syntactic levels of set operations (UNION, INTERSECT and
14961520
EXCEPT) into setop, and set new last DISTINCT index for setop. We only ever
@@ -1653,8 +1677,7 @@ void PT_set_operation::merge_descendants(Parse_context *pc,
16531677
// distinct
16541678
last_distinct = count + lower->m_children.size() - 1;
16551679
count = count + lower->m_children.size();
1656-
// fold in children
1657-
for (auto child : lower->m_children) setop->m_children.push_back(child);
1680+
merge_children(setop, lower);
16581681
} else {
16591682
// similar kind of set operation, but contains limit, so do not merge
16601683
count++;
@@ -1693,8 +1716,7 @@ void PT_set_operation::merge_descendants(Parse_context *pc,
16931716
}
16941717
last_distinct = count + lower->m_last_distinct;
16951718
count = count + lower->m_children.size();
1696-
for (auto child : lower->m_children)
1697-
setop->m_children.push_back(child);
1719+
merge_children(setop, lower);
16981720
}
16991721
} else {
17001722
// upper and lower level are both ALL, so ok to merge, unless we have
@@ -1705,8 +1727,7 @@ void PT_set_operation::merge_descendants(Parse_context *pc,
17051727
first_distinct = count + lower->m_first_distinct;
17061728
}
17071729
count = count + lower->m_children.size();
1708-
for (auto child : lower->m_children)
1709-
setop->m_children.push_back(child);
1730+
merge_children(setop, lower);
17101731
} else {
17111732
// do not merge INTERSECT ALL: the execution time logic can only
17121733
// handle binary INTERSECT ALL.
@@ -1725,7 +1746,7 @@ void PT_set_operation::merge_descendants(Parse_context *pc,
17251746
first_distinct = count + lower->m_first_distinct;
17261747
}
17271748
count = count + lower->m_children.size();
1728-
for (auto child : lower->m_children) setop->m_children.push_back(child);
1749+
merge_children(setop, lower);
17291750
} else {
17301751
// do not merge
17311752
count++;

sql/parse_tree_nodes.h

+1
Original file line numberDiff line numberDiff line change
@@ -1822,6 +1822,7 @@ class PT_set_operation : public PT_query_expression_body {
18221822
protected:
18231823
bool contextualize_setop(Parse_context *pc, Query_term_type setop_type,
18241824
Surrounding_context context);
1825+
void merge_children(Query_term_set_op *setop, Query_term_set_op *lower);
18251826
PT_query_expression_body *m_lhs;
18261827
bool m_is_distinct;
18271828
PT_query_expression_body *m_rhs;

0 commit comments

Comments
 (0)