Skip to content

Commit 8265d6d

Browse files
committed
MDEV-22846 Server crashes in handler_index_cond_check on SELECT
If the optimizer decides to rewrites a NOT IN predicand of the form outer_expr IN (SELECT inner_col FROM ... WHERE subquery_where) into the EXISTS subquery EXISTS (SELECT 1 FROM ... WHERE subquery_where AND (outer_expr=inner_col OR inner_col IS NULL)) then the pushed equality predicate outer_expr=inner_col can be used for ref[or_null] access if inner_col is a reference to an indexed column. In this case if there is a selective range condition over this column then a Rowid filter may be employed coupled the with ref[or_null] access. The filter is 'pushed' into the engine and in InnoDB currently it cannot be used with index look-ups by primary key. The ref[or_null] access can be used only when outer_expr is not NULL. Otherwise the original predicand is evaluated to TRUE only if the result set returned by the query SELECT 1 FROM ... WHERE subquery_where is empty. When performing this evaluation the executor switches to the table scan by primary key. Before this patch the pushed filter still remained marked as active and the engine tried to apply the filter. This was incorrect and in InnoDB this attempt to use the filter led to an assertion failure. This patch fixes the problem by disabling usage of the filter when outer_expr is evaluated to NULL.
1 parent c18896f commit 8265d6d

File tree

4 files changed

+102
-0
lines changed

4 files changed

+102
-0
lines changed

mysql-test/main/rowid_filter_innodb.result

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2919,3 +2919,45 @@ set optimizer_switch=@save_optimizer_switch;
29192919
set join_cache_level=@save_join_cache_level;
29202920
drop table filt, acei, acli;
29212921
set global innodb_stats_persistent= @stats.save;
2922+
#
2923+
# MDEV-22846: ref access with full scan on keys with NULLs + rowid_filter
2924+
#
2925+
CREATE TABLE t1 (pk int NOT NULL, c1 varchar(1)) engine=innodb;
2926+
INSERT INTO t1 VALUES
2927+
(1,NULL),(15,'o'),(16,'x'),(19,'t'),(35,'k'),(36,'h'),(42,'t'),(43,'h'),
2928+
(53,'l'),(62,'a'),(71,NULL),(79,'u'),(128,'y'),(129,NULL),(133,NULL);
2929+
CREATE TABLE t2 (
2930+
i1 int, c1 varchar(1) NOT NULL, KEY c1 (c1), KEY i1 (i1)
2931+
) engine=innodb;
2932+
INSERT INTO t2 VALUES
2933+
(1,'1'),(NULL,'1'),(42,'t'),(NULL,'1'),(79,'u'),(NULL,'1'),
2934+
(NULL,'4'),(NULL,'4'),(NULL,'1'),(NULL,'u'),(2,'1'),(NULL,'w');
2935+
INSERT INTO t2 SELECT * FROM t2;
2936+
SELECT * FROM t1
2937+
WHERE t1.c1 NOT IN (SELECT t2.c1 FROM t2, t1 AS a1
2938+
WHERE t2.i1 = t1.pk AND t2.i1 IS NOT NULL);
2939+
pk c1
2940+
15 o
2941+
16 x
2942+
19 t
2943+
35 k
2944+
36 h
2945+
43 h
2946+
53 l
2947+
62 a
2948+
71 NULL
2949+
128 y
2950+
129 NULL
2951+
133 NULL
2952+
EXPLAIN EXTENDED SELECT * FROM t1
2953+
WHERE t1.c1 NOT IN (SELECT t2.c1 FROM t2, t1 AS a1
2954+
WHERE t2.i1 = t1.pk AND t2.i1 IS NOT NULL);
2955+
id select_type table type possible_keys key key_len ref rows filtered Extra
2956+
1 PRIMARY t1 ALL NULL NULL NULL NULL 15 100.00 Using where
2957+
2 DEPENDENT SUBQUERY t2 ref|filter c1,i1 c1|i1 3|5 func 6 (33%) 33.33 Using where; Full scan on NULL key; Using rowid filter
2958+
2 DEPENDENT SUBQUERY a1 ALL NULL NULL NULL NULL 15 100.00 Using join buffer (flat, BNL join)
2959+
Warnings:
2960+
Note 1276 Field or reference 'test.t1.pk' of SELECT #2 was resolved in SELECT #1
2961+
Note 1003 /* select#1 */ select `test`.`t1`.`pk` AS `pk`,`test`.`t1`.`c1` AS `c1` from `test`.`t1` where !<expr_cache><`test`.`t1`.`c1`,`test`.`t1`.`pk`>(<in_optimizer>(`test`.`t1`.`c1`,<exists>(/* select#2 */ select `test`.`t2`.`c1` from `test`.`t2` join `test`.`t1` `a1` where `test`.`t2`.`i1` = `test`.`t1`.`pk` and `test`.`t2`.`i1` is not null and trigcond(<cache>(`test`.`t1`.`c1`) = `test`.`t2`.`c1`))))
2962+
DROP TABLE t1,t2;
2963+
# End of 10.4 tests

mysql-test/main/rowid_filter_innodb.test

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -534,3 +534,33 @@ set join_cache_level=@save_join_cache_level;
534534
drop table filt, acei, acli;
535535

536536
set global innodb_stats_persistent= @stats.save;
537+
538+
--echo #
539+
--echo # MDEV-22846: ref access with full scan on keys with NULLs + rowid_filter
540+
--echo #
541+
542+
543+
CREATE TABLE t1 (pk int NOT NULL, c1 varchar(1)) engine=innodb;
544+
INSERT INTO t1 VALUES
545+
(1,NULL),(15,'o'),(16,'x'),(19,'t'),(35,'k'),(36,'h'),(42,'t'),(43,'h'),
546+
(53,'l'),(62,'a'),(71,NULL),(79,'u'),(128,'y'),(129,NULL),(133,NULL);
547+
548+
CREATE TABLE t2 (
549+
i1 int, c1 varchar(1) NOT NULL, KEY c1 (c1), KEY i1 (i1)
550+
) engine=innodb;
551+
INSERT INTO t2 VALUES
552+
(1,'1'),(NULL,'1'),(42,'t'),(NULL,'1'),(79,'u'),(NULL,'1'),
553+
(NULL,'4'),(NULL,'4'),(NULL,'1'),(NULL,'u'),(2,'1'),(NULL,'w');
554+
INSERT INTO t2 SELECT * FROM t2;
555+
556+
let $q=
557+
SELECT * FROM t1
558+
WHERE t1.c1 NOT IN (SELECT t2.c1 FROM t2, t1 AS a1
559+
WHERE t2.i1 = t1.pk AND t2.i1 IS NOT NULL);
560+
561+
eval $q;
562+
eval EXPLAIN EXTENDED $q;
563+
564+
DROP TABLE t1,t2;
565+
566+
--echo # End of 10.4 tests

sql/handler.h

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3147,6 +3147,9 @@ class handler :public Sql_alloc
31473147
Rowid_filter *pushed_rowid_filter;
31483148
/* true when the pushed rowid filter has been already filled */
31493149
bool rowid_filter_is_active;
3150+
/* Used for disabling/enabling pushed_rowid_filter */
3151+
Rowid_filter *save_pushed_rowid_filter;
3152+
bool save_rowid_filter_is_active;
31503153

31513154
Discrete_interval auto_inc_interval_for_cur_row;
31523155
/**
@@ -3214,6 +3217,8 @@ class handler :public Sql_alloc
32143217
pushed_idx_cond_keyno(MAX_KEY),
32153218
pushed_rowid_filter(NULL),
32163219
rowid_filter_is_active(0),
3220+
save_pushed_rowid_filter(NULL),
3221+
save_rowid_filter_is_active(false),
32173222
auto_inc_intervals_count(0),
32183223
m_psi(NULL), set_top_table_fields(FALSE), top_table(0),
32193224
top_table_field(0), top_table_fields(0),
@@ -4258,6 +4263,27 @@ class handler :public Sql_alloc
42584263
rowid_filter_is_active= false;
42594264
}
42604265

4266+
virtual void disable_pushed_rowid_filter()
4267+
{
4268+
DBUG_ASSERT(pushed_rowid_filter != NULL &&
4269+
save_pushed_rowid_filter == NULL);
4270+
save_pushed_rowid_filter= pushed_rowid_filter;
4271+
if (rowid_filter_is_active)
4272+
save_rowid_filter_is_active= rowid_filter_is_active;
4273+
pushed_rowid_filter= NULL;
4274+
rowid_filter_is_active= false;
4275+
}
4276+
4277+
virtual void enable_pushed_rowid_filter()
4278+
{
4279+
DBUG_ASSERT(save_pushed_rowid_filter != NULL &&
4280+
pushed_rowid_filter == NULL);
4281+
pushed_rowid_filter= save_pushed_rowid_filter;
4282+
if (save_rowid_filter_is_active)
4283+
rowid_filter_is_active= true;
4284+
save_pushed_rowid_filter= NULL;
4285+
}
4286+
42614287
virtual bool rowid_filter_push(Rowid_filter *rowid_filter) { return true; }
42624288

42634289
/* Needed for partition / spider */

sql/item_subselect.cc

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4026,6 +4026,8 @@ int subselect_single_select_engine::exec()
40264026
tab->save_read_record= tab->read_record.read_record_func;
40274027
tab->read_record.read_record_func= rr_sequential;
40284028
tab->read_first_record= read_first_record_seq;
4029+
if (tab->rowid_filter)
4030+
tab->table->file->disable_pushed_rowid_filter();
40294031
tab->read_record.thd= join->thd;
40304032
tab->read_record.ref_length= tab->table->file->ref_length;
40314033
tab->read_record.unlock_row= rr_unlock_row;
@@ -4046,6 +4048,8 @@ int subselect_single_select_engine::exec()
40464048
tab->read_record.ref_length= 0;
40474049
tab->read_first_record= tab->save_read_first_record;
40484050
tab->read_record.read_record_func= tab->save_read_record;
4051+
if (tab->rowid_filter)
4052+
tab->table->file->enable_pushed_rowid_filter();
40494053
}
40504054
executed= 1;
40514055
if (!(uncacheable() & ~UNCACHEABLE_EXPLAIN) &&

0 commit comments

Comments
 (0)