diff options
author | unknown <aelkin@mysql.com> | 2006-04-23 12:18:57 +0300 |
---|---|---|
committer | unknown <aelkin@mysql.com> | 2006-04-23 12:18:57 +0300 |
commit | 329ebbd14dc562a1206997cd855c29c6291c3854 (patch) | |
tree | 29a91d7d3a9b215d5596a3a5509eeb69ffaace4a /sql/sql_base.cc | |
parent | 8ed06b840e2aaac5d01ca2731f9abd53c339d968 (diff) | |
download | mariadb-git-329ebbd14dc562a1206997cd855c29c6291c3854.tar.gz |
Bug#17263 temporary tables and replication
Backporting a changeset made for 5.0. Comments from there:
The fix refines the algorithm of generating DROPs for binlog.
Temp tables with common pseudo_thread_id are clustered into one query.
Consequently one replication event per pseudo_thread_id is generated.
mysql-test/r/rpl_temporary.result:
results changed
mysql-test/t/rpl_temporary.test:
test to generate problematic drop in binlog to feed it to restarting slave
to see no stop.
sql/sql_base.cc:
change in drop temprorary tables alg in close_temporary_tables.
Diffstat (limited to 'sql/sql_base.cc')
-rw-r--r-- | sql/sql_base.cc | 161 |
1 files changed, 121 insertions, 40 deletions
diff --git a/sql/sql_base.cc b/sql/sql_base.cc index 223e8482f49..756fb8189dc 100644 --- a/sql/sql_base.cc +++ b/sql/sql_base.cc @@ -483,13 +483,23 @@ void close_temporary(TABLE *table,bool delete_table) DBUG_VOID_RETURN; } +/* close_temporary_tables' internal */ +static inline uint tmpkeyval(THD *thd, TABLE *table) +{ + return uint4korr(table->table_cache_key + table->key_length - + sizeof(thd->variables.pseudo_thread_id)); +} + +/* Creates one DROP TEMPORARY TABLE binlog event for each pseudo-thread */ void close_temporary_tables(THD *thd) { - TABLE *table,*next; - char *query, *end; - uint query_buf_size; - bool found_user_tables = 0; + TABLE *next, + *prev_table /* prev link is not maintained in TABLE's double-linked list */, + *table; + char *query= (gptr) 0, *end; + uint query_buf_size, max_names_len; + bool found_user_tables; if (!thd->temporary_tables) return; @@ -497,51 +507,122 @@ void close_temporary_tables(THD *thd) LINT_INIT(end); query_buf_size= 50; // Enough for DROP ... TABLE IF EXISTS - for (table=thd->temporary_tables ; table ; table=table->next) + /* + insertion sort of temp tables by pseudo_thread_id to build ordered list + of sublists of equal pseudo_thread_id + */ + for (prev_table= thd->temporary_tables, + table= prev_table->next, + found_user_tables= (prev_table->table_name[0] != '#'); + table; + prev_table= table, table= table->next) + { + TABLE *prev_sorted /* same as for prev_table */, + *sorted; /* - We are going to add 4 ` around the db/table names, so 1 does not look - enough; indeed it is enough, because table->key_length is greater (by 8, - because of server_id and thread_id) than db||table. + table not created directly by the user is moved to the tail. + Fixme/todo: nothing (I checked the manual) prevents user to create temp + with `#' */ - query_buf_size+= table->key_length+1; - - if ((query = alloc_root(thd->mem_root, query_buf_size))) + if (table->real_name[0] == '#') + continue; + else + { + found_user_tables = 1; + } + for (prev_sorted= NULL, sorted= thd->temporary_tables; sorted != table; + prev_sorted= sorted, sorted= sorted->next) + { + if (sorted->real_name[0] == '#' || tmpkeyval(thd, sorted) > tmpkeyval(thd, table)) + { + /* move into the sorted part of the list from the unsorted */ + prev_table->next= table->next; + table->next= sorted; + if (prev_sorted) + { + prev_sorted->next= table; + } + else + { + thd->temporary_tables= table; + } + table= prev_table; + break; + } + } + } + /* + calc query_buf_size as max per sublists, one sublist per pseudo thread id. + Also stop at first occurence of `#'-named table that starts + all implicitly created temp tables + */ + for (max_names_len= 0, table=thd->temporary_tables; + table && table->real_name[0] != '#'; + table=table->next) + { + uint tmp_names_len; + for (tmp_names_len= table->key_length + 1; + table->next && table->real_name[0] != '#' && + tmpkeyval(thd, table) == tmpkeyval(thd, table->next); + table=table->next) + { + /* + We are going to add 4 ` around the db/table names, so 1 might not look + enough; indeed it is enough, because table->key_length is greater (by 8, + because of server_id and thread_id) than db||table. + */ + tmp_names_len += table->next->key_length + 1; + } + if (tmp_names_len > max_names_len) max_names_len= tmp_names_len; + } + + /* allocate */ + if (found_user_tables && mysql_bin_log.is_open() && + (query = alloc_root(thd->mem_root, query_buf_size+= max_names_len))) // Better add "if exists", in case a RESET MASTER has been done - end=strmov(query, "DROP /*!40005 TEMPORARY */ TABLE IF EXISTS "); + end= strmov(query, "DROP /*!40005 TEMPORARY */ TABLE IF EXISTS "); - for (table=thd->temporary_tables ; table ; table=next) + /* scan sorted tmps to generate sequence of DROP */ + for (table=thd->temporary_tables; table; table= next) { - if (query) // we might be out of memory, but this is not fatal + if (query // we might be out of memory, but this is not fatal + && table->real_name[0] != '#') { - // skip temporary tables not created directly by the user - if (table->real_name[0] != '#') - found_user_tables = 1; + char *end_cur; + /* Set pseudo_thread_id to be that of the processed table */ + thd->variables.pseudo_thread_id= tmpkeyval(thd, table); + /* Loop forward through all tables within the sublist of + common pseudo_thread_id to create single DROP query */ + for (end_cur= end; + table && table->real_name[0] != '#' && + tmpkeyval(thd, table) == thd->variables.pseudo_thread_id; + table= next) + { + end_cur= strxmov(end_cur, "`", table->table_cache_key, "`.`", + table->real_name, "`,", NullS); + next= table->next; + close_temporary(table, 1); + } + thd->clear_error(); + /* The -1 is to remove last ',' */ + Query_log_event qinfo(thd, query, (ulong)(end_cur - query) - 1, 0, FALSE); /* - Here we assume table_cache_key always starts - with \0 terminated db name + Imagine the thread had created a temp table, then was doing a SELECT, and + the SELECT was killed. Then it's not clever to mark the statement above as + "killed", because it's not really a statement updating data, and there + are 99.99% chances it will succeed on slave. + If a real update (one updating a persistent table) was killed on the + master, then this real update will be logged with error_code=killed, + rightfully causing the slave to stop. */ - end = strxmov(end,"`",table->table_cache_key,"`.`", - table->real_name,"`,", NullS); + qinfo.error_code= 0; + mysql_bin_log.write(&qinfo); + } + else + { + next= table->next; + close_temporary(table, 1); } - next=table->next; - close_temporary(table); - } - if (query && found_user_tables && mysql_bin_log.is_open()) - { - /* The -1 is to remove last ',' */ - thd->clear_error(); - Query_log_event qinfo(thd, query, (ulong)(end-query)-1, 0, FALSE); - /* - Imagine the thread had created a temp table, then was doing a SELECT, and - the SELECT was killed. Then it's not clever to mark the statement above as - "killed", because it's not really a statement updating data, and there - are 99.99% chances it will succeed on slave. - If a real update (one updating a persistent table) was killed on the - master, then this real update will be logged with error_code=killed, - rightfully causing the slave to stop. - */ - qinfo.error_code= 0; - mysql_bin_log.write(&qinfo); } thd->temporary_tables=0; } |