mirror of
https://github.com/MariaDB/server.git
synced 2026-10-23 18:49:29 +03:00
MDEV-24684 Quadratic time for MIN/MAX/STD/VARIANCE as window functions
Frame_scan_cursor cleared and re-scanned the whole frame for every row. For MIN, MAX, STD and VARIANCE keep the result if the frame did not change (whole-partition frames, peers), and add only the new rows if just the bottom moved down. Other functions without remove() (UDF and stored aggregates) are still re-scanned. Argument conversion warnings are no longer repeated when the frame is reused or extended.
This commit is contained in:
@@ -862,3 +862,155 @@ pk a b c min1 max1 min2 max2
|
||||
11 4 200 eleven 100 300 100 300
|
||||
drop table t2;
|
||||
drop table t1;
|
||||
#
|
||||
# MDEV-24684 MIN/MAX as window functions are slow when the frame is not changing
|
||||
#
|
||||
create table t1 (g int, a int, b int);
|
||||
insert into t1 values
|
||||
(1,1,10),(1,1,20),(1,2,NULL),(1,2,5),(1,3,7),
|
||||
(2,1,NULL),(2,1,NULL),
|
||||
(3,4,3),(3,5,9),(3,5,1),(3,6,2);
|
||||
select g, a, b, min(b) over (partition by g) mn, max(b) over (partition by g) mx
|
||||
from t1 order by g, a, b;
|
||||
g a b mn mx
|
||||
1 1 10 5 20
|
||||
1 1 20 5 20
|
||||
1 2 NULL 5 20
|
||||
1 2 5 5 20
|
||||
1 3 7 5 20
|
||||
2 1 NULL NULL NULL
|
||||
2 1 NULL NULL NULL
|
||||
3 4 3 1 9
|
||||
3 5 1 1 9
|
||||
3 5 9 1 9
|
||||
3 6 2 1 9
|
||||
select g, a, b, min(b) over (partition by g order by a) mn,
|
||||
max(b) over (partition by g order by a) mx,
|
||||
std(b) over (partition by g order by a) sd,
|
||||
variance(b) over (partition by g order by a) vr
|
||||
from t1 order by g, a, b;
|
||||
g a b mn mx sd vr
|
||||
1 1 10 10 20 5.0000 25.0000
|
||||
1 1 20 10 20 5.0000 25.0000
|
||||
1 2 NULL 5 20 6.2361 38.8889
|
||||
1 2 5 5 20 6.2361 38.8889
|
||||
1 3 7 5 20 5.7663 33.2500
|
||||
2 1 NULL NULL NULL NULL NULL
|
||||
2 1 NULL NULL NULL NULL NULL
|
||||
3 4 3 3 3 0.0000 0.0000
|
||||
3 5 1 1 9 3.3993 11.5556
|
||||
3 5 9 1 9 3.3993 11.5556
|
||||
3 6 2 1 9 3.1125 9.6875
|
||||
select g, a, b, min(b) over (partition by g order by a, b
|
||||
rows between 1 following and 2 following) mn
|
||||
from t1 order by g, a, b;
|
||||
g a b mn
|
||||
1 1 10 20
|
||||
1 1 20 5
|
||||
1 2 NULL 5
|
||||
1 2 5 7
|
||||
1 3 7 NULL
|
||||
2 1 NULL NULL
|
||||
2 1 NULL NULL
|
||||
3 4 3 1
|
||||
3 5 1 2
|
||||
3 5 9 2
|
||||
3 6 2 NULL
|
||||
select g, a, b,
|
||||
min(b) over (partition by g order by a, b rows between unbounded preceding and current row) c1,
|
||||
max(b) over (partition by g order by a, b rows between unbounded preceding and 1 following) c2,
|
||||
std(b) over (partition by g order by a, b rows between unbounded preceding and 1 preceding) c3
|
||||
from t1 order by g, a, b;
|
||||
g a b c1 c2 c3
|
||||
1 1 10 10 20 NULL
|
||||
1 1 20 10 20 0.0000
|
||||
1 2 NULL 10 20 5.0000
|
||||
1 2 5 5 20 5.0000
|
||||
1 3 7 5 20 6.2361
|
||||
2 1 NULL NULL NULL NULL
|
||||
2 1 NULL NULL NULL NULL
|
||||
3 4 3 3 3 NULL
|
||||
3 5 1 1 9 0.0000
|
||||
3 5 9 1 9 1.0000
|
||||
3 6 2 1 9 3.3993
|
||||
select g, a, b,
|
||||
min(b) over (partition by g order by a, b rows between 1 preceding and 1 following) mn,
|
||||
max(b) over (partition by g order by a, b rows between 1 preceding and 1 following) mx
|
||||
from t1 order by g, a, b;
|
||||
g a b mn mx
|
||||
1 1 10 10 20
|
||||
1 1 20 10 20
|
||||
1 2 NULL 5 20
|
||||
1 2 5 5 7
|
||||
1 3 7 5 7
|
||||
2 1 NULL NULL NULL
|
||||
2 1 NULL NULL NULL
|
||||
3 4 3 1 3
|
||||
3 5 1 1 9
|
||||
3 5 9 1 9
|
||||
3 6 2 2 9
|
||||
select max(b) over () from t1 order by 1 limit 3;
|
||||
max(b) over ()
|
||||
20
|
||||
20
|
||||
20
|
||||
drop table t1;
|
||||
create table t1 (a int);
|
||||
insert into t1 values (0),(4),(5),(6),(20);
|
||||
select a, min(a) over (order by a range between 10 following and 5 following) mn,
|
||||
max(a) over (order by a range between 10 following and 5 following) mx,
|
||||
std(a) over (order by a range between 10 following and 5 following) sd
|
||||
from t1;
|
||||
a mn mx sd
|
||||
0 NULL NULL NULL
|
||||
4 NULL NULL NULL
|
||||
5 NULL NULL NULL
|
||||
6 NULL NULL NULL
|
||||
20 NULL NULL NULL
|
||||
drop table t1;
|
||||
create table t2 (i int, v varchar(10) collate utf8mb4_general_ci, d decimal(10,2), t datetime);
|
||||
insert into t2 values
|
||||
(1,'b',1.50,'2020-01-03 00:00:00'),(2,'A',-2.25,'2020-01-01 00:00:00'),
|
||||
(3,'c',NULL,NULL),(4,'D',4.00,'2020-01-02 00:00:00'),(5,NULL,0.75,'2020-01-05 00:00:00');
|
||||
select i, v, d, t,
|
||||
min(v) over (order by i) mnv,
|
||||
max(v) over (order by i desc) mxv,
|
||||
max(d) over (order by i rows between unbounded preceding and 1 following) mxd,
|
||||
std(d) over (order by i rows between unbounded preceding and current row) sdd,
|
||||
min(t) over (order by i desc) mnt
|
||||
from t2 order by i;
|
||||
i v d t mnv mxv mxd sdd mnt
|
||||
1 b 1.50 2020-01-03 00:00:00 b D 1.50 0.000000 2020-01-01 00:00:00
|
||||
2 A -2.25 2020-01-01 00:00:00 A D 1.50 1.875000 2020-01-01 00:00:00
|
||||
3 c NULL NULL A D 4.00 1.875000 2020-01-02 00:00:00
|
||||
4 D 4.00 2020-01-02 00:00:00 A D 4.00 2.568506 2020-01-02 00:00:00
|
||||
5 NULL 0.75 2020-01-05 00:00:00 A NULL 4.00 2.229069 2020-01-05 00:00:00
|
||||
drop table t2;
|
||||
create table t1 (a int);
|
||||
insert into t1 select seq from seq_1_to_2000;
|
||||
flush status;
|
||||
select max(s) from (select max(a) over () s from t1) w;
|
||||
max(s)
|
||||
2000
|
||||
select variable_value < 100000 from information_schema.session_status
|
||||
where variable_name = 'handler_read_rnd';
|
||||
variable_value < 100000
|
||||
1
|
||||
flush status;
|
||||
select max(s) from (select min(a) over (order by a) s from t1) w;
|
||||
max(s)
|
||||
1
|
||||
select variable_value < 100000 from information_schema.session_status
|
||||
where variable_name = 'handler_read_rnd';
|
||||
variable_value < 100000
|
||||
1
|
||||
flush status;
|
||||
select max(s) from (select std(a) over (order by a) s from t1) w;
|
||||
max(s)
|
||||
577.3502
|
||||
select variable_value < 100000 from information_schema.session_status
|
||||
where variable_name = 'handler_read_rnd';
|
||||
variable_value < 100000
|
||||
1
|
||||
drop table t1;
|
||||
# End of 13.1 tests
|
||||
|
||||
@@ -368,3 +368,86 @@ from t2;
|
||||
|
||||
drop table t2;
|
||||
drop table t1;
|
||||
|
||||
--source include/have_sequence.inc
|
||||
|
||||
--echo #
|
||||
--echo # MDEV-24684 MIN/MAX as window functions are slow when the frame is not changing
|
||||
--echo #
|
||||
|
||||
create table t1 (g int, a int, b int);
|
||||
insert into t1 values
|
||||
(1,1,10),(1,1,20),(1,2,NULL),(1,2,5),(1,3,7),
|
||||
(2,1,NULL),(2,1,NULL),
|
||||
(3,4,3),(3,5,9),(3,5,1),(3,6,2);
|
||||
|
||||
select g, a, b, min(b) over (partition by g) mn, max(b) over (partition by g) mx
|
||||
from t1 order by g, a, b;
|
||||
|
||||
select g, a, b, min(b) over (partition by g order by a) mn,
|
||||
max(b) over (partition by g order by a) mx,
|
||||
std(b) over (partition by g order by a) sd,
|
||||
variance(b) over (partition by g order by a) vr
|
||||
from t1 order by g, a, b;
|
||||
|
||||
select g, a, b, min(b) over (partition by g order by a, b
|
||||
rows between 1 following and 2 following) mn
|
||||
from t1 order by g, a, b;
|
||||
|
||||
select g, a, b,
|
||||
min(b) over (partition by g order by a, b rows between unbounded preceding and current row) c1,
|
||||
max(b) over (partition by g order by a, b rows between unbounded preceding and 1 following) c2,
|
||||
std(b) over (partition by g order by a, b rows between unbounded preceding and 1 preceding) c3
|
||||
from t1 order by g, a, b;
|
||||
|
||||
select g, a, b,
|
||||
min(b) over (partition by g order by a, b rows between 1 preceding and 1 following) mn,
|
||||
max(b) over (partition by g order by a, b rows between 1 preceding and 1 following) mx
|
||||
from t1 order by g, a, b;
|
||||
|
||||
select max(b) over () from t1 order by 1 limit 3;
|
||||
|
||||
drop table t1;
|
||||
|
||||
create table t1 (a int);
|
||||
insert into t1 values (0),(4),(5),(6),(20);
|
||||
select a, min(a) over (order by a range between 10 following and 5 following) mn,
|
||||
max(a) over (order by a range between 10 following and 5 following) mx,
|
||||
std(a) over (order by a range between 10 following and 5 following) sd
|
||||
from t1;
|
||||
drop table t1;
|
||||
|
||||
create table t2 (i int, v varchar(10) collate utf8mb4_general_ci, d decimal(10,2), t datetime);
|
||||
insert into t2 values
|
||||
(1,'b',1.50,'2020-01-03 00:00:00'),(2,'A',-2.25,'2020-01-01 00:00:00'),
|
||||
(3,'c',NULL,NULL),(4,'D',4.00,'2020-01-02 00:00:00'),(5,NULL,0.75,'2020-01-05 00:00:00');
|
||||
select i, v, d, t,
|
||||
min(v) over (order by i) mnv,
|
||||
max(v) over (order by i desc) mxv,
|
||||
max(d) over (order by i rows between unbounded preceding and 1 following) mxd,
|
||||
std(d) over (order by i rows between unbounded preceding and current row) sdd,
|
||||
min(t) over (order by i desc) mnt
|
||||
from t2 order by i;
|
||||
drop table t2;
|
||||
|
||||
create table t1 (a int);
|
||||
insert into t1 select seq from seq_1_to_2000;
|
||||
|
||||
flush status;
|
||||
select max(s) from (select max(a) over () s from t1) w;
|
||||
select variable_value < 100000 from information_schema.session_status
|
||||
where variable_name = 'handler_read_rnd';
|
||||
|
||||
flush status;
|
||||
select max(s) from (select min(a) over (order by a) s from t1) w;
|
||||
select variable_value < 100000 from information_schema.session_status
|
||||
where variable_name = 'handler_read_rnd';
|
||||
|
||||
flush status;
|
||||
select max(s) from (select std(a) over (order by a) s from t1) w;
|
||||
select variable_value < 100000 from information_schema.session_status
|
||||
where variable_name = 'handler_read_rnd';
|
||||
|
||||
drop table t1;
|
||||
|
||||
--echo # End of 13.1 tests
|
||||
|
||||
@@ -2201,16 +2201,34 @@ private:
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/* Sum functions whose add() only accumulates and whose result can be read
|
||||
repeatedly */
|
||||
static bool is_scan_result_reusable(Item_sum::Sumfunctype sum_func)
|
||||
{
|
||||
switch (sum_func)
|
||||
{
|
||||
case Item_sum::MIN_FUNC:
|
||||
case Item_sum::MAX_FUNC:
|
||||
case Item_sum::STD_FUNC:
|
||||
case Item_sum::VARIANCE_FUNC:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
A cursor that performs a table scan between two indices. The indices
|
||||
are provided by the two cursors representing the top and bottom bound
|
||||
of the window function's frame definition.
|
||||
|
||||
Each scan clears the sum function.
|
||||
If the sum functions allow it, the result is kept if the frame is unchanged
|
||||
and extended if only the bottom moved down. Else the frame is rescanned.
|
||||
|
||||
NOTE:
|
||||
The cursor does not alter the top and bottom cursors.
|
||||
This type of cursor is expensive computational wise. This is only to be
|
||||
A rescan is expensive computational wise. This is only to be
|
||||
used when the sum functions do not support removal.
|
||||
*/
|
||||
class Frame_scan_cursor : public Frame_cursor
|
||||
@@ -2218,21 +2236,23 @@ class Frame_scan_cursor : public Frame_cursor
|
||||
public:
|
||||
Frame_scan_cursor(const Frame_cursor &top_bound,
|
||||
const Frame_cursor &bottom_bound) :
|
||||
top_bound(top_bound), bottom_bound(bottom_bound) {}
|
||||
top_bound(top_bound), bottom_bound(bottom_bound), have_result(false),
|
||||
can_append(false) {}
|
||||
|
||||
void init(READ_RECORD *info) override
|
||||
{
|
||||
cursor.init(info);
|
||||
can_append= true;
|
||||
List_iterator_fast<Item_sum> it(sum_functions);
|
||||
Item_sum *item_sum;
|
||||
while ((item_sum= it++))
|
||||
can_append&= is_scan_result_reusable(item_sum->sum_func());
|
||||
}
|
||||
|
||||
void pre_next_partition(ha_rows rownum) override
|
||||
{
|
||||
/* TODO(cvicentiu) Sum functions get cleared on next partition anyway during
|
||||
the window function computation algorithm. Either perform this only in
|
||||
cursors, or remove it from pre_next_partition.
|
||||
*/
|
||||
curr_rownum= rownum;
|
||||
clear_sum_functions();
|
||||
have_result= false;
|
||||
}
|
||||
|
||||
void next_partition(ha_rows rownum) override
|
||||
@@ -2240,11 +2260,6 @@ public:
|
||||
compute_values_for_current_row();
|
||||
}
|
||||
|
||||
void pre_next_row() override
|
||||
{
|
||||
clear_sum_functions();
|
||||
}
|
||||
|
||||
void next_row() override
|
||||
{
|
||||
curr_rownum++;
|
||||
@@ -2261,31 +2276,63 @@ private:
|
||||
const Frame_cursor &bottom_bound;
|
||||
Table_read_cursor cursor;
|
||||
ha_rows curr_rownum;
|
||||
/* The sum functions hold the result for rows last_top..last_bottom */
|
||||
bool have_result;
|
||||
ha_rows last_top, last_bottom;
|
||||
bool can_append;
|
||||
|
||||
/* Scan the rows between the top bound and bottom bound. Add all the values
|
||||
between them, top bound row and bottom bound row inclusive. */
|
||||
/* Make the sum functions hold the rows between the top bound and bottom
|
||||
bound, both inclusive: reuse, extend or rescan the previous result. */
|
||||
void compute_values_for_current_row()
|
||||
{
|
||||
THD *thd= current_thd;
|
||||
if (top_bound.is_outside_computation_bounds() ||
|
||||
bottom_bound.is_outside_computation_bounds())
|
||||
{
|
||||
clear_sum_functions();
|
||||
have_result= false;
|
||||
return;
|
||||
}
|
||||
|
||||
ha_rows start_rownum= top_bound.get_curr_rownum();
|
||||
ha_rows bottom_rownum= bottom_bound.get_curr_rownum();
|
||||
DBUG_PRINT("info", ("COMPUTING (%llu %llu)", start_rownum, bottom_rownum));
|
||||
|
||||
cursor.move_to(start_rownum);
|
||||
|
||||
for (ha_rows idx= start_rownum; idx <= bottom_rownum
|
||||
&& ((idx & 0xFF) || !thd->check_killed(true)); idx++)
|
||||
ha_rows first_rownum= start_rownum;
|
||||
if (can_append && have_result && start_rownum == last_top)
|
||||
{
|
||||
if (cursor.fetch()) //EOF
|
||||
if (bottom_rownum == last_bottom)
|
||||
return;
|
||||
/* Extend only a previous frame ending at most 1 row above the top:
|
||||
after an inverted frame ending higher, the rows in between would
|
||||
be added */
|
||||
if (bottom_rownum > last_bottom && last_bottom + 1 >= start_rownum)
|
||||
first_rownum= last_bottom + 1;
|
||||
}
|
||||
|
||||
if (first_rownum == start_rownum)
|
||||
clear_sum_functions();
|
||||
have_result= false;
|
||||
cursor.move_to(first_rownum);
|
||||
|
||||
ha_rows idx= first_rownum;
|
||||
while (idx <= bottom_rownum
|
||||
&& ((idx & 0xFF) || !thd->check_killed(true)))
|
||||
{
|
||||
if (cursor.fetch()) // EOF or read error
|
||||
break;
|
||||
add_value_to_items();
|
||||
idx++;
|
||||
if (cursor.next()) // EOF
|
||||
break;
|
||||
}
|
||||
/* Cache the result only if the scan completed */
|
||||
if (idx > bottom_rownum)
|
||||
{
|
||||
have_result= true;
|
||||
last_top= start_rownum;
|
||||
last_bottom= bottom_rownum;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user