MDEV-25008: UPDATE/DELETE: Cost-based choice IN->EXISTS vs Materialization
Single-table UPDATE/DELETE didn't provide outer_lookup_keys value for subqueries. This didn't allow to make a meaningful choice between IN->EXISTS and Materialization strategies for subqueries. Fix this: * Make UPDATE/DELETE save Sql_cmd_dml::scanned_rows, * Then, subquery's JOIN::choose_subquery_plan() can fetch it from there for outer_lookup_keys Details: UPDATE/DELETE now calls select_lex->optimize_unflattened_subqueries() twice, like SELECT does (first call optimize_constant_subquries() in JOIN::optimize_inner(), then call optimize_unflattened_subqueries() in JOIN::optimize_stage2()): 1. Call with const_only=true before any optimizations. This allows range optimizer and others to use the values of cheap const subqueries. 2. Call it with const_only=false after range optimizer, partition pruning, etc. outer_lookup_keys value is provided, so it's possible to pick a good subquery strategy. Note: PROTECT_STATEMENT_MEMROOT requires that first SP execution performs subquery optimization for all subqueries, even for degenerate query plans like "Impossible WHERE". Due to that, we ensure that the call to optimize_unflattened_subqueries (with const_only=false) even for degenerate query plans still happens, as was the case before this change.
This commit is contained in:
parent
fd87e01f38
commit
4b6922a315
@ -647,3 +647,36 @@ c2
|
||||
1
|
||||
DROP TABLE t1, t2;
|
||||
End of 11.6 tests
|
||||
#
|
||||
# MDEV-25008: Delete query gets stuck on mariadb, same query works
|
||||
# on MySQL 8.0.21
|
||||
#
|
||||
CREATE TABLE t1 (
|
||||
id int NOT NULL PRIMARY KEY,
|
||||
item_id varchar(100),
|
||||
seller_name varchar(400),
|
||||
variant varchar(400),
|
||||
FULLTEXT KEY t1_serial_IDX (item_id,seller_name,variant)
|
||||
)engine=innodb;
|
||||
insert into t1 select seq,seq,seq,seq from seq_1_to_10000;
|
||||
explain
|
||||
DELETE FROM t1 WHERE id NOT IN
|
||||
(SELECT m FROM (SELECT max(id) m FROM t1 GROUP BY item_id, seller_name, variant) AS innertable);
|
||||
id select_type table type possible_keys key key_len ref rows Extra
|
||||
1 PRIMARY t1 ALL NULL NULL NULL NULL # Using where
|
||||
2 MATERIALIZED <derived3> ALL NULL NULL NULL NULL #
|
||||
3 DERIVED t1 ALL NULL NULL NULL NULL # Using temporary; Using filesort
|
||||
drop table t1;
|
||||
create table t1 (a int primary key, b int, c int, key(b));
|
||||
insert into t1 select seq, seq, seq from seq_1_to_20000;
|
||||
create table t2 as select * from t1;
|
||||
explain delete from t1 where b <= 2 and a not in (select b from t2);
|
||||
id select_type table type possible_keys key key_len ref rows Extra
|
||||
1 PRIMARY t1 range b b 5 NULL 2 Using where
|
||||
2 DEPENDENT SUBQUERY t2 ALL NULL NULL NULL NULL 20000 Using where
|
||||
explain delete from t1 where b <= 3 and a not in (select b from t2);
|
||||
id select_type table type possible_keys key key_len ref rows Extra
|
||||
1 PRIMARY t1 range b b 5 NULL 3 Using where
|
||||
2 MATERIALIZED t2 ALL NULL NULL NULL NULL 20000
|
||||
drop table t1, t2;
|
||||
# End of 11.7 tests
|
||||
|
@ -705,3 +705,40 @@ SELECT * FROM t2;
|
||||
DROP TABLE t1, t2;
|
||||
|
||||
--echo End of 11.6 tests
|
||||
|
||||
--echo #
|
||||
--echo # MDEV-25008: Delete query gets stuck on mariadb, same query works
|
||||
--echo # on MySQL 8.0.21
|
||||
--echo #
|
||||
|
||||
--source include/have_sequence.inc
|
||||
--source include/have_innodb.inc
|
||||
|
||||
# original case
|
||||
CREATE TABLE t1 (
|
||||
id int NOT NULL PRIMARY KEY,
|
||||
item_id varchar(100),
|
||||
seller_name varchar(400),
|
||||
variant varchar(400),
|
||||
FULLTEXT KEY t1_serial_IDX (item_id,seller_name,variant)
|
||||
)engine=innodb;
|
||||
|
||||
insert into t1 select seq,seq,seq,seq from seq_1_to_10000;
|
||||
|
||||
# Masking the `rows` column as the value might vary a bit
|
||||
--replace_column 9 #
|
||||
explain
|
||||
DELETE FROM t1 WHERE id NOT IN
|
||||
(SELECT m FROM (SELECT max(id) m FROM t1 GROUP BY item_id, seller_name, variant) AS innertable);
|
||||
|
||||
drop table t1;
|
||||
|
||||
# example of not full table
|
||||
create table t1 (a int primary key, b int, c int, key(b));
|
||||
insert into t1 select seq, seq, seq from seq_1_to_20000;
|
||||
create table t2 as select * from t1;
|
||||
explain delete from t1 where b <= 2 and a not in (select b from t2);
|
||||
explain delete from t1 where b <= 3 and a not in (select b from t2);
|
||||
drop table t1, t2;
|
||||
|
||||
--echo # End of 11.7 tests
|
||||
|
@ -5968,4 +5968,25 @@ DROP VIEW t1;
|
||||
#
|
||||
# End of 10.4 tests
|
||||
#
|
||||
#
|
||||
# MDEV-25008 Delete query gets stuck on mariadb , same query works on MySQL 8.0.21
|
||||
#
|
||||
create table t1 (a int, b int, primary key (a), key (b));
|
||||
insert into t1 values (1,1),(2,2),(3,3),(4,4),(5,5),(6,6),(7,7),(8,8);
|
||||
CREATE TABLE t2 ( id INT(10) );
|
||||
insert into t2 values (1),(2);
|
||||
set @@optimizer_switch="index_merge=off";
|
||||
set sql_safe_updates=1;
|
||||
set @var1=1;
|
||||
set @var2=2;
|
||||
prepare stmt from 'update t1 set b=(SELECT 1 FROM t2 WHERE id = t1.a) where 1=? or b=2';
|
||||
execute stmt using @var1;
|
||||
ERROR HY000: You are using safe update mode and you tried to update a table without a WHERE that uses a KEY column
|
||||
execute stmt using @var2;
|
||||
delete from t1 where a=1 or b=2;
|
||||
ERROR HY000: You are using safe update mode and you tried to update a table without a WHERE that uses a KEY column
|
||||
drop table t1, t2;
|
||||
#
|
||||
# End of 11.7 tests
|
||||
#
|
||||
ALTER DATABASE test CHARACTER SET utf8mb4 COLLATE utf8mb4_uca1400_ai_ci;
|
||||
|
@ -5451,4 +5451,30 @@ DROP VIEW t1;
|
||||
--echo # End of 10.4 tests
|
||||
--echo #
|
||||
|
||||
--echo #
|
||||
--echo # MDEV-25008 Delete query gets stuck on mariadb , same query works on MySQL 8.0.21
|
||||
--echo #
|
||||
|
||||
create table t1 (a int, b int, primary key (a), key (b));
|
||||
insert into t1 values (1,1),(2,2),(3,3),(4,4),(5,5),(6,6),(7,7),(8,8);
|
||||
CREATE TABLE t2 ( id INT(10) );
|
||||
insert into t2 values (1),(2);
|
||||
|
||||
# calls select_lex->optimize_unflattened_subqueries(false) in the err label
|
||||
set @@optimizer_switch="index_merge=off";
|
||||
set sql_safe_updates=1;
|
||||
set @var1=1;
|
||||
set @var2=2;
|
||||
prepare stmt from 'update t1 set b=(SELECT 1 FROM t2 WHERE id = t1.a) where 1=? or b=2';
|
||||
--error ER_UPDATE_WITHOUT_KEY_IN_SAFE_MODE
|
||||
execute stmt using @var1;
|
||||
execute stmt using @var2;
|
||||
--error ER_UPDATE_WITHOUT_KEY_IN_SAFE_MODE
|
||||
delete from t1 where a=1 or b=2;
|
||||
drop table t1, t2;
|
||||
|
||||
--echo #
|
||||
--echo # End of 11.7 tests
|
||||
--echo #
|
||||
|
||||
--source include/test_db_charset_restore.inc
|
||||
|
@ -146,7 +146,9 @@ DROP PROCEDURE p1;
|
||||
--echo #
|
||||
--echo # MDEV-34757: assertion of (mem_root->flags & 4) == 0 fails in 2nd ps execution with partition pruning
|
||||
--echo #
|
||||
# same as the first MDEV-34444 testcase but with explain
|
||||
# same as the first MDEV-34447 testcase but with explain; calling
|
||||
# select_lex->optimize_unflattened_subqueries(false) under the
|
||||
# emit_explain_and_leave label
|
||||
CREATE TABLE t1 (id INT, value INT);
|
||||
CREATE TABLE t2 (id INT);
|
||||
|
||||
@ -165,7 +167,9 @@ SELECT * FROM t1;
|
||||
DEALLOCATE PREPARE stmt;
|
||||
DROP TABLE t1, t2;
|
||||
|
||||
# 2nd ps mem leak; partition pruning
|
||||
# 2nd ps mem leak; partition pruning; calls
|
||||
# select_lex->optimize_unflattened_subqueries(false) under if
|
||||
# (prune_partitions())...
|
||||
set @var1=5;
|
||||
set @var2=4;
|
||||
create table t1 (a int) partition by list(a) (
|
||||
@ -201,7 +205,9 @@ select * from t1;
|
||||
deallocate prepare stmt;
|
||||
drop table t1, t2;
|
||||
|
||||
# top level impossible where
|
||||
# top level impossible where; calling
|
||||
# select_lex->optimize_unflattened_subqueries(false) in if (... ||
|
||||
# (select && select->check_quick(...)))
|
||||
set @var1=1;
|
||||
set @var2=2;
|
||||
CREATE TABLE t1 ( id INT(10), value INT(10) );
|
||||
@ -215,7 +221,9 @@ execute stmt using @var1, @var1;
|
||||
deallocate prepare stmt;
|
||||
DROP TABLE t1,t2;
|
||||
|
||||
# top level impossible where, with explain
|
||||
# top level impossible where, with explain; also calling
|
||||
# select_lex->optimize_unflattened_subqueries(false) under the
|
||||
# emit_explain_and_leave label
|
||||
set @var1=1;
|
||||
set @var2=2;
|
||||
CREATE TABLE t1 ( id INT(10), value INT(10) );
|
||||
@ -246,7 +254,9 @@ CALL p1(2);
|
||||
DROP TABLE t1;
|
||||
DROP PROCEDURE p1;
|
||||
|
||||
# partition pruning
|
||||
# partition pruning; calling
|
||||
# select_lex->optimize_unflattened_subqueries(false) under if
|
||||
# (prune_partitions())...
|
||||
set @var1=5;
|
||||
set @var2=4;
|
||||
create table t1 (a int) partition by list(a) (
|
||||
@ -282,7 +292,15 @@ select * from t1;
|
||||
deallocate prepare stmt;
|
||||
drop table t1, t2;
|
||||
|
||||
# top level impossible where
|
||||
# top level impossible where;
|
||||
|
||||
# execute stmt using @var1, @var2; calling
|
||||
# select_lex->optimize_unflattened_subqueries(false) under if (...
|
||||
# (select && select->check_quick(...)) ...)
|
||||
#
|
||||
# execute stmt using @var1, @var1; calling
|
||||
# select_lex->optimize_unflattened_subqueries(false)) after jumping to
|
||||
# the cleanup label
|
||||
set @var1=1;
|
||||
set @var2=2;
|
||||
CREATE TABLE t1 ( id INT(10), value INT(10) );
|
||||
@ -296,7 +314,9 @@ execute stmt using @var1, @var1;
|
||||
deallocate prepare stmt;
|
||||
DROP TABLE t1,t2;
|
||||
|
||||
# top level impossible where, with explain
|
||||
# top level impossible where, with explain; calling
|
||||
# select_lex->optimize_unflattened_subqueries(false) under the
|
||||
# send_nothing_and_leave label
|
||||
set @var1=1;
|
||||
set @var2=2;
|
||||
CREATE TABLE t1 ( id INT(10), value INT(10) );
|
||||
|
@ -802,4 +802,37 @@ DROP FUNCTION f1;
|
||||
DROP FUNCTION f2;
|
||||
DROP TABLE t1;
|
||||
# End of MariaDB 10.10 tests
|
||||
#
|
||||
# MDEV-25008: Delete query gets stuck on mariadb, same query works
|
||||
# on MySQL 8.0.21
|
||||
#
|
||||
CREATE TABLE t1 (
|
||||
id int NOT NULL PRIMARY KEY,
|
||||
item_id varchar(100),
|
||||
seller_name varchar(400),
|
||||
variant varchar(400),
|
||||
FULLTEXT KEY t1_serial_IDX (item_id,seller_name,variant)
|
||||
)engine=innodb;
|
||||
insert into t1 select seq,seq,seq,seq from seq_1_to_10000;
|
||||
explain
|
||||
UPDATE t1 SET item_id="foo" WHERE id NOT IN
|
||||
(SELECT m FROM (SELECT max(id) m FROM t1 GROUP BY item_id, seller_name, variant) AS innertable);
|
||||
id select_type table type possible_keys key key_len ref rows Extra
|
||||
1 PRIMARY t1 index NULL PRIMARY 4 NULL # Using where
|
||||
2 MATERIALIZED <derived3> ALL NULL NULL NULL NULL #
|
||||
3 DERIVED t1 ALL NULL NULL NULL NULL # Using temporary; Using filesort
|
||||
drop table t1;
|
||||
create table t1 (a int primary key, b int, c int, key(b));
|
||||
insert into t1 select seq, seq, seq from seq_1_to_20000;
|
||||
create table t2 as select * from t1;
|
||||
explain update t1 set c = 5 where b <= 2 and a not in (select b from t2);
|
||||
id select_type table type possible_keys key key_len ref rows Extra
|
||||
1 PRIMARY t1 range b b 5 NULL 2 Using where
|
||||
2 DEPENDENT SUBQUERY t2 ALL NULL NULL NULL NULL 20000 Using where
|
||||
explain update t1 set c = 5 where b <= 3 and a not in (select b from t2);
|
||||
id select_type table type possible_keys key key_len ref rows Extra
|
||||
1 PRIMARY t1 range b b 5 NULL 3 Using where
|
||||
2 MATERIALIZED t2 ALL NULL NULL NULL NULL 20000
|
||||
drop table t1, t2;
|
||||
# End of 11.7 tests
|
||||
ALTER DATABASE test CHARACTER SET utf8mb4 COLLATE utf8mb4_uca1400_ai_ci;
|
||||
|
@ -750,4 +750,40 @@ DROP TABLE t1;
|
||||
|
||||
--echo # End of MariaDB 10.10 tests
|
||||
|
||||
--echo #
|
||||
--echo # MDEV-25008: Delete query gets stuck on mariadb, same query works
|
||||
--echo # on MySQL 8.0.21
|
||||
--echo #
|
||||
|
||||
--source include/have_sequence.inc
|
||||
|
||||
# original case
|
||||
CREATE TABLE t1 (
|
||||
id int NOT NULL PRIMARY KEY,
|
||||
item_id varchar(100),
|
||||
seller_name varchar(400),
|
||||
variant varchar(400),
|
||||
FULLTEXT KEY t1_serial_IDX (item_id,seller_name,variant)
|
||||
)engine=innodb;
|
||||
|
||||
insert into t1 select seq,seq,seq,seq from seq_1_to_10000;
|
||||
|
||||
# Masking the `rows` column as the value might vary a bit
|
||||
--replace_column 9 #
|
||||
explain
|
||||
UPDATE t1 SET item_id="foo" WHERE id NOT IN
|
||||
(SELECT m FROM (SELECT max(id) m FROM t1 GROUP BY item_id, seller_name, variant) AS innertable);
|
||||
|
||||
drop table t1;
|
||||
|
||||
# example of not full table
|
||||
create table t1 (a int primary key, b int, c int, key(b));
|
||||
insert into t1 select seq, seq, seq from seq_1_to_20000;
|
||||
create table t2 as select * from t1;
|
||||
explain update t1 set c = 5 where b <= 2 and a not in (select b from t2);
|
||||
explain update t1 set c = 5 where b <= 3 and a not in (select b from t2);
|
||||
drop table t1, t2;
|
||||
|
||||
--echo # End of 11.7 tests
|
||||
|
||||
--source include/test_db_charset_restore.inc
|
||||
|
@ -2663,9 +2663,7 @@ static int fill_used_fields_bitmap(PARAM *param)
|
||||
|
||||
In the table struct the following information is updated:
|
||||
quick_keys - Which keys can be used
|
||||
quick_rows - How many rows the key matches
|
||||
opt_range_condition_rows - E(# rows that will satisfy the table
|
||||
condition)
|
||||
opt_range_condition_rows - E(# rows that will satisfy the table condition)
|
||||
|
||||
IMPLEMENTATION
|
||||
opt_range_condition_rows value is obtained as follows:
|
||||
|
@ -6803,15 +6803,16 @@ bool JOIN::choose_subquery_plan(table_map join_tables)
|
||||
&dummy,
|
||||
&outer_lookup_keys);
|
||||
}
|
||||
/*
|
||||
In case of a DELETE or UPDATE, get number of scanned rows as an
|
||||
(upper bound) estimate of how many times the subquery will be
|
||||
executed.
|
||||
*/
|
||||
else if (outer_join && outer_join->sql_cmd_dml)
|
||||
outer_lookup_keys=
|
||||
rows2double(outer_join->sql_cmd_dml->get_scanned_rows());
|
||||
else
|
||||
{
|
||||
/*
|
||||
TODO: outer_join can be NULL for DELETE statements.
|
||||
How to compute its cost?
|
||||
*/
|
||||
outer_lookup_keys= 1;
|
||||
}
|
||||
|
||||
/*
|
||||
B. Estimate the cost and number of records of the subquery both
|
||||
unmodified, and with injected IN->EXISTS predicates.
|
||||
|
@ -8314,7 +8314,7 @@ bool setup_tables(THD *thd, Name_resolution_context *context,
|
||||
0);
|
||||
SELECT_LEX *select_lex= select_insert ? thd->lex->first_select_lex() :
|
||||
thd->lex->current_select;
|
||||
if (select_lex->first_cond_optimization)
|
||||
if (select_lex->first_cond_optimization || !select_lex->leaf_tables_saved)
|
||||
{
|
||||
leaves.empty();
|
||||
if (select_lex->prep_leaf_list_state != SELECT_LEX::SAVED)
|
||||
|
@ -20,6 +20,8 @@
|
||||
#ifndef SQL_CMD_INCLUDED
|
||||
#define SQL_CMD_INCLUDED
|
||||
|
||||
#include "my_base.h"
|
||||
|
||||
/*
|
||||
When a command is added here, be sure it's also added in mysqld.cc
|
||||
in "struct show_var_st status_vars[]= {" ...
|
||||
@ -325,10 +327,12 @@ public:
|
||||
|
||||
select_result *get_result() { return result; }
|
||||
|
||||
ha_rows get_scanned_rows() { return scanned_rows; }
|
||||
|
||||
protected:
|
||||
Sql_cmd_dml()
|
||||
: Sql_cmd(), lex(nullptr), result(nullptr),
|
||||
m_empty_query(false)
|
||||
m_empty_query(false), scanned_rows(0)
|
||||
{}
|
||||
|
||||
/**
|
||||
@ -395,6 +399,7 @@ protected:
|
||||
LEX *lex; /**< Pointer to LEX for this statement */
|
||||
select_result *result; /**< Pointer to object for handling of the result */
|
||||
bool m_empty_query; /**< True if query will produce no rows */
|
||||
ha_rows scanned_rows; /**< Number of scanned rows */
|
||||
};
|
||||
|
||||
|
||||
|
@ -352,6 +352,16 @@ bool Sql_cmd_delete::delete_from_single_table(THD *thd)
|
||||
bool delete_record= false;
|
||||
bool delete_while_scanning= table_list->delete_while_scanning;
|
||||
bool portion_of_time_through_update;
|
||||
/*
|
||||
TRUE if we are after the call to
|
||||
select_lex->optimize_unflattened_subqueries(true) and before the
|
||||
call to select_lex->optimize_unflattened_subqueries(false), to
|
||||
ensure a call to
|
||||
select_lex->optimize_unflattened_subqueries(false) happens which
|
||||
avoid 2nd ps mem leaks when e.g. the first execution produces
|
||||
empty result and the second execution produces a non-empty set
|
||||
*/
|
||||
bool optimize_subqueries= FALSE;
|
||||
|
||||
DBUG_ENTER("Sql_cmd_delete::delete_single_table");
|
||||
|
||||
@ -388,9 +398,18 @@ bool Sql_cmd_delete::delete_from_single_table(THD *thd)
|
||||
|
||||
thd->lex->promote_select_describe_flag_if_needed();
|
||||
|
||||
/* Apply the IN=>EXISTS transformation to all subqueries and optimize them. */
|
||||
if (select_lex->optimize_unflattened_subqueries(false))
|
||||
/*
|
||||
Apply the IN=>EXISTS transformation to all constant subqueries
|
||||
and optimize them.
|
||||
|
||||
It is too early to choose subquery optimization strategies without
|
||||
an estimate of how many times the subquery will be executed so we
|
||||
call optimize_unflattened_subqueries() with const_only= true, and
|
||||
choose between materialization and in-to-exists later.
|
||||
*/
|
||||
if (select_lex->optimize_unflattened_subqueries(true))
|
||||
DBUG_RETURN(TRUE);
|
||||
optimize_subqueries= TRUE;
|
||||
|
||||
const_cond= (!conds || conds->const_item());
|
||||
safe_update= (thd->variables.option_bits & OPTION_SAFE_UPDATES) &&
|
||||
@ -491,6 +510,9 @@ bool Sql_cmd_delete::delete_from_single_table(THD *thd)
|
||||
#ifdef WITH_PARTITION_STORAGE_ENGINE
|
||||
if (prune_partitions(thd, table, conds))
|
||||
{
|
||||
if (optimize_subqueries && select_lex->optimize_unflattened_subqueries(false))
|
||||
DBUG_RETURN(TRUE);
|
||||
optimize_subqueries= FALSE;
|
||||
free_underlaid_joins(thd, select_lex);
|
||||
|
||||
query_plan.set_no_partitions();
|
||||
@ -529,6 +551,9 @@ bool Sql_cmd_delete::delete_from_single_table(THD *thd)
|
||||
goto produce_explain_and_leave;
|
||||
|
||||
delete select;
|
||||
if (select_lex->optimize_unflattened_subqueries(false))
|
||||
DBUG_RETURN(TRUE);
|
||||
optimize_subqueries= FALSE;
|
||||
free_underlaid_joins(thd, select_lex);
|
||||
/*
|
||||
Error was already created by quick select evaluation (check_quick()).
|
||||
@ -559,6 +584,9 @@ bool Sql_cmd_delete::delete_from_single_table(THD *thd)
|
||||
if (safe_update && !using_limit)
|
||||
{
|
||||
delete select;
|
||||
if (optimize_subqueries && select_lex->optimize_unflattened_subqueries(false))
|
||||
DBUG_RETURN(TRUE);
|
||||
optimize_subqueries= FALSE;
|
||||
free_underlaid_joins(thd, select_lex);
|
||||
my_message(ER_UPDATE_WITHOUT_KEY_IN_SAFE_MODE,
|
||||
ER_THD(thd, ER_UPDATE_WITHOUT_KEY_IN_SAFE_MODE), MYF(0));
|
||||
@ -568,7 +596,18 @@ bool Sql_cmd_delete::delete_from_single_table(THD *thd)
|
||||
if (options & OPTION_QUICK)
|
||||
(void) table->file->extra(HA_EXTRA_QUICK);
|
||||
|
||||
query_plan.scanned_rows= select? select->records: table->file->stats.records;
|
||||
/*
|
||||
Estimate the number of scanned rows and have it accessible in
|
||||
JOIN::choose_subquery_plan() from the outer join through
|
||||
JOIN::sql_cmd_dml
|
||||
*/
|
||||
scanned_rows= query_plan.scanned_rows= select ?
|
||||
select->records : table->file->stats.records;
|
||||
select_lex->join->sql_cmd_dml= this;
|
||||
DBUG_ASSERT(optimize_subqueries);
|
||||
if (select_lex->optimize_unflattened_subqueries(false))
|
||||
DBUG_RETURN(TRUE);
|
||||
optimize_subqueries= FALSE;
|
||||
if (order)
|
||||
{
|
||||
table->update_const_key_parts(conds);
|
||||
@ -991,6 +1030,8 @@ cleanup:
|
||||
DBUG_PRINT("info",("%ld records deleted",(long) deleted));
|
||||
}
|
||||
delete file_sort;
|
||||
if (optimize_subqueries && select_lex->optimize_unflattened_subqueries(false))
|
||||
DBUG_RETURN(TRUE);
|
||||
free_underlaid_joins(thd, select_lex);
|
||||
if (table->file->pushed_cond)
|
||||
table->file->cond_pop();
|
||||
@ -1014,6 +1055,9 @@ send_nothing_and_leave:
|
||||
|
||||
delete select;
|
||||
delete file_sort;
|
||||
if (!thd->is_error() && optimize_subqueries &&
|
||||
select_lex->optimize_unflattened_subqueries(false))
|
||||
DBUG_RETURN(TRUE);
|
||||
free_underlaid_joins(thd, select_lex);
|
||||
if (table->file->pushed_cond)
|
||||
table->file->cond_pop();
|
||||
|
@ -1057,6 +1057,7 @@ public:
|
||||
*/
|
||||
List<Item_func_in> in_funcs;
|
||||
List<TABLE_LIST> leaf_tables;
|
||||
/* Saved leaf tables for subsequent executions */
|
||||
List<TABLE_LIST> leaf_tables_exec;
|
||||
List<TABLE_LIST> leaf_tables_prep;
|
||||
|
||||
|
@ -578,6 +578,7 @@ void JOIN::init(THD *thd_arg, List<Item> &fields_arg,
|
||||
is_orig_degenerated= false;
|
||||
with_ties_order_count= 0;
|
||||
prepared= false;
|
||||
sql_cmd_dml= NULL;
|
||||
};
|
||||
|
||||
|
||||
|
@ -33,6 +33,8 @@
|
||||
#include "records.h" /* READ_RECORD */
|
||||
#include "opt_range.h" /* SQL_SELECT, QUICK_SELECT_I */
|
||||
#include "filesort.h"
|
||||
#include "sql_delete.h"
|
||||
#include "sql_update.h"
|
||||
|
||||
#include "cset_narrowing.h"
|
||||
|
||||
@ -1736,6 +1738,13 @@ public:
|
||||
*/
|
||||
bool is_orig_degenerated;
|
||||
|
||||
/*
|
||||
DELETE and UPDATE may have an imitation JOIN, which is not NULL,
|
||||
but has NULL join_tab. In such cases we may want to access
|
||||
sql_cmd_dml::scanned_rows to choose optimization strategies.
|
||||
*/
|
||||
Sql_cmd_dml *sql_cmd_dml;
|
||||
|
||||
JOIN(THD *thd_arg, List<Item> &fields_arg, ulonglong select_options_arg,
|
||||
select_result *result_arg)
|
||||
:fields_list(fields_arg)
|
||||
|
@ -376,6 +376,16 @@ bool Sql_cmd_update::update_single_table(THD *thd)
|
||||
List<Item> all_fields;
|
||||
killed_state killed_status= NOT_KILLED;
|
||||
bool has_triggers, binlog_is_row, do_direct_update= FALSE;
|
||||
/*
|
||||
TRUE if we are after the call to
|
||||
select_lex->optimize_unflattened_subqueries(true) and before the
|
||||
call to select_lex->optimize_unflattened_subqueries(false), to
|
||||
ensure a call to
|
||||
select_lex->optimize_unflattened_subqueries(false) happens which
|
||||
avoid 2nd ps mem leaks when e.g. the first execution produces
|
||||
empty result and the second execution produces a non-empty set
|
||||
*/
|
||||
bool need_to_optimize= FALSE;
|
||||
Update_plan query_plan(thd->mem_root);
|
||||
Explain_update *explain;
|
||||
query_plan.index= MAX_KEY;
|
||||
@ -422,9 +432,18 @@ bool Sql_cmd_update::update_single_table(THD *thd)
|
||||
switch_to_nullable_trigger_fields(*fields, table);
|
||||
switch_to_nullable_trigger_fields(*values, table);
|
||||
|
||||
/* Apply the IN=>EXISTS transformation to all subqueries and optimize them */
|
||||
if (select_lex->optimize_unflattened_subqueries(false))
|
||||
/*
|
||||
Apply the IN=>EXISTS transformation to all constant subqueries
|
||||
and optimize them.
|
||||
|
||||
It is too early to choose subquery optimization strategies without
|
||||
an estimate of how many times the subquery will be executed so we
|
||||
call optimize_unflattened_subqueries() with const_only= true, and
|
||||
choose between materialization and in-to-exists later.
|
||||
*/
|
||||
if (select_lex->optimize_unflattened_subqueries(true))
|
||||
DBUG_RETURN(TRUE);
|
||||
need_to_optimize= TRUE;
|
||||
|
||||
if (conds)
|
||||
{
|
||||
@ -458,6 +477,9 @@ bool Sql_cmd_update::update_single_table(THD *thd)
|
||||
#ifdef WITH_PARTITION_STORAGE_ENGINE
|
||||
if (prune_partitions(thd, table, conds))
|
||||
{
|
||||
if (need_to_optimize && select_lex->optimize_unflattened_subqueries(false))
|
||||
DBUG_RETURN(TRUE);
|
||||
need_to_optimize= FALSE;
|
||||
free_underlaid_joins(thd, select_lex);
|
||||
|
||||
query_plan.set_no_partitions();
|
||||
@ -493,6 +515,9 @@ bool Sql_cmd_update::update_single_table(THD *thd)
|
||||
goto produce_explain_and_leave;
|
||||
|
||||
delete select;
|
||||
if (need_to_optimize && select_lex->optimize_unflattened_subqueries(false))
|
||||
DBUG_RETURN(TRUE);
|
||||
need_to_optimize= FALSE;
|
||||
free_underlaid_joins(thd, select_lex);
|
||||
/*
|
||||
There was an error or the error was already sent by
|
||||
@ -545,8 +570,20 @@ bool Sql_cmd_update::update_single_table(THD *thd)
|
||||
|
||||
table->update_const_key_parts(conds);
|
||||
order= simple_remove_const(order, conds);
|
||||
query_plan.scanned_rows= select? select->records: table->file->stats.records;
|
||||
|
||||
|
||||
/*
|
||||
Estimate the number of scanned rows and have it accessible in
|
||||
JOIN::choose_subquery_plan() from the outer join through
|
||||
JOIN::sql_cmd_dml
|
||||
*/
|
||||
scanned_rows= query_plan.scanned_rows= select ?
|
||||
select->records : table->file->stats.records;
|
||||
select_lex->join->sql_cmd_dml= this;
|
||||
DBUG_ASSERT(need_to_optimize);
|
||||
if (select_lex->optimize_unflattened_subqueries(false))
|
||||
DBUG_RETURN(TRUE);
|
||||
need_to_optimize= FALSE;
|
||||
|
||||
if (select && select->quick && select->quick->unique_key_range())
|
||||
{
|
||||
/* Single row select (always "ordered"): Ok to use with key field UPDATE */
|
||||
@ -1308,6 +1345,9 @@ update_end:
|
||||
err:
|
||||
delete select;
|
||||
delete file_sort;
|
||||
if (!thd->is_error() && need_to_optimize &&
|
||||
select_lex->optimize_unflattened_subqueries(false))
|
||||
DBUG_RETURN(TRUE);
|
||||
free_underlaid_joins(thd, select_lex);
|
||||
table->file->ha_end_keyread();
|
||||
if (table->file->pushed_cond)
|
||||
@ -1328,6 +1368,9 @@ emit_explain_and_leave:
|
||||
int err2= thd->lex->explain->send_explain(thd, extended);
|
||||
|
||||
delete select;
|
||||
if (!thd->is_error() && need_to_optimize &&
|
||||
select_lex->optimize_unflattened_subqueries(false))
|
||||
DBUG_RETURN(TRUE);
|
||||
free_underlaid_joins(thd, select_lex);
|
||||
DBUG_RETURN((err2 || thd->is_error()) ? 1 : 0);
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user