Skip to content

Commit ebce2b5

Browse files
author
Ajo Robert
committed
Bug#24423143 - WRONG RESULTS FOR AGGREGATE QUERY
Optimizer chooses loose index scan (QUICK_GROUP_MIN_MAX_SELECT) for a group by even when there exists a predicate with disjunction. This is due to that either of the below two cases are encountered and not handled properly. 1. A range tree created for index merge scan is not taken into account while checking for the presence of disjuncted conditions in get_best_group_min_max(). 2. A disjunction condition could lead to null tree if an index merge scan is not possible. A NULL tree will cause get_best_group_min_max() to skip many relevent checks like WA2. There are two scenarios here, (a) The WHERE clause is a disjunction of conditions on MIN/MAX column only. => We can use min_max optimization considering all other criteria (eg:SA2) are met. (b) MIN/MAX column is participating in a disjunctive WHERE clause along with other columns. => min_max optimization is not applicable in this scenario. Fix: Skip loose index scan in below scenarios involving disjoint conditions in WHERE clause. 1. When the condition in WHERE clause results in more than one disjoint range trees (when index merge scan is possible). 2. The range tree is null and MIN/MAX column participates in the WHERE clause along with other columns. [There is no range tree if WHERE condition can't be represented in a single range tree and index merge is not possible] Change-Id: I2ec8ef815d9dfa64e0bf60e97d3b6129c070e24b
1 parent 9204f92 commit ebce2b5

File tree

4 files changed

+642
-2
lines changed

4 files changed

+642
-2
lines changed

mysql-test/r/group_min_max.result

Lines changed: 277 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3794,3 +3794,280 @@ Handler_read_rnd 0
37943794
Handler_read_rnd_next 0
37953795
DROP TABLE t1;
37963796
# End of test for Bug#18109609
3797+
#
3798+
# Bug#24423143 - WRONG RESULTS FOR AGGREGATE QUERY
3799+
#
3800+
# Test index merge tree scenario
3801+
CREATE TABLE A (
3802+
aggr_col int,
3803+
group_by_col int,
3804+
KEY aggr_col_key (aggr_col),
3805+
KEY group_by_col_key (group_by_col, aggr_col)
3806+
) ENGINE=InnoDB;
3807+
set optimizer_trace_max_mem_size=1048576;
3808+
set @@session.optimizer_trace='enabled=on';
3809+
set end_markers_in_json=on;
3810+
INSERT INTO A VALUES (2,3),(5,6),(6,3),(7,NULL),(9,NULL),(10,6);
3811+
ANALYZE TABLE A;
3812+
Table Op Msg_type Msg_text
3813+
test.A analyze status OK
3814+
SELECT group_by_col, MIN(aggr_col) FROM A
3815+
WHERE (group_by_col IN (70, 9)) OR (aggr_col > 2) GROUP BY group_by_col;
3816+
group_by_col MIN(aggr_col)
3817+
NULL 7
3818+
3 6
3819+
6 5
3820+
EXPLAIN SELECT group_by_col, MIN(aggr_col) FROM A
3821+
WHERE (group_by_col IN (70 ,9)) OR (aggr_col > 2) GROUP BY group_by_col;
3822+
id select_type table partitions type possible_keys key key_len ref rows filtered Extra
3823+
1 SIMPLE A NULL index aggr_col_key,group_by_col_key group_by_col_key 10 NULL 6 55.55 Using where; Using index
3824+
Warnings:
3825+
Note 1003 /* select#1 */ select `test`.`A`.`group_by_col` AS `group_by_col`,min(`test`.`A`.`aggr_col`) AS `MIN(aggr_col)` from `test`.`A` where ((`test`.`A`.`group_by_col` in (70,9)) or (`test`.`A`.`aggr_col` > 2)) group by `test`.`A`.`group_by_col`
3826+
SELECT TRACE RLIKE 'disjuntive_predicate_present' AS OK
3827+
FROM INFORMATION_SCHEMA.OPTIMIZER_TRACE;
3828+
OK
3829+
1
3830+
SELECT group_by_col, MAX(aggr_col) FROM A
3831+
WHERE (group_by_col IN (70, 9)) OR (aggr_col < 9) GROUP BY group_by_col;
3832+
group_by_col MAX(aggr_col)
3833+
NULL 7
3834+
3 6
3835+
6 5
3836+
EXPLAIN SELECT group_by_col, MAX(aggr_col) FROM A
3837+
WHERE (group_by_col IN (70 , 9)) OR (aggr_col < 9) GROUP BY group_by_col;
3838+
id select_type table partitions type possible_keys key key_len ref rows filtered Extra
3839+
1 SIMPLE A NULL index aggr_col_key,group_by_col_key group_by_col_key 10 NULL 6 55.55 Using where; Using index
3840+
Warnings:
3841+
Note 1003 /* select#1 */ select `test`.`A`.`group_by_col` AS `group_by_col`,max(`test`.`A`.`aggr_col`) AS `MAX(aggr_col)` from `test`.`A` where ((`test`.`A`.`group_by_col` in (70,9)) or (`test`.`A`.`aggr_col` < 9)) group by `test`.`A`.`group_by_col`
3842+
SELECT TRACE RLIKE 'disjuntive_predicate_present' AS OK
3843+
FROM INFORMATION_SCHEMA.OPTIMIZER_TRACE;
3844+
OK
3845+
1
3846+
# Test IMPOSSIBLE TREE scenario
3847+
ALTER TABLE A DROP KEY aggr_col_key;
3848+
SELECT group_by_col, MIN(aggr_col) FROM A
3849+
WHERE (group_by_col IN (70 ,9)) OR (aggr_col > 2) GROUP BY group_by_col;
3850+
group_by_col MIN(aggr_col)
3851+
NULL 7
3852+
3 6
3853+
6 5
3854+
EXPLAIN SELECT group_by_col, MIN(aggr_col) FROM A
3855+
WHERE (group_by_col IN (70, 9)) OR (aggr_col > 2) GROUP BY group_by_col;
3856+
id select_type table partitions type possible_keys key key_len ref rows filtered Extra
3857+
1 SIMPLE A NULL index group_by_col_key group_by_col_key 10 NULL 6 55.55 Using where; Using index
3858+
Warnings:
3859+
Note 1003 /* select#1 */ select `test`.`A`.`group_by_col` AS `group_by_col`,min(`test`.`A`.`aggr_col`) AS `MIN(aggr_col)` from `test`.`A` where ((`test`.`A`.`group_by_col` in (70,9)) or (`test`.`A`.`aggr_col` > 2)) group by `test`.`A`.`group_by_col`
3860+
SELECT TRACE RLIKE 'minmax_keypart_in_disjunctive_query' AS OK
3861+
FROM INFORMATION_SCHEMA.OPTIMIZER_TRACE;
3862+
OK
3863+
1
3864+
SELECT group_by_col, MAX(aggr_col) FROM A
3865+
WHERE (group_by_col IN (70, 9)) OR (aggr_col < 9) GROUP BY group_by_col;
3866+
group_by_col MAX(aggr_col)
3867+
NULL 7
3868+
3 6
3869+
6 5
3870+
EXPLAIN SELECT group_by_col, MAX(aggr_col) FROM A
3871+
WHERE (group_by_col IN (70, 9)) OR (aggr_col < 9) GROUP BY group_by_col;
3872+
id select_type table partitions type possible_keys key key_len ref rows filtered Extra
3873+
1 SIMPLE A NULL index group_by_col_key group_by_col_key 10 NULL 6 55.55 Using where; Using index
3874+
Warnings:
3875+
Note 1003 /* select#1 */ select `test`.`A`.`group_by_col` AS `group_by_col`,max(`test`.`A`.`aggr_col`) AS `MAX(aggr_col)` from `test`.`A` where ((`test`.`A`.`group_by_col` in (70,9)) or (`test`.`A`.`aggr_col` < 9)) group by `test`.`A`.`group_by_col`
3876+
SELECT TRACE RLIKE 'minmax_keypart_in_disjunctive_query' AS OK
3877+
FROM INFORMATION_SCHEMA.OPTIMIZER_TRACE;
3878+
OK
3879+
1
3880+
# Scenario 3: aggregate field used as equal expression.
3881+
SELECT group_by_col, MIN(aggr_col), MAX(aggr_col) FROM A
3882+
WHERE (group_by_col IN (3, 9)) OR (aggr_col = 9) GROUP BY group_by_col;
3883+
group_by_col MIN(aggr_col) MAX(aggr_col)
3884+
NULL 9 9
3885+
3 2 6
3886+
EXPLAIN SELECT group_by_col, MIN(aggr_col), MAX(aggr_col) FROM A
3887+
WHERE (group_by_col IN (3, 9)) OR (aggr_col = 9) GROUP BY group_by_col;
3888+
id select_type table partitions type possible_keys key key_len ref rows filtered Extra
3889+
1 SIMPLE A NULL index group_by_col_key group_by_col_key 10 NULL 6 44.44 Using where; Using index
3890+
Warnings:
3891+
Note 1003 /* select#1 */ select `test`.`A`.`group_by_col` AS `group_by_col`,min(`test`.`A`.`aggr_col`) AS `MIN(aggr_col)`,max(`test`.`A`.`aggr_col`) AS `MAX(aggr_col)` from `test`.`A` where ((`test`.`A`.`group_by_col` in (3,9)) or (`test`.`A`.`aggr_col` = 9)) group by `test`.`A`.`group_by_col`
3892+
SELECT TRACE RLIKE 'minmax_keypart_in_disjunctive_query' AS OK
3893+
FROM INFORMATION_SCHEMA.OPTIMIZER_TRACE;
3894+
OK
3895+
1
3896+
# Scenario 4: non aggregate field used as equal expression.
3897+
SELECT group_by_col, MIN(aggr_col), MAX(aggr_col) FROM A
3898+
WHERE (group_by_col = 3) OR (aggr_col > 8) GROUP BY group_by_col;
3899+
group_by_col MIN(aggr_col) MAX(aggr_col)
3900+
NULL 9 9
3901+
3 2 6
3902+
6 10 10
3903+
EXPLAIN SELECT group_by_col, MIN(aggr_col), MAX(aggr_col) FROM A
3904+
WHERE (group_by_col = 3) OR (aggr_col > 8) GROUP BY group_by_col;
3905+
id select_type table partitions type possible_keys key key_len ref rows filtered Extra
3906+
1 SIMPLE A NULL index group_by_col_key group_by_col_key 10 NULL 6 55.55 Using where; Using index
3907+
Warnings:
3908+
Note 1003 /* select#1 */ select `test`.`A`.`group_by_col` AS `group_by_col`,min(`test`.`A`.`aggr_col`) AS `MIN(aggr_col)`,max(`test`.`A`.`aggr_col`) AS `MAX(aggr_col)` from `test`.`A` where ((`test`.`A`.`group_by_col` = 3) or (`test`.`A`.`aggr_col` > 8)) group by `test`.`A`.`group_by_col`
3909+
SELECT TRACE RLIKE 'minmax_keypart_in_disjunctive_query' AS OK
3910+
FROM INFORMATION_SCHEMA.OPTIMIZER_TRACE;
3911+
OK
3912+
1
3913+
# Scenario 5: aggregate field used as non-zero expression.
3914+
INSERT INTO A VALUES(0, 3);
3915+
INSERT INTO A VALUES(0, 9);
3916+
INSERT INTO A VALUES(8, 0);
3917+
ANALYZE TABLE A;
3918+
Table Op Msg_type Msg_text
3919+
test.A analyze status OK
3920+
SELECT group_by_col, MIN(aggr_col), MAX(aggr_col) FROM A
3921+
WHERE (group_by_col = 9) OR aggr_col GROUP BY group_by_col;
3922+
group_by_col MIN(aggr_col) MAX(aggr_col)
3923+
NULL 7 9
3924+
0 8 8
3925+
3 2 6
3926+
6 5 10
3927+
9 0 0
3928+
EXPLAIN SELECT group_by_col, MIN(aggr_col), MAX(aggr_col) FROM A
3929+
WHERE group_by_col = 9 OR aggr_col GROUP BY group_by_col;
3930+
id select_type table partitions type possible_keys key key_len ref rows filtered Extra
3931+
1 SIMPLE A NULL index group_by_col_key group_by_col_key 10 NULL 9 91.11 Using where; Using index
3932+
Warnings:
3933+
Note 1003 /* select#1 */ select `test`.`A`.`group_by_col` AS `group_by_col`,min(`test`.`A`.`aggr_col`) AS `MIN(aggr_col)`,max(`test`.`A`.`aggr_col`) AS `MAX(aggr_col)` from `test`.`A` where ((`test`.`A`.`group_by_col` = 9) or `test`.`A`.`aggr_col`) group by `test`.`A`.`group_by_col`
3934+
SELECT TRACE RLIKE 'minmax_keypart_in_disjunctive_query' AS OK
3935+
FROM INFORMATION_SCHEMA.OPTIMIZER_TRACE;
3936+
OK
3937+
1
3938+
# Scenario 6: non aggregate field used as non-zero expression.
3939+
SELECT group_by_col, MIN(aggr_col), MAX(aggr_col) FROM A
3940+
WHERE group_by_col OR (aggr_col < 9) GROUP BY group_by_col;
3941+
group_by_col MIN(aggr_col) MAX(aggr_col)
3942+
NULL 7 7
3943+
0 8 8
3944+
3 0 6
3945+
6 5 10
3946+
9 0 0
3947+
EXPLAIN SELECT group_by_col, MIN(aggr_col), MAX(aggr_col) FROM A
3948+
WHERE group_by_col OR (aggr_col < 9) GROUP BY group_by_col;
3949+
id select_type table partitions type possible_keys key key_len ref rows filtered Extra
3950+
1 SIMPLE A NULL index group_by_col_key group_by_col_key 10 NULL 9 92.59 Using where; Using index
3951+
Warnings:
3952+
Note 1003 /* select#1 */ select `test`.`A`.`group_by_col` AS `group_by_col`,min(`test`.`A`.`aggr_col`) AS `MIN(aggr_col)`,max(`test`.`A`.`aggr_col`) AS `MAX(aggr_col)` from `test`.`A` where (`test`.`A`.`group_by_col` or (`test`.`A`.`aggr_col` < 9)) group by `test`.`A`.`group_by_col`
3953+
SELECT TRACE RLIKE 'minmax_keypart_in_disjunctive_query' AS OK
3954+
FROM INFORMATION_SCHEMA.OPTIMIZER_TRACE;
3955+
OK
3956+
1
3957+
# Scenario 7: aggregate field used in equal exp without a CONST
3958+
INSERT INTO A VALUES(1,1),(1,2),(2,1);
3959+
SELECT group_by_col, MIN(aggr_col), MAX(aggr_col) FROM A
3960+
WHERE aggr_col = group_by_col GROUP BY group_by_col;
3961+
group_by_col MIN(aggr_col) MAX(aggr_col)
3962+
1 1 1
3963+
EXPLAIN SELECT group_by_col, MIN(aggr_col), MAX(aggr_col) FROM A
3964+
WHERE aggr_col = group_by_col GROUP BY group_by_col;
3965+
id select_type table partitions type possible_keys key key_len ref rows filtered Extra
3966+
1 SIMPLE A NULL index group_by_col_key group_by_col_key 10 NULL 12 100.00 Using where; Using index
3967+
Warnings:
3968+
Note 1003 /* select#1 */ select `test`.`A`.`group_by_col` AS `group_by_col`,min(`test`.`A`.`aggr_col`) AS `MIN(aggr_col)`,max(`test`.`A`.`aggr_col`) AS `MAX(aggr_col)` from `test`.`A` where (`test`.`A`.`group_by_col` = `test`.`A`.`aggr_col`) group by `test`.`A`.`group_by_col`
3969+
SELECT TRACE RLIKE 'minmax_keypart_in_disjunctive_query' AS OK
3970+
FROM INFORMATION_SCHEMA.OPTIMIZER_TRACE;
3971+
OK
3972+
1
3973+
# Scenario 8: aggregate field used in a non-eq exp without a CONST
3974+
SELECT group_by_col, MIN(aggr_col), MAX(aggr_col) FROM A
3975+
WHERE aggr_col < group_by_col GROUP BY group_by_col;
3976+
group_by_col MIN(aggr_col) MAX(aggr_col)
3977+
2 1 1
3978+
3 0 2
3979+
6 5 5
3980+
9 0 0
3981+
EXPLAIN SELECT group_by_col, MIN(aggr_col), MAX(aggr_col) FROM A
3982+
WHERE aggr_col < group_by_col GROUP BY group_by_col;
3983+
id select_type table partitions type possible_keys key key_len ref rows filtered Extra
3984+
1 SIMPLE A NULL index group_by_col_key group_by_col_key 10 NULL 12 33.33 Using where; Using index
3985+
Warnings:
3986+
Note 1003 /* select#1 */ select `test`.`A`.`group_by_col` AS `group_by_col`,min(`test`.`A`.`aggr_col`) AS `MIN(aggr_col)`,max(`test`.`A`.`aggr_col`) AS `MAX(aggr_col)` from `test`.`A` where (`test`.`A`.`aggr_col` < `test`.`A`.`group_by_col`) group by `test`.`A`.`group_by_col`
3987+
SELECT TRACE RLIKE 'minmax_keypart_in_disjunctive_query' AS OK
3988+
FROM INFORMATION_SCHEMA.OPTIMIZER_TRACE;
3989+
OK
3990+
1
3991+
# Scenario 8
3992+
INSERT INTO A VALUES(0,1),(1,0),(0,0);
3993+
SELECT group_by_col, MIN(aggr_col), MAX(aggr_col) FROM A
3994+
WHERE aggr_col OR group_by_col GROUP BY group_by_col;
3995+
group_by_col MIN(aggr_col) MAX(aggr_col)
3996+
NULL 7 9
3997+
0 1 8
3998+
1 0 2
3999+
2 1 1
4000+
3 0 6
4001+
6 5 10
4002+
9 0 0
4003+
EXPLAIN SELECT group_by_col, MIN(aggr_col), MAX(aggr_col) FROM A
4004+
WHERE aggr_col OR group_by_col GROUP BY group_by_col;
4005+
id select_type table partitions type possible_keys key key_len ref rows filtered Extra
4006+
1 SIMPLE A NULL index group_by_col_key group_by_col_key 10 NULL 15 99.00 Using where; Using index
4007+
Warnings:
4008+
Note 1003 /* select#1 */ select `test`.`A`.`group_by_col` AS `group_by_col`,min(`test`.`A`.`aggr_col`) AS `MIN(aggr_col)`,max(`test`.`A`.`aggr_col`) AS `MAX(aggr_col)` from `test`.`A` where (`test`.`A`.`aggr_col` or `test`.`A`.`group_by_col`) group by `test`.`A`.`group_by_col`
4009+
SELECT TRACE RLIKE 'minmax_keypart_in_disjunctive_query' AS OK
4010+
FROM INFORMATION_SCHEMA.OPTIMIZER_TRACE;
4011+
OK
4012+
1
4013+
# Scenario 9
4014+
SELECT group_by_col, MIN(aggr_col), MAX(aggr_col) FROM A
4015+
WHERE aggr_col AND group_by_col GROUP BY group_by_col;
4016+
group_by_col MIN(aggr_col) MAX(aggr_col)
4017+
1 1 2
4018+
2 1 1
4019+
3 2 6
4020+
6 5 10
4021+
EXPLAIN SELECT group_by_col, MIN(aggr_col), MAX(aggr_col) FROM A
4022+
WHERE aggr_col AND group_by_col GROUP BY group_by_col;
4023+
id select_type table partitions type possible_keys key key_len ref rows filtered Extra
4024+
1 SIMPLE A NULL index group_by_col_key group_by_col_key 10 NULL 15 81.00 Using where; Using index
4025+
Warnings:
4026+
Note 1003 /* select#1 */ select `test`.`A`.`group_by_col` AS `group_by_col`,min(`test`.`A`.`aggr_col`) AS `MIN(aggr_col)`,max(`test`.`A`.`aggr_col`) AS `MAX(aggr_col)` from `test`.`A` where (`test`.`A`.`aggr_col` and `test`.`A`.`group_by_col`) group by `test`.`A`.`group_by_col`
4027+
SELECT TRACE RLIKE 'minmax_keypart_in_disjunctive_query' AS OK
4028+
FROM INFORMATION_SCHEMA.OPTIMIZER_TRACE;
4029+
OK
4030+
1
4031+
# Scenario 10: Added for completion. This fix does not have an impact.
4032+
SELECT group_by_col, MIN(aggr_col), MAX(aggr_col) FROM A
4033+
WHERE aggr_col<>0 AND group_by_col<>0 GROUP BY group_by_col;
4034+
group_by_col MIN(aggr_col) MAX(aggr_col)
4035+
1 1 2
4036+
2 1 1
4037+
3 2 6
4038+
6 5 10
4039+
EXPLAIN SELECT group_by_col, MIN(aggr_col), MAX(aggr_col) FROM A
4040+
WHERE aggr_col<>0 AND group_by_col<>0 GROUP BY group_by_col;
4041+
id select_type table partitions type possible_keys key key_len ref rows filtered Extra
4042+
1 SIMPLE A NULL range group_by_col_key group_by_col_key 10 NULL 7 100.00 Using where; Using index for group-by
4043+
Warnings:
4044+
Note 1003 /* select#1 */ select `test`.`A`.`group_by_col` AS `group_by_col`,min(`test`.`A`.`aggr_col`) AS `MIN(aggr_col)`,max(`test`.`A`.`aggr_col`) AS `MAX(aggr_col)` from `test`.`A` where ((`test`.`A`.`aggr_col` <> 0) and (`test`.`A`.`group_by_col` <> 0)) group by `test`.`A`.`group_by_col`
4045+
SELECT TRACE RLIKE 'minmax_keypart_in_disjunctive_query' AS OK
4046+
FROM INFORMATION_SCHEMA.OPTIMIZER_TRACE;
4047+
OK
4048+
0
4049+
# Scenario 11: ITEM_FUNC as an argument of ITEM_FUNC
4050+
SELECT group_by_col, MIN(aggr_col), MAX(aggr_col) FROM A
4051+
WHERE group_by_col OR (group_by_col < (aggr_col = 1)) GROUP BY group_by_col;
4052+
group_by_col MIN(aggr_col) MAX(aggr_col)
4053+
0 1 1
4054+
1 0 2
4055+
2 1 1
4056+
3 0 6
4057+
6 5 10
4058+
9 0 0
4059+
EXPLAIN SELECT group_by_col, MIN(aggr_col), MAX(aggr_col) FROM A
4060+
WHERE group_by_col OR (group_by_col < (aggr_col = 1)) GROUP BY group_by_col;
4061+
id select_type table partitions type possible_keys key key_len ref rows filtered Extra
4062+
1 SIMPLE A NULL index group_by_col_key group_by_col_key 10 NULL 15 93.33 Using where; Using index
4063+
Warnings:
4064+
Note 1003 /* select#1 */ select `test`.`A`.`group_by_col` AS `group_by_col`,min(`test`.`A`.`aggr_col`) AS `MIN(aggr_col)`,max(`test`.`A`.`aggr_col`) AS `MAX(aggr_col)` from `test`.`A` where (`test`.`A`.`group_by_col` or (`test`.`A`.`group_by_col` < (`test`.`A`.`aggr_col` = 1))) group by `test`.`A`.`group_by_col`
4065+
SELECT TRACE RLIKE 'minmax_keypart_in_disjunctive_query' AS OK
4066+
FROM INFORMATION_SCHEMA.OPTIMIZER_TRACE;
4067+
OK
4068+
1
4069+
SET optimizer_trace_max_mem_size=DEFAULT;
4070+
SET optimizer_trace=DEFAULT;
4071+
SET end_markers_in_json=DEFAULT;
4072+
DROP TABLE A;
4073+
# End of test for Bug#24423143

mysql-test/r/group_min_max_innodb.result

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -338,7 +338,7 @@ F 30 30
338338
SELECT TRACE RLIKE 'minmax_keypart_in_disjunctive_query'
339339
AS OK FROM INFORMATION_SCHEMA.OPTIMIZER_TRACE;
340340
OK
341-
0
341+
1
342342
SET optimizer_trace_max_mem_size=DEFAULT;
343343
SET optimizer_trace=DEFAULT;
344344
SET end_markers_in_json=DEFAULT;

0 commit comments

Comments
 (0)