1
0
mirror of https://github.com/MariaDB/server.git synced 2026-10-23 18:49:29 +03:00
Files
mariadb/sql/sql_parallel_workers.cc
Rex Johnston 26a94a7fae PQ: the workers pre-aggregate a query that has no GROUP BY
SELECT COUNT(*), SUM(b) FROM t ran serially. The workers could pre-aggregate a
GROUP BY, but aggregation over the whole table was refused, because the shape
the grouped path merges through is not there: with no group the server
materialises nothing, and aggregates in end_send_group() straight off the last
join tab -- no aggregation table, no group key, nothing for a partial to be
merged into.

It turns out neither end needs new machinery, and this commit is mostly the
finding of that.

The manager already merges these. end_send_group() reaches Item_sum::add(), and
add() honours a value left by direct_add() for every aggregate the gate accepts
-- COUNT adds a count instead of incrementing, AVG takes a partial sum together
with the number of rows behind it. So the drain primes the aggregates and hands
the row to the plan's own terminal here exactly as it does through end_update()
for a GROUP BY, and the drain needed no case of its own.

The workers accumulate into the grouped machinery with a single group in it.
The layout gives this shape a group of one constant column, and from there it
is the grouped path: a worker keeps one partial in a keyed container, merges
into it through end_update(), and ships one row. Beyond costing a column per
shipped row -- of which there is one per worker -- that is also what keeps
Item_sum_avg::create_tmp_field() packing the count beside the sum, which it does
only for a container that has a group. An average built any other way would have
arrived without its count.

So what is actually new is the gate. The aggregate tests -- the five that can be
merged, no DISTINCT, one argument, an argument a worker can evaluate -- are the
same for both shapes and move out to pwt_aggregates_can_be_merged(). What is
left is the shape question, and which shape to ask about is the query's own
grouping rather than the plan's: a query that names a GROUP BY stays the grouped
question whatever the optimizer then did with it, so a plan with no group key to
merge through is a refusal of that query and not an invitation to read it as
aggregating over everything, which would answer something else. Only a query
that never grouped reaches the second question.

Refused there: a select-list item reading a column outside an aggregate. Its
value is whichever row the terminal happened to hold, and a worker holds a
different one.

An empty result needed nothing. No rows means no groups means no partials
shipped, and end_send_group() still sends its one row at end of records:
COUNT(*) is 0, SUM is NULL, one row and not one per worker. The new test checks
each of those rather than assuming them.

main.parallel_query_aggregate_whole is that test: serial against parallel for
COUNT, COUNT(col), SUM, MIN, MAX and AVG over integer, decimal and double
columns; the empty case; that exactly one row is sent; and that a DISTINCT
aggregate, STD, GROUP_CONCAT and a bare column outside an aggregate are still
refused and run serially. main.parallel_query_aggregate gains the fall-back
case, where the container turns out to have no group index: the constant group
reaches the same fall-back, ships plain rows, and the manager aggregates them a
row at a time.

Known and accepted: adding doubles is not associative, so SUM and AVG over DOUBLE
or FLOAT can differ from the serial answer, and between runs, in the last
significant bit -- which worker summed which chunk is scheduling. Decimal and
integer aggregates are exact and unaffected. The test rounds well clear of the
last bit rather than leaving floats uncovered.

Under a forced-worker sweep of the main suite (--parallel-worker-threads=4)
three more tests differ than before, all of them EXPLAIN text where a query of
this shape now says ALL_parallel or range_parallel: main.costs,
main.func_group_innodb and main.innodb_full_scan. No value changes in any of
them.

This commit was prepared with Claude Code (Opus 5), which established that
Item_sum::add() already honours a directly added partial -- the fact the whole
change rests on -- and found that a container without a group would have
dropped an average's count.
2026-09-14 15:47:37 +12:00

25 KiB