← Back to team overview

maria-developers team mailing list archive

bzr commit into file:///home/tsk/mprog/src/5.3-mwl89/ branch (timour:2801)

 

#At file:///home/tsk/mprog/src/5.3-mwl89/ based on revid:sanja@xxxxxxxxxxxx-20100710103730-ayy6a61pdibspf4o

 2801 timour@xxxxxxxxxxxx	2010-07-16
      MWL#89: Cost-based choice between Materialization and IN->EXISTS transformation
      
      1. Changed the lazy optimization for subqueries that can be
         materialized into bottom-up optimization during the optimization of
         the main query.
      
         The main change is implemented by the method
         Item_in_subselect::setup_engine.
        
         All other changes were required to correct problems resulting from
         changing the order of optimization. Most of these problems followed
         the same pattern - there are some shared structures between a
         subquery and its parent query. Depending on which one is optimized
         first (parent or child query), these shared strucutres may get
         different values, thus resulting in an inconsistent query plan.
      
      2. Changed the code-generation for subquery materialization to be
         performed in runtime memory for each (re)execution, instead of in
         statement memory (once per prepared statement).
         - Item_in_subselect::setup_engine() no longer creates materialization
           related objects in statement memory.
         - Merged subselect_hash_sj_engine::init_permanent and
           subselect_hash_sj_engine::init_runtime into
           subselect_hash_sj_engine::init, which is called for each
           (re)execution.
         - Fixed deletion of the temp table accordingly.
     @ mysql-test/r/subselect_mat.result
        Adjusted changed EXPLAIN because of earlier optimization of subqueries.

    modified:
      mysql-test/r/subselect_mat.result
      sql/item_subselect.cc
      sql/item_subselect.h
      sql/sql_class.cc
      sql/sql_class.h
      sql/sql_select.cc
=== modified file 'mysql-test/r/subselect_mat.result'
--- a/mysql-test/r/subselect_mat.result	2010-06-26 10:05:41 +0000
+++ b/mysql-test/r/subselect_mat.result	2010-07-16 10:52:02 +0000
@@ -1139,7 +1139,7 @@ insert into t1 values (5);
 explain select min(a1) from t1 where 7 in (select b1 from t2 group by b1);
 id	select_type	table	type	possible_keys	key	key_len	ref	rows	Extra
 1	PRIMARY	NULL	NULL	NULL	NULL	NULL	NULL	NULL	Select tables optimized away
-2	SUBQUERY	t2	system	NULL	NULL	NULL	NULL	0	const row not found
+2	SUBQUERY	NULL	NULL	NULL	NULL	NULL	NULL	NULL	no matching row in const table
 select min(a1) from t1 where 7 in (select b1 from t2 group by b1);
 min(a1)
 set @@optimizer_switch='default,materialization=off';
@@ -1153,7 +1153,7 @@ set @@optimizer_switch='default,semijoin
 explain select min(a1) from t1 where 7 in (select b1 from t2);
 id	select_type	table	type	possible_keys	key	key_len	ref	rows	Extra
 1	PRIMARY	NULL	NULL	NULL	NULL	NULL	NULL	NULL	Select tables optimized away
-2	SUBQUERY	t2	system	NULL	NULL	NULL	NULL	0	const row not found
+2	SUBQUERY	NULL	NULL	NULL	NULL	NULL	NULL	NULL	no matching row in const table
 select min(a1) from t1 where 7 in (select b1 from t2);
 min(a1)
 set @@optimizer_switch='default,materialization=off';

=== modified file 'sql/item_subselect.cc'
--- a/sql/item_subselect.cc	2010-07-10 10:37:30 +0000
+++ b/sql/item_subselect.cc	2010-07-16 10:52:02 +0000
@@ -166,6 +166,7 @@ void Item_in_subselect::cleanup()
 Item_subselect::~Item_subselect()
 {
   delete engine;
+  engine= NULL;
 }
 
 Item_subselect::trans_res
@@ -2220,73 +2221,73 @@ void Item_in_subselect::update_used_tabl
 
 bool Item_in_subselect::setup_engine()
 {
-  subselect_hash_sj_engine *new_engine= NULL;
-  bool res= FALSE;
+  subselect_hash_sj_engine       *mat_engine= NULL;
+  subselect_single_select_engine *select_engine;
 
   DBUG_ENTER("Item_in_subselect::setup_engine");
 
-  if (engine->engine_type() == subselect_engine::SINGLE_SELECT_ENGINE)
-  {
-    /* Create/initialize objects in permanent memory. */
-    subselect_single_select_engine *old_engine;
-    Query_arena *arena= thd->stmt_arena, backup;
 
-    old_engine= (subselect_single_select_engine*) engine;
+  SELECT_LEX *save_select= thd->lex->current_select;
+  thd->lex->current_select= get_select_lex();
+  int res= thd->lex->current_select->join->optimize();
+  thd->lex->current_select= save_select;
+  if (res)
+    DBUG_RETURN(TRUE);
 
-    if (arena->is_conventional())
-      arena= 0;
-    else
-      thd->set_n_backup_active_arena(arena, &backup);
+  /*
+    The select_engine (that executes transformed IN=>EXISTS subselects) is
+    pre-created at parse time, and is stored in statment memory (preserved
+    across PS executions).
+  */
+  DBUG_ASSERT(engine->engine_type() == subselect_engine::SINGLE_SELECT_ENGINE);
+  select_engine= (subselect_single_select_engine*) engine;
 
-    if (!(new_engine= new subselect_hash_sj_engine(thd, this,
-                                                   old_engine)) ||
-        new_engine->init_permanent(unit->get_unit_column_types()))
-    {
-      Item_subselect::trans_res trans_res;
-      /*
-        If for some reason we cannot use materialization for this IN predicate,
-        delete all materialization-related objects, and apply the IN=>EXISTS
-        transformation.
-      */
-      delete new_engine;
-      new_engine= NULL;
-      exec_method= NOT_TRANSFORMED;
-      if (left_expr->cols() == 1)
-        trans_res= single_value_in_to_exists_transformer(old_engine->join,
-                                                         &eq_creator);
-      else
-        trans_res= row_value_in_to_exists_transformer(old_engine->join);
-      res= (trans_res != Item_subselect::RES_OK);
-    }
-    if (new_engine)
-      engine= new_engine;
+  /* Create/initialize execution objects. */
+  if (!(mat_engine= new subselect_hash_sj_engine(thd, this, select_engine)))
+    DBUG_RETURN(TRUE);
 
-    if (arena)
-      thd->restore_active_arena(arena, &backup);
-  }
-  else
+  if (mat_engine->init(&select_engine->join->fields_list))
   {
-    DBUG_ASSERT(engine->engine_type() == subselect_engine::HASH_SJ_ENGINE);
-    new_engine= (subselect_hash_sj_engine*) engine;
-  }
+    Item_subselect::trans_res trans_res;
+    /*
+      If for some reason we cannot use materialization for this IN predicate,
+      delete all materialization-related objects, and apply the IN=>EXISTS
+      transformation.
+    */
+    delete mat_engine;
+    mat_engine= NULL;
+    exec_method= NOT_TRANSFORMED;
+
+    if (left_expr->cols() == 1)
+      trans_res= single_value_in_to_exists_transformer(select_engine->join,
+                                                       &eq_creator);
+    else
+      trans_res= row_value_in_to_exists_transformer(select_engine->join);
 
-  /* Initilizations done in runtime memory, repeated for each execution. */
-  if (new_engine)
-  {
     /*
-      Reset the LIMIT 1 set in Item_exists_subselect::fix_length_and_dec.
-      TODO:
-      Currently we set the subquery LIMIT to infinity, and this is correct
-      because we forbid at parse time LIMIT inside IN subqueries (see
-      Item_in_subselect::test_limit). However, once we allow this, here
-      we should set the correct limit if given in the query.
+      The IN=>EXISTS transformation above injects new predicates into the
+      WHERE and HAVING clauses. Since the subquery was already optimized,
+      below we force its reoptimization with the new injected conditions
+      by the first call to subselect_single_select_engine::exec().
+      This is the only case of lazy subquery optimization in the server.
     */
-    unit->global_parameters->select_limit= NULL;
-    if ((res= new_engine->init_runtime()))
-      DBUG_RETURN(res);
+    DBUG_ASSERT(select_engine->join->optimized);
+    select_engine->join->optimized= false;
+    DBUG_RETURN(trans_res != Item_subselect::RES_OK);
   }
 
-  DBUG_RETURN(res);
+  /*
+    Reset the "LIMIT 1" set in Item_exists_subselect::fix_length_and_dec.
+    TODO:
+    Currently we set the subquery LIMIT to infinity, and this is correct
+    because we forbid at parse time LIMIT inside IN subqueries (see
+    Item_in_subselect::test_limit). However, once we allow this, here
+    we should set the correct limit if given in the query.
+  */
+  unit->global_parameters->select_limit= NULL;
+
+  engine= mat_engine;
+  DBUG_RETURN(FALSE);
 }
 
 
@@ -3787,13 +3788,14 @@ bitmap_init_memroot(MY_BITMAP *map, uint
   @retval FALSE otherwise
 */
 
-bool subselect_hash_sj_engine::init_permanent(List<Item> *tmp_columns)
+bool subselect_hash_sj_engine::init(List<Item> *tmp_columns)
 {
+  select_union *result_sink;
   /* Options to create_tmp_table. */
   ulonglong tmp_create_options= thd->options | TMP_TABLE_ALL_COLUMNS;
                              /* | TMP_TABLE_FORCE_MYISAM; TIMOUR: force MYISAM */
 
-  DBUG_ENTER("subselect_hash_sj_engine::init_permanent");
+  DBUG_ENTER("subselect_hash_sj_engine::init");
 
   if (bitmap_init_memroot(&non_null_key_parts, tmp_columns->elements,
                             thd->mem_root) ||
@@ -3822,15 +3824,16 @@ bool subselect_hash_sj_engine::init_perm
     DBUG_RETURN(TRUE);
   }
 */
-  if (!(result= new select_materialize_with_stats))
+  if (!(result_sink= new select_materialize_with_stats))
     DBUG_RETURN(TRUE);
-
-  if (((select_union*) result)->create_result_table(
-                         thd, tmp_columns, TRUE, tmp_create_options,
-                         "materialized subselect", TRUE))
+  result_sink->get_tmp_table_param()->materialized_subquery= true;
+  if (result_sink->create_result_table(thd, tmp_columns, TRUE,
+                                       tmp_create_options,
+                                       "materialized subselect", TRUE))
     DBUG_RETURN(TRUE);
 
-  tmp_table= ((select_union*) result)->table;
+  tmp_table= result_sink->table;
+  result= result_sink;
 
   /*
     If the subquery has blobs, or the total key lenght is bigger than
@@ -3867,6 +3870,17 @@ bool subselect_hash_sj_engine::init_perm
       !(lookup_engine= make_unique_engine()))
     DBUG_RETURN(TRUE);
 
+  /*
+    Repeat name resolution for 'cond' since cond is not part of any
+    clause of the query, and it is not 'fixed' during JOIN::prepare.
+  */
+  if (semi_join_conds && !semi_join_conds->fixed &&
+      semi_join_conds->fix_fields(thd, (Item**)&semi_join_conds))
+    DBUG_RETURN(TRUE);
+  /* Let our engine reuse this query plan for materialization. */
+  materialize_join= materialize_engine->join;
+  materialize_join->change_result(result);
+
   DBUG_RETURN(FALSE);
 }
 
@@ -3957,8 +3971,6 @@ subselect_hash_sj_engine::make_unique_en
   Item_iterator_row it(item_in->left_expr);
   /* The only index on the temporary table. */
   KEY *tmp_key= tmp_table->key_info;
-  /* Number of keyparts in tmp_key. */
-  uint tmp_key_parts= tmp_key->key_parts;
   JOIN_TAB *tab;
 
   DBUG_ENTER("subselect_hash_sj_engine::make_unique_engine");
@@ -3981,41 +3993,22 @@ subselect_hash_sj_engine::make_unique_en
 }
 
 
-/**
-  Initialize members of the engine that need to be re-initilized at each
-  execution.
+subselect_hash_sj_engine::~subselect_hash_sj_engine()
+{
+  delete lookup_engine;
+  delete result;
+  if (tmp_table)
+    free_tmp_table(thd, tmp_table);
+}
 
-  @retval TRUE  if a memory allocation error occurred
-  @retval FALSE if success
-*/
 
-bool subselect_hash_sj_engine::init_runtime()
+int subselect_hash_sj_engine::prepare()
 {
   /*
     Create and optimize the JOIN that will be used to materialize
     the subquery if not yet created.
   */
-  materialize_engine->prepare();
-  /*
-    Repeat name resolution for 'cond' since cond is not part of any
-    clause of the query, and it is not 'fixed' during JOIN::prepare.
-  */
-  if (semi_join_conds && !semi_join_conds->fixed &&
-      semi_join_conds->fix_fields(thd, (Item**)&semi_join_conds))
-    return TRUE;
-  /* Let our engine reuse this query plan for materialization. */
-  materialize_join= materialize_engine->join;
-  materialize_join->change_result(result);
-  return FALSE;
-}
-
-
-subselect_hash_sj_engine::~subselect_hash_sj_engine()
-{
-  delete lookup_engine;
-  delete result;
-  if (tmp_table)
-    free_tmp_table(thd, tmp_table);
+  return materialize_engine->prepare();
 }
 
 
@@ -4036,6 +4029,12 @@ void subselect_hash_sj_engine::cleanup()
   count_null_only_columns= 0;
   strategy= UNDEFINED;
   materialize_engine->cleanup();
+  /*
+    Restore the original Item_in_subselect engine. This engine is created once
+    at parse time and stored across executions, while all other materialization
+    related engines are created and chosen for each execution.
+  */
+  ((Item_in_subselect *) item)->engine= materialize_engine;
   if (lookup_engine_type == TABLE_SCAN_ENGINE ||
       lookup_engine_type == ROWID_MERGE_ENGINE)
   {
@@ -4052,6 +4051,9 @@ void subselect_hash_sj_engine::cleanup()
   DBUG_ASSERT(lookup_engine->engine_type() == UNIQUESUBQUERY_ENGINE);
   lookup_engine->cleanup();
   result->cleanup(); /* Resets the temp table as well. */
+  DBUG_ASSERT(tmp_table);
+  free_tmp_table(thd, tmp_table);
+  tmp_table= NULL;
 }
 
 
@@ -4080,9 +4082,8 @@ int subselect_hash_sj_engine::exec()
     the subquery predicate.
   */
   thd->lex->current_select= materialize_engine->select_lex;
-  if ((res= materialize_join->optimize()))
-    goto err; /* purecov: inspected */
-  DBUG_ASSERT(!is_materialized); /* We should materialize only once. */
+  /* The subquery should be optimized, and materialized only once. */
+  DBUG_ASSERT(materialize_join->optimized && !is_materialized);
   materialize_join->exec();
   if ((res= test(materialize_join->error || thd->is_fatal_error)))
     goto err;

=== modified file 'sql/item_subselect.h'
--- a/sql/item_subselect.h	2010-07-10 10:37:30 +0000
+++ b/sql/item_subselect.h	2010-07-16 10:52:02 +0000
@@ -817,10 +817,9 @@ public:
   }
   ~subselect_hash_sj_engine();
 
-  bool init_permanent(List<Item> *tmp_columns);
-  bool init_runtime();
+  bool init(List<Item> *tmp_columns);
   void cleanup();
-  int prepare() { return 0; } /* Override virtual function in base class. */
+  int prepare();
   int exec();
   virtual void print(String *str, enum_query_type query_type);
   uint cols()

=== modified file 'sql/sql_class.cc'
--- a/sql/sql_class.cc	2010-07-10 10:37:30 +0000
+++ b/sql/sql_class.cc	2010-07-16 10:52:02 +0000
@@ -3052,6 +3052,7 @@ void TMP_TABLE_PARAM::init()
   table_charset= 0;
   precomputed_group_by= 0;
   bit_fields_as_long= 0;
+  materialized_subquery= 0;
   skip_create_table= 0;
   DBUG_VOID_RETURN;
 }

=== modified file 'sql/sql_class.h'
--- a/sql/sql_class.h	2010-07-10 10:37:30 +0000
+++ b/sql/sql_class.h	2010-07-16 10:52:02 +0000
@@ -2852,6 +2852,8 @@ public:
   uint  convert_blob_length;
   CHARSET_INFO *table_charset;
   bool schema_table;
+  /* TRUE if the temp table is created for subquery materialization. */
+  bool materialized_subquery;
   /*
     True if GROUP BY and its aggregate functions are already computed
     by a table access method (e.g. by loose index scan). In this case
@@ -2875,8 +2877,8 @@ public:
   TMP_TABLE_PARAM()
     :copy_field(0), group_parts(0),
      group_length(0), group_null_parts(0), convert_blob_length(0),
-     schema_table(0), precomputed_group_by(0), force_copy_fields(0),
-     bit_fields_as_long(0), skip_create_table(0)
+    schema_table(0), materialized_subquery(0), precomputed_group_by(0),
+    force_copy_fields(0), bit_fields_as_long(0), skip_create_table(0)
   {}
   ~TMP_TABLE_PARAM()
   {
@@ -2905,6 +2907,7 @@ public:
   bool send_data(List<Item> &items);
   bool send_eof();
   bool flush();
+  TMP_TABLE_PARAM *get_tmp_table_param() { return &tmp_table_param; }
 
   virtual bool create_result_table(THD *thd, List<Item> *column_types,
                                    bool is_distinct, ulonglong options,
@@ -2969,7 +2972,7 @@ protected:
   ha_rows count_rows;
 
 public:
-  select_materialize_with_stats() {}
+  select_materialize_with_stats() { tmp_table_param.init(); }
   virtual bool create_result_table(THD *thd, List<Item> *column_types,
                                    bool is_distinct, ulonglong options,
                                    const char *alias, bool bit_fields_as_long);

=== modified file 'sql/sql_select.cc'
--- a/sql/sql_select.cc	2010-07-10 10:37:30 +0000
+++ b/sql/sql_select.cc	2010-07-16 10:52:02 +0000
@@ -2586,14 +2586,13 @@ err:
   Setup for execution all subqueries of a query, for which the optimizer
   chose hash semi-join.
 
-  @details Iterate over all subqueries of the query, and if they are under an
-  IN predicate, and the optimizer chose to compute it via hash semi-join:
-  - try to initialize all data structures needed for the materialized execution
-    of the IN predicate,
-  - if this fails, then perform the IN=>EXISTS transformation which was
-    previously blocked during JOIN::prepare.
-
-  This method is part of the "code generation" query processing phase.
+  @details Iterate over all immediate child subqueries of the query, and if
+  they are under an IN predicate, and the optimizer chose to compute it via
+  materialization:
+  - optimize each subquery,
+  - choose an optimial execution strategy for the IN predicate - either
+    materialization, or an IN=>EXISTS transformation with an approriate
+    engine.
 
   This phase must be called after substitute_for_best_equal_field() because
   that function may replace items with other items from a multiple equality,
@@ -7925,7 +7924,7 @@ bool TABLE_REF::tmp_table_index_lookup_i
                                     use that information instead.
                                  */
                                  cur_ref_buff + null_count,
-                                 null_count ? key_buff : 0,
+                                 null_count ? cur_ref_buff : 0,
                                  cur_key_part->length, items[i], value);
     cur_ref_buff+= cur_key_part->store_length;
   }
@@ -11408,10 +11407,30 @@ create_tmp_table(THD *thd,TMP_TABLE_PARA
       {
 	if (thd->is_fatal_error)
 	  goto err;				// Got OOM
-	continue;				// Some kindf of const item
+	continue;				// Some kind of const item
       }
       if (type == Item::SUM_FUNC_ITEM)
-	((Item_sum *) item)->result_field= new_field;
+      {
+        Item_sum *agg_item= (Item_sum *) item;
+        /*
+          Update the result field only if it has never been set, or if the
+          created temporary table is not to be used for subquery
+          materialization.
+
+          The reason is that for subqueries that require materialization as part
+          of their plan, we create the 'external' temporary table needed for IN
+          execution, after the 'internal' temporary table needed for grouping.
+          Since both the external and the internal temporary tables are created
+          for the same list of SELECT fields of the subquery, setting
+          'result_field' for each invocation of create_tmp_table overrides the
+           previous value of 'result_field'.
+
+          The condition below prevents the creation of the external temp table
+          to override the 'result_field' that was set for the internal temp table.
+        */
+        if (!agg_item->result_field || !param->materialized_subquery)
+          agg_item->result_field= new_field;
+      }
       tmp_from_field++;
       reclength+=new_field->pack_length();
       if (!(new_field->flags & NOT_NULL_FLAG))
@@ -19240,6 +19259,8 @@ bool JOIN::change_result(select_result *
 {
   DBUG_ENTER("JOIN::change_result");
   result= res;
+  if (tmp_join)
+    tmp_join->result= res;
   if (!procedure && (result->prepare(fields_list, select_lex->master_unit()) ||
                      result->prepare2()))
   {

# Bazaar merge directive format 2 (Bazaar 0.90)
# revision_id: timour@xxxxxxxxxxxx-20100716105202-8narq4tzhka2n1a5
# target_branch: file:///home/tsk/mprog/src/5.3-mwl89/
# testament_sha1: 1f2c87c55849aec8fd79c696f4d643f6e9c5e4c6
# timestamp: 2010-07-16 13:52:07 +0300
# base_revision_id: sanja@xxxxxxxxxxxx-20100710103730-ayy6a61pdibspf4o
# 
# Begin bundle
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