diff options
author | Alfranio Correia <alfranio.correia@oracle.com> | 2010-08-20 03:59:58 +0100 |
---|---|---|
committer | Alfranio Correia <alfranio.correia@oracle.com> | 2010-08-20 03:59:58 +0100 |
commit | ac6026ce2774222b4607fcad4e36bbd5c25ff60a (patch) | |
tree | 50a64e874f314240cba6e127b70048adb841cb30 /sql/sql_table.cc | |
parent | 7f98714247d601c353779d8615a5624cf00b5e6a (diff) | |
download | mariadb-git-ac6026ce2774222b4607fcad4e36bbd5c25ff60a.tar.gz |
BUG#53452 Inconsistent behavior of binlog_direct_non_transactional_updates with
temp table
This patch introduces two key changes in the replication's behavior.
Firstly, it reverts part of BUG#51894 which puts any update to temporary tables
into the trx-cache. Now, updates to temporary tables are handled according to
the type of their engines as a regular table.
Secondly, an unsafe mixed statement, (i.e. a statement that access transactional
table as well non-transactional or temporary table, and writes to any of them),
are written into the trx-cache in order to minimize errors in the execution when
the statement logging format is in use.
Such changes has a direct impact on which statements are classified as unsafe
statements and thus part of BUG#53259 is reverted.
Diffstat (limited to 'sql/sql_table.cc')
-rw-r--r-- | sql/sql_table.cc | 347 |
1 files changed, 202 insertions, 145 deletions
diff --git a/sql/sql_table.cc b/sql/sql_table.cc index 224de005af3..de492a14c78 100644 --- a/sql/sql_table.cc +++ b/sql/sql_table.cc @@ -1795,6 +1795,8 @@ end: clear_error is clear_error to be called query Query to log query_length Length of query + is_trans if the event changes either + a trans or non-trans engine. RETURN VALUES NONE @@ -1917,18 +1919,64 @@ int mysql_rm_table_part2(THD *thd, TABLE_LIST *tables, bool if_exists, String wrong_tables; int error= 0; int non_temp_tables_count= 0; - bool some_tables_deleted=0, tmp_table_deleted=0, foreign_key_error=0; + bool foreign_key_error=0; + bool non_tmp_error= 0; + bool trans_tmp_table_deleted= 0, non_trans_tmp_table_deleted= 0; + bool non_tmp_table_deleted= 0; String built_query; - String built_tmp_query; + String built_trans_tmp_query, built_non_trans_tmp_query; DBUG_ENTER("mysql_rm_table_part2"); - if (thd->is_current_stmt_binlog_format_row() && !dont_log_query) + /* + Prepares the drop statements that will be written into the binary + log as follows: + + 1 - If we are not processing a "DROP TEMPORARY" it prepares a + "DROP". + + 2 - A "DROP" may result in a "DROP TEMPORARY" but the opposite is + not true. + + 3 - If the current format is row, the IF EXISTS token needs to be + appended because one does not know if CREATE TEMPORARY was previously + written to the binary log. + + 4 - Add the IF_EXISTS token if necessary, i.e. if_exists is TRUE. + + 5 - For temporary tables, there is a need to differentiate tables + in transactional and non-transactional storage engines. For that, + reason, two types of drop statements are prepared. + + The need to different the type of tables when dropping a temporary + table stems from the fact that such drop does not commit an ongoing + transaction and changes to non-transactional tables must be written + ahead of the transaction in some circumstances. + */ + if (!dont_log_query) { - built_query.set_charset(system_charset_info); - if (if_exists) - built_query.append("DROP TABLE IF EXISTS "); + if (!drop_temporary) + { + built_query.set_charset(system_charset_info); + if (if_exists) + built_query.append("DROP TABLE IF EXISTS "); + else + built_query.append("DROP TABLE "); + } + + if (thd->is_current_stmt_binlog_format_row() || if_exists) + { + built_trans_tmp_query.set_charset(system_charset_info); + built_trans_tmp_query.append("DROP TEMPORARY TABLE IF EXISTS "); + built_non_trans_tmp_query.set_charset(system_charset_info); + built_non_trans_tmp_query.append("DROP TEMPORARY TABLE IF EXISTS "); + } else - built_query.append("DROP TABLE "); + { + built_trans_tmp_query.set_charset(system_charset_info); + built_trans_tmp_query.append("DROP TEMPORARY TABLE "); + built_non_trans_tmp_query.set_charset(system_charset_info); + built_non_trans_tmp_query.append("DROP TEMPORARY TABLE "); + } } mysql_ha_rm_tables(thd, tables); @@ -1997,6 +2045,7 @@ int mysql_rm_table_part2(THD *thd, TABLE_LIST *tables, bool if_exists, for (table= tables; table; table= table->next_local) { + bool is_trans; char *db=table->db; handlerton *table_type; enum legacy_db_type frm_db_type= DB_TYPE_UNKNOWN; @@ -2005,78 +2054,70 @@ int mysql_rm_table_part2(THD *thd, TABLE_LIST *tables, bool if_exists, table->db, table->table_name, (long) table->table, table->table ? (long) table->table->s : (long) -1)); + /* + drop_temporary_table may return one of the following error codes: + . 0 - a temporary table was successfully dropped. + . 1 - a temporary table was not found. + . -1 - a temporary table is used by an outer statement. + */ if (table->open_type == OT_BASE_ONLY) error= 1; - else - error= drop_temporary_table(thd, table); - - switch (error) { - case 0: - // removed temporary table - tmp_table_deleted= 1; + else if ((error= drop_temporary_table(thd, table, &is_trans)) == -1) + { + DBUG_ASSERT(thd->in_sub_stmt); + goto err; + } + + if ((drop_temporary && if_exists) || !error) + { /* - One needs to always log any temporary table drop if the current - statement logging format is set to row. This happens because one - might have created a temporary table while the statement logging - format was statement and then switched to mixed or row format. + This handles the case of temporary tables. We have the following cases: + + . "DROP TEMPORARY" was executed and a temporary table was affected + (i.e. drop_temporary && !error) or the if_exists was specified (i.e. + drop_temporary && if_exists). + + . "DROP" was executed but a temporary table was affected (.i.e + !error). */ - if (thd->is_current_stmt_binlog_format_row()) + if (!dont_log_query) { - if (built_tmp_query.is_empty()) - { - built_tmp_query.set_charset(system_charset_info); - built_tmp_query.append("DROP TEMPORARY TABLE IF EXISTS "); - } + /* + If there is an error, we don't know the type of the engine + at this point. So, we keep it in the trx-cache. + */ + is_trans= error ? TRUE : is_trans; + if (is_trans) + trans_tmp_table_deleted= TRUE; + else + non_trans_tmp_table_deleted= TRUE; - built_tmp_query.append("`"); + String *built_ptr_query= + (is_trans ? &built_trans_tmp_query : &built_non_trans_tmp_query); + /* + Don't write the database name if it is the current one (or if + thd->db is NULL). + */ + built_ptr_query->append("`"); if (thd->db == NULL || strcmp(db,thd->db) != 0) { - built_tmp_query.append(db); - built_tmp_query.append("`.`"); + built_ptr_query->append(db); + built_ptr_query->append("`.`"); } - built_tmp_query.append(table->table_name); - built_tmp_query.append("`,"); + built_ptr_query->append(table->table_name); + built_ptr_query->append("`,"); } - - continue; - case -1: - DBUG_ASSERT(thd->in_sub_stmt); - error= 1; - goto err; - default: - // temporary table not found - error= 0; - } - - /* Probably a non-temporary table. */ - if (!drop_temporary) - non_temp_tables_count++; - - /* - If row-based replication is used and the table is not a - temporary table, we add the table name to the drop statement - being built. The string always end in a comma and the comma - will be chopped off before being written to the binary log. - */ - if (!drop_temporary && thd->is_current_stmt_binlog_format_row() && !dont_log_query) - { /* - Don't write the database name if it is the current one (or if - thd->db is NULL). + This means that a temporary table was droped and as such there + is no need to proceed with the code that tries to drop a regular + table. */ - built_query.append("`"); - if (thd->db == NULL || strcmp(db,thd->db) != 0) - { - built_query.append(db); - built_query.append("`.`"); - } - - built_query.append(table->table_name); - built_query.append("`,"); + if (!error) continue; } - - if (!drop_temporary) + else if (!drop_temporary) { + non_temp_tables_count++; + if (thd->locked_tables_mode) { if (wait_while_table_is_used(thd, table->table, HA_EXTRA_FORCE_REOPEN)) @@ -2099,7 +2140,38 @@ int mysql_rm_table_part2(THD *thd, TABLE_LIST *tables, bool if_exists, reg_ext, table->internal_tmp_table ? FN_IS_TMP : 0); + + /* + This handles the case where a "DROP" was executed and a regular + table "may be" dropped as drop_temporary is FALSE and error is + TRUE. If the error was FALSE a temporary table was dropped and + regardless of the status of drop_tempoary a "DROP TEMPORARY" + must be used. + */ + if (!dont_log_query) + { + /* + Note that unless if_exists is TRUE or a temporary table was deleted, + there is no means to know if the statement should be written to the + binary log. See further information on this variable in what follows. + */ + non_tmp_table_deleted= (if_exists ? TRUE : non_tmp_table_deleted); + /* + Don't write the database name if it is the current one (or if + thd->db is NULL). + */ + built_query.append("`"); + if (thd->db == NULL || strcmp(db,thd->db) != 0) + { + built_query.append(db); + built_query.append("`.`"); + } + + built_query.append(table->table_name); + built_query.append("`,"); + } } + /* TODO: Investigate what should be done to remove this lock completely. Is exclusive meta-data lock enough ? @@ -2108,19 +2180,29 @@ int mysql_rm_table_part2(THD *thd, TABLE_LIST *tables, bool if_exists, DBUG_EXECUTE_IF("sleep_before_part2_delete_table", my_sleep(100000);); mysql_mutex_lock(&LOCK_open); + error= 0; if (drop_temporary || ((access(path, F_OK) && ha_create_table_from_engine(thd, db, alias)) || (!drop_view && dd_frm_type(thd, path, &frm_db_type) != FRMTYPE_TABLE))) { - // Table was not found on disk and table can't be created from engine + /* + One of the following cases happened: + . "DROP TEMPORARY" but a temporary table was not found. + . "DROP" but table was not found on disk and table can't be + created from engine. + . ./sql/datadict.cc +32 /Alfranio - TODO: We need to test this. + */ if (if_exists) push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_NOTE, ER_BAD_TABLE_ERROR, ER(ER_BAD_TABLE_ERROR), table->table_name); else + { + non_tmp_error = (drop_temporary ? non_tmp_error : TRUE); error= 1; + } } else { @@ -2153,7 +2235,7 @@ int mysql_rm_table_part2(THD *thd, TABLE_LIST *tables, bool if_exists, if (error == HA_ERR_ROW_IS_REFERENCED) { /* the table is referenced by a foreign key constraint */ - foreign_key_error=1; + foreign_key_error= 1; } if (!error || error == ENOENT || error == HA_ERR_NO_SUCH_TABLE) { @@ -2162,12 +2244,13 @@ int mysql_rm_table_part2(THD *thd, TABLE_LIST *tables, bool if_exists, strmov(end,reg_ext); if (!(new_error= mysql_file_delete(key_file_frm, path, MYF(MY_WME)))) { - some_tables_deleted=1; + non_tmp_table_deleted= TRUE; new_error= Table_triggers_list::drop_all_triggers(thd, db, table->table_name); } error|= new_error; } + non_tmp_error= error ? TRUE : non_tmp_error; } mysql_mutex_unlock(&LOCK_open); if (error) @@ -2183,11 +2266,12 @@ int mysql_rm_table_part2(THD *thd, TABLE_LIST *tables, bool if_exists, my_printf_error(ER_BAD_TABLE_ERROR, ER(ER_BAD_TABLE_ERROR), MYF(0), table->table_name);); - } DEBUG_SYNC(thd, "rm_table_part2_before_binlog"); - thd->thread_specific_used|= tmp_table_deleted; + thd->thread_specific_used|= (trans_tmp_table_deleted || + non_trans_tmp_table_deleted); error= 0; +err: if (wrong_tables.length()) { if (!foreign_key_error) @@ -2198,85 +2282,48 @@ int mysql_rm_table_part2(THD *thd, TABLE_LIST *tables, bool if_exists, error= 1; } - if (some_tables_deleted || tmp_table_deleted || !error) + if (non_trans_tmp_table_deleted || + trans_tmp_table_deleted || non_tmp_table_deleted) { query_cache_invalidate3(thd, tables, 0); if (!dont_log_query && mysql_bin_log.is_open()) { - if (!thd->is_current_stmt_binlog_format_row() || - (non_temp_tables_count > 0 && !tmp_table_deleted)) + if (non_trans_tmp_table_deleted) { - /* - In this case, we are either using statement-based - replication or using row-based replication but have only - deleted one or more non-temporary tables (and no temporary - tables). In this case, we can write the original query into - the binary log. - */ - error |= write_bin_log(thd, !error, thd->query(), thd->query_length()); + /* Chop of the last comma */ + built_non_trans_tmp_query.chop(); + built_non_trans_tmp_query.append(" /* generated by server */"); + error |= thd->binlog_query(THD::STMT_QUERY_TYPE, + built_non_trans_tmp_query.ptr(), + built_non_trans_tmp_query.length(), + FALSE, FALSE, FALSE, 0); } - else if (thd->is_current_stmt_binlog_format_row() && - tmp_table_deleted) + if (trans_tmp_table_deleted) { - if (non_temp_tables_count > 0) - { - /* - In this case we have deleted both temporary and - non-temporary tables, so: - - since we have deleted a non-temporary table we have to - binlog the statement, but - - since we have deleted a temporary table we cannot binlog - the statement (since the table may have not been created on the - slave - check "if" branch below, this might cause the slave to - stop). - - Instead, we write a built statement, only containing the - non-temporary tables, to the binary log - */ - built_query.chop(); // Chop of the last comma + /* Chop of the last comma */ + built_trans_tmp_query.chop(); + built_trans_tmp_query.append(" /* generated by server */"); + error |= thd->binlog_query(THD::STMT_QUERY_TYPE, + built_trans_tmp_query.ptr(), + built_trans_tmp_query.length(), + TRUE, FALSE, FALSE, 0); + } + if (non_tmp_table_deleted) + { + /* Chop of the last comma */ + built_query.chop(); built_query.append(" /* generated by server */"); - error|= write_bin_log(thd, !error, built_query.ptr(), built_query.length()); - } - - /* - One needs to always log any temporary table drop if the current - statement logging format is set to row. This happens because one - might have created a temporary table while the statement logging - format was statement and then switched to mixed or row format. - */ - if (thd->is_current_stmt_binlog_format_row()) - { - /* - In this case we have deleted some temporary tables but we are using - row based logging for the statement. However, thread uses mixed mode - format, thence we need to log the dropping as we cannot tell for - sure whether the create was logged as statement previously or not, ie, - before switching to row mode. - */ - built_tmp_query.chop(); // Chop of the last comma - built_tmp_query.append(" /* generated by server */"); - /* - We cannot call the write_bin_log as we do not care about any errors - in the master as the statement is always DROP TEMPORARY TABLE IF EXISTS - and as such there will be no errors in the slave. - */ - error|= thd->binlog_query(THD::STMT_QUERY_TYPE, built_tmp_query.ptr(), - built_tmp_query.length(), FALSE, FALSE, FALSE, - 0); - } + int error_code = (non_tmp_error ? + (foreign_key_error ? ER_ROW_IS_REFERENCED : ER_BAD_TABLE_ERROR) : 0); + error |= thd->binlog_query(THD::STMT_QUERY_TYPE, + built_query.ptr(), + built_query.length(), + TRUE, FALSE, FALSE, + error_code); } - - /* - The remaining cases are: - - no tables were deleted and - - only temporary tables were deleted and row-based - replication is used. - In both these cases, nothing should be written to the binary - log. - */ } } -err: + if (!drop_temporary) { /* @@ -3791,6 +3838,8 @@ void sp_prepare_create_field(THD *thd, Create_field *sql_field) internal_tmp_table Set to 1 if this is an internal temporary table (From ALTER TABLE) select_field_count + is_trans identifies the type of engine where the table + was created: either trans or non-trans. DESCRIPTION If one creates a temporary table, this is automatically opened @@ -3815,7 +3864,8 @@ bool mysql_create_table_no_lock(THD *thd, HA_CREATE_INFO *create_info, Alter_info *alter_info, bool internal_tmp_table, - uint select_field_count) + uint select_field_count, + bool *is_trans) { char path[FN_REFLEN + 1]; uint path_length; @@ -4180,12 +4230,17 @@ bool mysql_create_table_no_lock(THD *thd, if (create_info->options & HA_LEX_CREATE_TMP_TABLE) { + TABLE *table= NULL; /* Open table and put in temporary table list */ - if (!(open_temporary_table(thd, path, db, table_name, 1))) + if (!(table= open_temporary_table(thd, path, db, table_name, 1))) { (void) rm_temporary_table(create_info->db_type, path); goto unlock_and_end; } + + if (is_trans != NULL) + *is_trans= table->file->has_transactions(); + thd->thread_specific_used= TRUE; } @@ -4236,9 +4291,10 @@ bool mysql_create_table(THD *thd, TABLE_LIST *create_table, /* Got lock. */ DEBUG_SYNC(thd, "locked_table_name"); + bool is_trans; result= mysql_create_table_no_lock(thd, create_table->db, create_table->table_name, create_info, - alter_info, FALSE, 0); + alter_info, FALSE, 0, &is_trans); /* Don't write statement if: @@ -4250,7 +4306,7 @@ bool mysql_create_table(THD *thd, TABLE_LIST *create_table, (!thd->is_current_stmt_binlog_format_row() || (thd->is_current_stmt_binlog_format_row() && !(create_info->options & HA_LEX_CREATE_TMP_TABLE)))) - result= write_bin_log(thd, TRUE, thd->query(), thd->query_length()); + result= write_bin_log(thd, TRUE, thd->query(), thd->query_length(), is_trans); end: DBUG_RETURN(result); @@ -4456,9 +4512,10 @@ bool mysql_create_like_table(THD* thd, TABLE_LIST* table, TABLE_LIST* src_table, /* Reset auto-increment counter for the new table. */ local_create_info.auto_increment_value= 0; + bool is_trans; if ((res= mysql_create_table_no_lock(thd, table->db, table->table_name, &local_create_info, &local_alter_info, - FALSE, 0))) + FALSE, 0, &is_trans))) goto err; /* @@ -4539,7 +4596,7 @@ bool mysql_create_like_table(THD* thd, TABLE_LIST* table, TABLE_LIST* src_table, Case 3 and 4 does nothing under RBR */ } - else if (write_bin_log(thd, TRUE, thd->query(), thd->query_length())) + else if (write_bin_log(thd, TRUE, thd->query(), thd->query_length(), is_trans)) goto err; err: @@ -6228,7 +6285,7 @@ bool mysql_alter_table(THD *thd,char *new_db, char *new_name, error= mysql_create_table_no_lock(thd, new_db, tmp_name, create_info, alter_info, - 1, 0); + 1, 0, NULL); reenable_binlog(thd); if (error) goto err; |