Skip to content

Commit f388222

Browse files
MDEV-36504 Memory leak after CREATE TABLE..SELECT
Problem: ======== - After commit cc8eefb (MDEV-33087), InnoDB does use bulk insert operation for ALTER TABLE.. ALGORITHM=COPY and CREATE TABLE..SELECT as well. InnoDB fails to clear the bulk buffer when it encounters error during CREATE..SELECT. Problem is that while transaction cleanup, InnoDB fails to identify the bulk insert for DDL operation. Fix: ==== - Represent bulk_insert in trx by 2 bits. By doing that, InnoDB can distinguish between TRX_DML_BULK, TRX_DDL_BULK. During DDL, set bulk insert value for transaction to TRX_DDL_BULK. - Introduce a parameter HA_EXTRA_ABORT_ALTER_COPY which rollbacks only TRX_DDL_BULK transaction. - bulk_insert_apply() happens for TRX_DDL_BULK transaction happens only during HA_EXTRA_END_ALTER_COPY extra() call.
1 parent 1a013ce commit f388222

File tree

14 files changed

+118
-22
lines changed

14 files changed

+118
-22
lines changed

include/my_base.h

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -219,7 +219,10 @@ enum ha_extra_function {
219219
/** Start writing rows during ALTER TABLE...ALGORITHM=COPY. */
220220
HA_EXTRA_BEGIN_ALTER_COPY,
221221
/** Finish writing rows during ALTER TABLE...ALGORITHM=COPY. */
222-
HA_EXTRA_END_ALTER_COPY
222+
HA_EXTRA_END_ALTER_COPY,
223+
/** Abort of writing rows during ALTER TABLE..ALGORITHM=COPY or
224+
CREATE..SELCT */
225+
HA_EXTRA_ABORT_ALTER_COPY
223226
};
224227

225228
/* Compatible option, to be deleted in 6.0 */

mysql-test/suite/innodb/r/alter_copy_bulk.result

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,3 +91,24 @@ INSERT INTO t1 VALUES
9191
ALTER TABLE t1 FORCE, ALGORITHM=COPY;
9292
DROP TABLE t1;
9393
SET GLOBAL innodb_stats_persistent=@default_stats_persistent;
94+
#
95+
# MDEV-36504 Memory leak after insert into empty table
96+
#
97+
CREATE TABLE t1 (k INT PRIMARY KEY)ENGINE=InnoDB;
98+
INSERT INTO t1 SET k= 1;
99+
START TRANSACTION;
100+
INSERT INTO t1 SET k= 2;
101+
SELECT COUNT(*) > 0 FROM mysql.innodb_index_stats LOCK IN SHARE MODE;
102+
COUNT(*) > 0
103+
1
104+
connect con1,localhost,root,,,;
105+
SET innodb_lock_wait_timeout=0;
106+
CREATE TABLE t2(f1 INT DEFAULT 1 PRIMARY KEY)
107+
STATS_PERSISTENT= 1 ENGINE=InnoDB as SELECT k FROM t1;
108+
ERROR HY000: Lock wait timeout exceeded; try restarting transaction
109+
disconnect con1;
110+
connection default;
111+
SET innodb_lock_wait_timeout=default;
112+
DROP TABLE t1;
113+
DROP TABLE IF EXISTS t2;
114+
# restart

mysql-test/suite/innodb/t/alter_copy_bulk.test

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,3 +109,24 @@ INSERT INTO t1 VALUES
109109
ALTER TABLE t1 FORCE, ALGORITHM=COPY;
110110
DROP TABLE t1;
111111
SET GLOBAL innodb_stats_persistent=@default_stats_persistent;
112+
113+
--echo #
114+
--echo # MDEV-36504 Memory leak after insert into empty table
115+
--echo #
116+
CREATE TABLE t1 (k INT PRIMARY KEY)ENGINE=InnoDB;
117+
INSERT INTO t1 SET k= 1;
118+
START TRANSACTION;
119+
INSERT INTO t1 SET k= 2;
120+
SELECT COUNT(*) > 0 FROM mysql.innodb_index_stats LOCK IN SHARE MODE;
121+
122+
connect(con1,localhost,root,,,);
123+
SET innodb_lock_wait_timeout=0;
124+
--error ER_LOCK_WAIT_TIMEOUT
125+
CREATE TABLE t2(f1 INT DEFAULT 1 PRIMARY KEY)
126+
STATS_PERSISTENT= 1 ENGINE=InnoDB as SELECT k FROM t1;
127+
disconnect con1;
128+
connection default;
129+
SET innodb_lock_wait_timeout=default;
130+
DROP TABLE t1;
131+
DROP TABLE IF EXISTS t2;
132+
--source include/restart_mysqld.inc

sql/ha_partition.cc

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2141,7 +2141,9 @@ int ha_partition::change_partitions(HA_CREATE_INFO *create_info,
21412141
m_added_file[i]->extra(HA_EXTRA_BEGIN_ALTER_COPY);
21422142
error= copy_partitions(copied, deleted);
21432143
for (i= 0; i < part_count; i++)
2144-
m_added_file[i]->extra(HA_EXTRA_END_ALTER_COPY);
2144+
m_added_file[i]->extra(error
2145+
? HA_EXTRA_ABORT_ALTER_COPY
2146+
: HA_EXTRA_END_ALTER_COPY);
21452147
if (unlikely(error))
21462148
{
21472149
/*
@@ -9467,6 +9469,7 @@ int ha_partition::extra(enum ha_extra_function operation)
94679469
case HA_EXTRA_STARTING_ORDERED_INDEX_SCAN:
94689470
case HA_EXTRA_BEGIN_ALTER_COPY:
94699471
case HA_EXTRA_END_ALTER_COPY:
9472+
case HA_EXTRA_ABORT_ALTER_COPY:
94709473
DBUG_RETURN(loop_partitions(extra_cb, &operation));
94719474
default:
94729475
{

sql/sql_insert.cc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4532,7 +4532,7 @@ void select_insert::abort_result_set()
45324532
table->file->ha_rnd_end();
45334533
table->file->extra(HA_EXTRA_NO_IGNORE_DUP_KEY);
45344534
table->file->extra(HA_EXTRA_WRITE_CANNOT_REPLACE);
4535-
4535+
table->file->extra(HA_EXTRA_ABORT_ALTER_COPY);
45364536
/*
45374537
If at least one row has been inserted/modified and will stay in
45384538
the table (the table doesn't have transactions) we must write to

sql/sql_table.cc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12378,6 +12378,7 @@ copy_data_between_tables(THD *thd, TABLE *from, TABLE *to, bool ignore,
1237812378
if (alt_error > 0)
1237912379
{
1238012380
error= alt_error;
12381+
to->file->extra(HA_EXTRA_ABORT_ALTER_COPY);
1238112382
copy_data_error_ignore(error, false, to, thd, alter_ctx);
1238212383
}
1238312384
}

storage/innobase/handler/ha_innodb.cc

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3652,7 +3652,7 @@ ha_innobase::init_table_handle_for_HANDLER(void)
36523652
m_prebuilt->used_in_HANDLER = TRUE;
36533653

36543654
reset_template();
3655-
m_prebuilt->trx->bulk_insert = false;
3655+
m_prebuilt->trx->bulk_insert &= TRX_DDL_BULK;
36563656
}
36573657

36583658
/*********************************************************************//**
@@ -4501,7 +4501,7 @@ static bool end_of_statement(trx_t *trx) noexcept
45014501
undo_no_t savept= 0;
45024502
trx->rollback(&savept);
45034503
/* MariaDB will roll back the entire transaction. */
4504-
trx->bulk_insert= false;
4504+
trx->bulk_insert&= TRX_DDL_BULK;
45054505
trx->last_stmt_start= 0;
45064506
return true;
45074507
}
@@ -15875,7 +15875,7 @@ ha_innobase::extra(
1587515875
stmt_boundary:
1587615876
trx->bulk_insert_apply();
1587715877
trx->end_bulk_insert(*m_prebuilt->table);
15878-
trx->bulk_insert = false;
15878+
trx->bulk_insert &= TRX_DDL_BULK;
1587915879
break;
1588015880
case HA_EXTRA_NO_KEYREAD:
1588115881
(void)check_trx_exists(ha_thd());
@@ -15941,15 +15941,15 @@ ha_innobase::extra(
1594115941
break;
1594215942
}
1594315943
m_prebuilt->table->skip_alter_undo = 0;
15944-
if (dberr_t err= trx->bulk_insert_apply()) {
15944+
if (dberr_t err= trx->bulk_insert_apply<TRX_DDL_BULK>()) {
1594515945
m_prebuilt->table->skip_alter_undo = 0;
1594615946
return convert_error_code_to_mysql(
1594715947
err, m_prebuilt->table->flags,
1594815948
trx->mysql_thd);
1594915949
}
1595015950

1595115951
trx->end_bulk_insert(*m_prebuilt->table);
15952-
trx->bulk_insert = false;
15952+
trx->bulk_insert &= TRX_DDL_BULK;
1595315953
if (!m_prebuilt->table->is_temporary()
1595415954
&& !high_level_read_only) {
1595515955
/* During copy_data_between_tables(), InnoDB only
@@ -15968,6 +15968,13 @@ ha_innobase::extra(
1596815968
log_buffer_flush_to_disk();
1596915969
}
1597015970
break;
15971+
case HA_EXTRA_ABORT_ALTER_COPY:
15972+
if (m_prebuilt->table->skip_alter_undo) {
15973+
trx = check_trx_exists(ha_thd());
15974+
m_prebuilt->table->skip_alter_undo = 0;
15975+
trx->rollback();
15976+
}
15977+
break;
1597115978
default:/* Do nothing */
1597215979
;
1597315980
}
@@ -16062,7 +16069,8 @@ ha_innobase::start_stmt(
1606216069
break;
1606316070
}
1606416071

16065-
trx->bulk_insert = false;
16072+
ut_ad(trx->bulk_insert != TRX_DDL_BULK);
16073+
trx->bulk_insert = TRX_NO_BULK;
1606616074
trx->last_stmt_start = trx->undo_no;
1606716075
}
1606816076

@@ -16270,7 +16278,7 @@ ha_innobase::external_lock(
1627016278
if (!trx->bulk_insert) {
1627116279
break;
1627216280
}
16273-
trx->bulk_insert = false;
16281+
trx->bulk_insert &= TRX_DDL_BULK;
1627416282
trx->last_stmt_start = trx->undo_no;
1627516283
}
1627616284

storage/innobase/handler/handler0alter.cc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5544,6 +5544,7 @@ static bool innodb_insert_sys_columns(
55445544
DBUG_EXECUTE_IF("instant_insert_fail",
55455545
my_error(ER_INTERNAL_ERROR, MYF(0),
55465546
"InnoDB: Insert into SYS_COLUMNS failed");
5547+
mem_heap_free(info->heap);
55475548
return true;);
55485549

55495550
if (DB_SUCCESS != que_eval_sql(

storage/innobase/include/trx0trx.h

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -809,8 +809,13 @@ struct trx_t : ilist_node<>
809809
/** normally set; "SET unique_checks=0, foreign_key_checks=0"
810810
enables bulk insert into an empty table */
811811
unsigned check_unique_secondary:1;
812-
/** whether an insert into an empty table is active */
813-
unsigned bulk_insert:1;
812+
/** whether an insert into an empty table is active
813+
Possible states are
814+
TRX_NO_BULK
815+
TRX_DML_BULK
816+
TRX_DDL_BULK
817+
@see trx_bulk_insert in trx0types.h */
818+
unsigned bulk_insert:2;
814819
/*------------------------------*/
815820
/* MySQL has a transaction coordinator to coordinate two phase
816821
commit between multiple storage engines and the binary log. When
@@ -1117,6 +1122,7 @@ struct trx_t : ilist_node<>
11171122
ut_ad(!is_not_inheriting_locks());
11181123
ut_ad(check_foreigns);
11191124
ut_ad(check_unique_secondary);
1125+
ut_ad(bulk_insert == TRX_NO_BULK);
11201126
}
11211127

11221128
/** This has to be invoked on SAVEPOINT or at the end of a statement.
@@ -1142,14 +1148,24 @@ struct trx_t : ilist_node<>
11421148
rollback to the start of a statement will work. */
11431149
void end_bulk_insert()
11441150
{
1151+
if (bulk_insert == TRX_DDL_BULK)
1152+
return;
11451153
for (auto& t : mod_tables)
11461154
t.second.end_bulk_insert();
11471155
}
11481156

11491157
/** @return whether a bulk insert into empty table is in progress */
11501158
bool is_bulk_insert() const
11511159
{
1152-
if (!bulk_insert || check_unique_secondary || check_foreigns)
1160+
switch (bulk_insert) {
1161+
case TRX_NO_BULK:
1162+
return false;
1163+
case TRX_DDL_BULK:
1164+
return true;
1165+
default:
1166+
ut_ad(bulk_insert == TRX_DML_BULK);
1167+
}
1168+
if (check_unique_secondary || check_foreigns)
11531169
return false;
11541170
for (const auto& t : mod_tables)
11551171
if (t.second.is_bulk_insert())
@@ -1179,9 +1195,11 @@ struct trx_t : ilist_node<>
11791195
/** Do the bulk insert for the buffered insert operation
11801196
for the transaction.
11811197
@return DB_SUCCESS or error code */
1198+
template<trx_bulk_insert type= TRX_DML_BULK>
11821199
dberr_t bulk_insert_apply()
11831200
{
1184-
return UNIV_UNLIKELY(bulk_insert) ? bulk_insert_apply_low(): DB_SUCCESS;
1201+
static_assert(type != TRX_NO_BULK, "");
1202+
return bulk_insert == type ? bulk_insert_apply_low(): DB_SUCCESS;
11851203
}
11861204

11871205
private:

storage/innobase/include/trx0types.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,15 @@ enum trx_state_t {
6565
TRX_STATE_COMMITTED_IN_MEMORY
6666
};
6767

68+
/** Transaction bulk insert operation @see trx_t::bulk_insert */
69+
enum trx_bulk_insert {
70+
TRX_NO_BULK,
71+
/** bulk insert is being executed during DML */
72+
TRX_DML_BULK,
73+
/** bulk insert is being executed in copy_data_between_tables() */
74+
TRX_DDL_BULK
75+
};
76+
6877
/** Memory objects */
6978
/* @{ */
7079
/** Transaction */

0 commit comments

Comments
 (0)