MDEV-34447: Memory leakage is detected on running the test main.ps against the server 11.1

The memory leak happened on second execution of a prepared statement
that runs UPDATE statement with correlated subquery in right hand side of
the SET clause. In this case, invocation of the method
  table->stat_records()
could return the zero value that results in going into the 'if' branch
that handles impossible where condition. The issue is that this condition
branch missed saving of leaf tables that has to be performed as first
condition optimization activity. Later the PS statement memory root
is marked as read only on finishing first time execution of the prepared
statement. Next time the same statement is executed it hits the assertion
on attempt to allocate a memory on the PS memory root marked as read only.
This memory allocation takes place by the sequence of the following
invocations:
 Prepared_statement::execute
  mysql_execute_command
   Sql_cmd_dml::execute
    Sql_cmd_update::execute_inner
     Sql_cmd_update::update_single_table
      st_select_lex::save_leaf_tables
       List<TABLE_LIST>::push_back

To fix the issue, add the flag SELECT_LEX::leaf_tables_saved to control
whether the method SELECT_LEX::save_leaf_tables() has to be called or
it has been already invoked and no more invocation required.

Similar issue could take place on running the DELETE statement with
the LIMIT clause in PS/SP mode. The reason of memory leak is the same as for
UPDATE case and be fixed in the same way.
This commit is contained in:
Yuchen Pei 2024-09-06 11:34:31 +10:00
parent 8024b8e4c1
commit 2c3e07df47
No known key found for this signature in database
GPG Key ID: 3DD1B35105743563
8 changed files with 91 additions and 7 deletions

View File

@ -89,3 +89,33 @@ f
DEALLOCATE PREPARE stmt;
DROP TABLE t1;
# End of 10.4 tests
#
# MDEV-34447: Memory leakage is detected on running the test main.ps against the server 11.1
#
CREATE TABLE t1 (id INT, value INT);
CREATE TABLE t2 (id INT);
PREPARE stmt FROM 'UPDATE t1 SET value = (SELECT 1 FROM t2 WHERE id = t1.id)';
EXECUTE stmt;
INSERT INTO t1 VALUES (1,10),(2,10),(3,10);
INSERT INTO t2 VALUES (1),(2);
EXECUTE stmt;
SELECT * FROM t1;
id value
1 1
2 1
3 NULL
DEALLOCATE PREPARE stmt;
DROP TABLE t1, t2;
# Memory leak also could take place on running the DELETE statement
# with the LIMIT clause. Check it.
CREATE TABLE t1 (c1 INT);
INSERT INTO t1 (c1) VALUES (1), (2), (3);
CREATE PROCEDURE p1(p1 INT)
DELETE FROM t1 LIMIT p1;
CALL p1(0);
CALL p1(1);
CALL p1(2);
# Clean up
DROP TABLE t1;
DROP PROCEDURE p1;
# End of 10.5 tests

View File

@ -110,3 +110,36 @@ DEALLOCATE PREPARE stmt;
DROP TABLE t1;
--echo # End of 10.4 tests
--echo #
--echo # MDEV-34447: Memory leakage is detected on running the test main.ps against the server 11.1
--echo #
CREATE TABLE t1 (id INT, value INT);
CREATE TABLE t2 (id INT);
PREPARE stmt FROM 'UPDATE t1 SET value = (SELECT 1 FROM t2 WHERE id = t1.id)';
EXECUTE stmt;
INSERT INTO t1 VALUES (1,10),(2,10),(3,10);
INSERT INTO t2 VALUES (1),(2);
EXECUTE stmt;
SELECT * FROM t1;
DEALLOCATE PREPARE stmt;
DROP TABLE t1, t2;
--echo # Memory leak also could take place on running the DELETE statement
--echo # with the LIMIT clause. Check it.
CREATE TABLE t1 (c1 INT);
INSERT INTO t1 (c1) VALUES (1), (2), (3);
CREATE PROCEDURE p1(p1 INT)
DELETE FROM t1 LIMIT p1;
CALL p1(0);
CALL p1(1);
CALL p1(2);
--echo # Clean up
DROP TABLE t1;
DROP PROCEDURE p1;
--echo # End of 10.5 tests

View File

@ -530,6 +530,13 @@ bool mysql_delete(THD *thd, TABLE_LIST *table_list, COND *conds,
*/
if (unlikely(thd->is_error()))
DBUG_RETURN(TRUE);
if (!thd->lex->current_select->leaf_tables_saved)
{
thd->lex->current_select->save_leaf_tables(thd);
thd->lex->current_select->leaf_tables_saved= true;
}
my_ok(thd, 0);
DBUG_RETURN(0); // Nothing to delete
}
@ -902,10 +909,10 @@ cleanup:
query_cache_invalidate3(thd, table_list, 1);
}
if (thd->lex->current_select->first_cond_optimization)
if (!thd->lex->current_select->leaf_tables_saved)
{
thd->lex->current_select->save_leaf_tables(thd);
thd->lex->current_select->first_cond_optimization= 0;
thd->lex->current_select->leaf_tables_saved= true;
}
delete deltempfile;

View File

@ -1373,10 +1373,10 @@ values_loop_end:
::my_ok(thd, info.copied + info.deleted + updated, id, buff);
}
thd->abort_on_warning= 0;
if (thd->lex->current_select->first_cond_optimization)
if (!thd->lex->current_select->leaf_tables_saved)
{
thd->lex->current_select->save_leaf_tables(thd);
thd->lex->current_select->first_cond_optimization= 0;
thd->lex->current_select->leaf_tables_saved= true;
}
my_free(readbuff);

View File

@ -3023,6 +3023,7 @@ void st_select_lex::init_query()
olap= UNSPECIFIED_OLAP_TYPE;
having_fix_field= 0;
having_fix_field_for_pushed_cond= 0;
leaf_tables_saved= false;
context.select_lex= this;
context.init();
cond_count= between_count= with_wild= 0;

View File

@ -1182,6 +1182,10 @@ public:
*/
bool have_merged_subqueries;
/**
Flag to guard against double initialization of leaf tables list
*/
bool leaf_tables_saved;
List<TABLE_LIST> leaf_tables;
List<TABLE_LIST> leaf_tables_exec;
List<TABLE_LIST> leaf_tables_prep;

View File

@ -2054,12 +2054,14 @@ JOIN::optimize_inner()
/* Convert all outer joins to inner joins if possible */
conds= simplify_joins(this, join_list, conds, TRUE, FALSE);
if (thd->is_error() || select_lex->save_leaf_tables(thd))
if (thd->is_error() ||
(!select_lex->leaf_tables_saved && select_lex->save_leaf_tables(thd)))
{
if (arena)
thd->restore_active_arena(arena, &backup);
DBUG_RETURN(1);
}
select_lex->leaf_tables_saved= true;
build_bitmap_for_nested_joins(join_list, 0);
sel->prep_where= conds ? conds->copy_andor_structure(thd) : 0;

View File

@ -598,6 +598,13 @@ int mysql_update(THD *thd,
{
DBUG_RETURN(1); // Error in where
}
if (!thd->lex->current_select->leaf_tables_saved)
{
thd->lex->current_select->save_leaf_tables(thd);
thd->lex->current_select->leaf_tables_saved= true;
}
my_ok(thd); // No matching records
DBUG_RETURN(0);
}
@ -1366,10 +1373,10 @@ update_end:
}
thd->count_cuted_fields= CHECK_FIELD_IGNORE; /* calc cuted fields */
thd->abort_on_warning= 0;
if (thd->lex->current_select->first_cond_optimization)
if (!thd->lex->current_select->leaf_tables_saved)
{
thd->lex->current_select->save_leaf_tables(thd);
thd->lex->current_select->first_cond_optimization= 0;
thd->lex->current_select->leaf_tables_saved= true;
}
*found_return= found;
*updated_return= updated;