MDEV-17167 - InnoDB: Failing assertion: table->get_ref_count() == 0 upon

truncating a temporary table

TRUNCATE expects only one TABLE instance (which is used by TRUNCATE
itself) to be open. However this requirement wasn't enforced after
"MDEV-5535: Cannot reopen temporary table".

Fixed by closing unused table instances before performing TRUNCATE.
This commit is contained in:
Sergey Vojtovich 2018-09-12 16:36:45 +04:00
parent b9a5ff3644
commit bad2f1569d
6 changed files with 89 additions and 1 deletions

View file

@ -548,3 +548,27 @@ DROP TABLE nonexisting_table, t1;
ERROR 42S02: Unknown table 'temp_db.nonexisting_table'
# Cleanup
DROP DATABASE temp_db;
USE test;
#
# MDEV-17167 - InnoDB: Failing assertion: table->get_ref_count() == 0
# upon truncating a temporary table
#
CREATE TEMPORARY TABLE t1(a INT) ENGINE=InnoDB;
SELECT * FROM t1 AS t1a1, t1 AS t2a2;
a a
TRUNCATE TABLE t1;
LOCK TABLES t1 WRITE;
TRUNCATE TABLE t1;
SELECT * FROM t1;
a
UNLOCK TABLES;
LOCK TABLES t1 AS t1a1 WRITE, t1 AS t1a2 WRITE;
TRUNCATE TABLE t1;
SELECT * FROM t1 AS t1a1, t1 AS t1a2;
a a
UNLOCK TABLES;
CREATE TABLE t2(a INT) ENGINE=InnoDB;
LOCK TABLES t2 WRITE;
TRUNCATE TABLE t1;
UNLOCK TABLES;
DROP TABLE t1, t2;

View file

@ -594,4 +594,30 @@ DROP TABLE nonexisting_table, t1;
--echo # Cleanup
DROP DATABASE temp_db;
USE test;
--echo #
--echo # MDEV-17167 - InnoDB: Failing assertion: table->get_ref_count() == 0
--echo # upon truncating a temporary table
--echo #
CREATE TEMPORARY TABLE t1(a INT) ENGINE=InnoDB;
SELECT * FROM t1 AS t1a1, t1 AS t2a2;
TRUNCATE TABLE t1;
LOCK TABLES t1 WRITE;
TRUNCATE TABLE t1;
SELECT * FROM t1;
UNLOCK TABLES;
LOCK TABLES t1 AS t1a1 WRITE, t1 AS t1a2 WRITE;
TRUNCATE TABLE t1;
SELECT * FROM t1 AS t1a1, t1 AS t1a2;
UNLOCK TABLES;
CREATE TABLE t2(a INT) ENGINE=InnoDB;
LOCK TABLES t2 WRITE;
TRUNCATE TABLE t1;
UNLOCK TABLES;
DROP TABLE t1, t2;

View file

@ -4628,6 +4628,7 @@ public:
TMP_TABLE_SHARE* save_tmp_table_share(TABLE *table);
void restore_tmp_table_share(TMP_TABLE_SHARE *share);
void close_unused_temporary_table_instances(const TABLE_LIST *tl);
private:
/* Whether a lock has been acquired? */

View file

@ -184,7 +184,12 @@ public:
list= &a;
current= a.m_first;
}
/* Operator for it++ */
/**
Operator for it++
@note since we save next element pointer, caller may remove current element.
Such modification doesn't invalidate iterator.
*/
inline T* operator++(int)
{
T *result= current;

View file

@ -401,6 +401,8 @@ bool Sql_cmd_truncate_table::truncate_table(THD *thd, TABLE_LIST *table_ref)
/* In RBR, the statement is not binlogged if the table is temporary. */
binlog_stmt= !thd->is_current_stmt_binlog_format_row();
thd->close_unused_temporary_table_instances(table_ref);
error= handler_truncate(thd, table_ref, TRUE);
/*

View file

@ -1540,3 +1540,33 @@ void THD::unlock_temporary_tables()
DBUG_VOID_RETURN;
}
/**
Close unused TABLE instances for given temporary table.
@param tl [IN] TABLE_LIST
Initial use case was TRUNCATE, which expects only one instance (which is used
by TRUNCATE itself) to be open. Most probably some ALTER TABLE variants and
REPAIR may have similar expectations.
*/
void THD::close_unused_temporary_table_instances(const TABLE_LIST *tl)
{
TMP_TABLE_SHARE *share= find_tmp_table_share(tl);
if (share)
{
All_share_tables_list::Iterator tables_it(share->all_tmp_tables);
while (TABLE *table= tables_it++)
{
if (table->query_id == 0)
{
/* Note: removing current list element doesn't invalidate iterator. */
share->all_tmp_tables.remove(table);
free_temporary_table(table);
}
}
}
}