BUG#53560 CREATE TEMP./DROP TEMP. are not binglogged correctly after a failed statement

This patch fixes two problems described as follows:

1 - If there is an on-going transaction and a temporary table is created or
dropped, any failed statement that follows the "create" or "drop commands"
triggers a rollback and by consequence the slave will go out sync because
the binary log will have a wrong sequence of events.

To fix the problem, we changed the expression that evaluates when the
cache should be flushed after either the rollback of a statment or
transaction.

2 - When a "CREATE TEMPORARY TABLE SELECT * FROM" was executed the
OPTION_KEEP_LOG was not set into the thd->options. For that reason, if
the transaction had updated only transactional engines and was rolled
back at the end (.e.g due to a deadlock) the changes were not written
to the binary log, including the creation of the temporary table.
      
To fix the problem, we have set the OPTION_KEEP_LOG into the thd->options
when a "CREATE TEMPORARY TABLE SELECT * FROM" is executed.

sql/log.cc:
  Reorganized the code based on the following functions:
  
  - bool ending_trans(const THD* thd, const bool all);
  - bool trans_has_updated_non_trans_table(const THD* thd);
  - bool trans_has_no_stmt_committed(const THD* thd, const bool all);
  - bool stmt_has_updated_non_trans_table(const THD* thd);
sql/log.h:
  Added functions to organize the code in log.cc.
sql/log_event.cc:
  Removed the OPTION_KEEP_LOG since it must be used only when
  creating and dropping temporary tables.
sql/log_event_old.cc:
  Removed the OPTION_KEEP_LOG since it must be used only when
  creating and dropping temporary tables.
sql/sql_parse.cc:
  When a "CREATE TEMPORARY TABLE SELECT * FROM" was executed the
  OPTION_KEEP_LOG was not set into the thd->options.
        
  To fix the problem, we have set the OPTION_KEEP_LOG into the
  thd->options when a "CREATE TEMPORARY TABLE SELECT * FROM"
  is executed.
This commit is contained in:
Alfranio Correia 2010-05-19 18:01:12 +01:00
commit 3f8bde448c
8 changed files with 190 additions and 41 deletions

View file

@ -1510,27 +1510,23 @@ static int binlog_commit(handlerton *hton, THD *thd, bool all)
}
/*
We commit the transaction if:
We flush the cache if:
- We are not in a transaction and committing a statement, or
- We are in a transaction and a full transaction is committed
- we are committing a transaction;
- and no statement was committed before and just non-transactional
tables were updated.
Otherwise, we accumulate the statement
*/
ulonglong const in_transaction=
thd->options & (OPTION_NOT_AUTOCOMMIT | OPTION_BEGIN);
DBUG_PRINT("debug",
("all: %d, empty: %s, in_transaction: %s, all.modified_non_trans_table: %s, stmt.modified_non_trans_table: %s",
("all: %d, empty: %s, all.modified_non_trans_table: %s, stmt.modified_non_trans_table: %s",
all,
YESNO(trx_data->empty()),
YESNO(in_transaction),
YESNO(thd->transaction.all.modified_non_trans_table),
YESNO(thd->transaction.stmt.modified_non_trans_table)));
if (!in_transaction || all ||
(!all && !trx_data->at_least_one_stmt_committed &&
!stmt_has_updated_trans_table(thd) &&
thd->transaction.stmt.modified_non_trans_table))
if (ending_trans(thd, all) ||
(trans_has_no_stmt_committed(thd, all) &&
!stmt_has_updated_trans_table(thd) && stmt_has_updated_non_trans_table(thd)))
{
Query_log_event qev(thd, STRING_WITH_LEN("COMMIT"), TRUE, TRUE, 0);
error= binlog_end_trans(thd, trx_data, &qev, all);
@ -1593,7 +1589,7 @@ static int binlog_rollback(handlerton *hton, THD *thd, bool all)
On the other hand, if a statement is transactional, we just safely roll it
back.
*/
if ((thd->transaction.stmt.modified_non_trans_table ||
if ((stmt_has_updated_non_trans_table(thd) ||
(thd->options & OPTION_KEEP_LOG)) &&
mysql_bin_log.check_write_error(thd))
trx_data->set_incident();
@ -1603,19 +1599,18 @@ static int binlog_rollback(handlerton *hton, THD *thd, bool all)
{
/*
We flush the cache with a rollback, wrapped in a beging/rollback if:
. aborting a transaction that modified a non-transactional table;
. aborting a transaction that modified a non-transactional table or
the OPTION_KEEP_LOG is activate.
. aborting a statement that modified both transactional and
non-transactional tables but which is not in the boundaries of any
transaction or there was no early change;
. the OPTION_KEEP_LOG is activate.
*/
if ((all && thd->transaction.all.modified_non_trans_table) ||
(!all && thd->transaction.stmt.modified_non_trans_table &&
!(thd->options & (OPTION_BEGIN | OPTION_NOT_AUTOCOMMIT))) ||
(!all && thd->transaction.stmt.modified_non_trans_table &&
!trx_data->at_least_one_stmt_committed &&
thd->current_stmt_binlog_row_based) ||
((thd->options & OPTION_KEEP_LOG)))
if ((ending_trans(thd, all) &&
(trans_has_updated_non_trans_table(thd) ||
(thd->options & OPTION_KEEP_LOG))) ||
(trans_has_no_stmt_committed(thd, all) &&
stmt_has_updated_non_trans_table(thd) &&
thd->current_stmt_binlog_row_based))
{
Query_log_event qev(thd, STRING_WITH_LEN("ROLLBACK"), TRUE, TRUE, 0);
error= binlog_end_trans(thd, trx_data, &qev, all);
@ -1624,8 +1619,8 @@ static int binlog_rollback(handlerton *hton, THD *thd, bool all)
Otherwise, we simply truncate the cache as there is no change on
non-transactional tables as follows.
*/
else if ((all && !thd->transaction.all.modified_non_trans_table) ||
(!all && !thd->transaction.stmt.modified_non_trans_table))
else if (ending_trans(thd, all) ||
(!(thd->options & OPTION_KEEP_LOG) && !stmt_has_updated_non_trans_table(thd)))
error= binlog_end_trans(thd, trx_data, 0, all);
}
if (!all)
@ -1721,7 +1716,7 @@ static int binlog_savepoint_rollback(handlerton *hton, THD *thd, void *sv)
non-transactional table. Otherwise, truncate the binlog cache starting
from the SAVEPOINT command.
*/
if (unlikely(thd->transaction.all.modified_non_trans_table ||
if (unlikely(trans_has_updated_non_trans_table(thd) ||
(thd->options & OPTION_KEEP_LOG)))
{
String log_query;
@ -3934,6 +3929,67 @@ bool MYSQL_BIN_LOG::is_query_in_union(THD *thd, query_id_t query_id_param)
query_id_param >= thd->binlog_evt_union.first_query_id);
}
/**
This function checks if a transaction, either a multi-statement
or a single statement transaction is about to commit or not.
@param thd The client thread that executed the current statement.
@param all Committing a transaction (i.e. TRUE) or a statement
(i.e. FALSE).
@return
@c true if committing a transaction, otherwise @c false.
*/
bool ending_trans(const THD* thd, const bool all)
{
return (all || (!all && !(thd->options &
(OPTION_BEGIN | OPTION_NOT_AUTOCOMMIT))));
}
/**
This function checks if a non-transactional table was updated by
the current transaction.
@param thd The client thread that executed the current statement.
@return
@c true if a non-transactional table was updated, @c false
otherwise.
*/
bool trans_has_updated_non_trans_table(const THD* thd)
{
return (thd->transaction.all.modified_non_trans_table ||
thd->transaction.stmt.modified_non_trans_table);
}
/**
This function checks if any statement was committed and cached.
@param thd The client thread that executed the current statement.
@param all Committing a transaction (i.e. TRUE) or a statement
(i.e. FALSE).
@return
@c true if at a statement was committed and cached, @c false
otherwise.
*/
bool trans_has_no_stmt_committed(const THD* thd, bool all)
{
binlog_trx_data *const trx_data=
(binlog_trx_data*) thd_get_ha_data(thd, binlog_hton);
return (!all && !trx_data->at_least_one_stmt_committed);
}
/**
This function checks if a non-transactional table was updated by the
current statement.
@param thd The client thread that executed the current statement.
@return
@c true if a non-transactional table was updated, @c false otherwise.
*/
bool stmt_has_updated_non_trans_table(const THD* thd)
{
return (thd->transaction.stmt.modified_non_trans_table);
}
/*
These functions are placed in this file since they need access to