mirror of
https://github.com/MariaDB/server.git
synced 2025-07-29 05:21:33 +03:00
BUG#53259 Unsafe statement binlogged in statement format w/MyIsam temp tables
BUG#54872 MBR: replication failure caused by using tmp table inside transaction Changed criteria to classify a statement as unsafe in order to reduce the number of spurious warnings. So a statement is classified as unsafe when there is on-going transaction at any point of the execution if: 1. The mixed statement is about to update a transactional table and a non-transactional table. 2. The mixed statement is about to update a temporary transactional table and a non-transactional table. 3. The mixed statement is about to update a transactional table and read from a non-transactional table. 4. The mixed statement is about to update a temporary transactional table and read from a non-transactional table. 5. The mixed statement is about to update a non-transactional table and read from a transactional table when the isolation level is lower than repeatable read. After updating a transactional table if: 6. The mixed statement is about to update a non-transactional table and read from a temporary transactional table. 7. The mixed statement is about to update a non-transactional table and read from a temporary transactional table. 8. The mixed statement is about to update a non-transactionala table and read from a temporary non-transactional table. 9. The mixed statement is about to update a temporary non-transactional table and update a non-transactional table. 10. The mixed statement is about to update a temporary non-transactional table and read from a non-transactional table. 11. A statement is about to update a non-transactional table and the option variables.binlog_direct_non_trans_update is OFF. The reason for this is that locks acquired may not protected a concurrent transaction of interfering in the current execution and by consequence in the result. So the patch reduced the number of spurious unsafe warnings. Besides we fixed a regression caused by BUG#51894, which makes temporary tables to go into the trx-cache if there is an on-going transaction. In MIXED mode, the patch for BUG#51894 ignores that the trx-cache may have updates to temporary non-transactional tables that must be written to the binary log while rolling back the transaction. So we fix this problem by writing the content of the trx-cache to the binary log while rolling back a transaction if a non-transactional temporary table was updated and the binary logging format is MIXED.
This commit is contained in:
297
sql/sql_class.cc
297
sql/sql_class.cc
@ -493,7 +493,9 @@ THD::THD()
|
||||
rli_fake(0),
|
||||
lock_id(&main_lock_id),
|
||||
user_time(0), in_sub_stmt(0),
|
||||
binlog_unsafe_warning_flags(0), binlog_table_maps(0),
|
||||
binlog_unsafe_warning_flags(0),
|
||||
stmt_accessed_table_flag(0),
|
||||
binlog_table_maps(0),
|
||||
table_map_for_update(0),
|
||||
arg_of_last_insert_id_function(FALSE),
|
||||
first_successful_insert_id_in_prev_stmt(0),
|
||||
@ -537,7 +539,7 @@ THD::THD()
|
||||
count_cuted_fields= CHECK_FIELD_IGNORE;
|
||||
killed= NOT_KILLED;
|
||||
col_access=0;
|
||||
is_slave_error= thread_specific_used= thread_temporary_used= FALSE;
|
||||
is_slave_error= thread_specific_used= FALSE;
|
||||
my_hash_clear(&handler_tables_hash);
|
||||
tmp_table=0;
|
||||
used_tables=0;
|
||||
@ -3663,7 +3665,7 @@ int THD::decide_logging_format(TABLE_LIST *tables)
|
||||
capabilities.
|
||||
*/
|
||||
handler::Table_flags flags_write_some_set= 0;
|
||||
handler::Table_flags flags_some_set= 0;
|
||||
handler::Table_flags flags_access_some_set= 0;
|
||||
handler::Table_flags flags_write_all_set=
|
||||
HA_BINLOG_ROW_CAPABLE | HA_BINLOG_STMT_CAPABLE;
|
||||
|
||||
@ -3678,17 +3680,7 @@ int THD::decide_logging_format(TABLE_LIST *tables)
|
||||
Innodb and Falcon; Innodb and MyIsam.
|
||||
*/
|
||||
my_bool multi_access_engine= FALSE;
|
||||
/*
|
||||
If non-transactional and transactional engines are about
|
||||
to be accessed and any of them is about to be updated.
|
||||
For example: Innodb and MyIsam.
|
||||
*/
|
||||
my_bool trans_non_trans_access_engines= FALSE;
|
||||
/*
|
||||
If all engines that are about to be updated are
|
||||
transactional.
|
||||
*/
|
||||
my_bool all_trans_write_engines= TRUE;
|
||||
|
||||
TABLE* prev_write_table= NULL;
|
||||
TABLE* prev_access_table= NULL;
|
||||
|
||||
@ -3712,9 +3704,12 @@ int THD::decide_logging_format(TABLE_LIST *tables)
|
||||
{
|
||||
if (table->placeholder())
|
||||
continue;
|
||||
|
||||
if (table->table->s->table_category == TABLE_CATEGORY_PERFORMANCE)
|
||||
lex->set_stmt_unsafe(LEX::BINLOG_STMT_UNSAFE_SYSTEM_TABLE);
|
||||
|
||||
handler::Table_flags const flags= table->table->file->ha_table_flags();
|
||||
|
||||
DBUG_PRINT("info", ("table: %s; ha_table_flags: 0x%llx",
|
||||
table->table_name, flags));
|
||||
if (table->lock_type >= TL_WRITE_ALLOW_WRITE)
|
||||
@ -3722,177 +3717,171 @@ int THD::decide_logging_format(TABLE_LIST *tables)
|
||||
if (prev_write_table && prev_write_table->file->ht !=
|
||||
table->table->file->ht)
|
||||
multi_write_engine= TRUE;
|
||||
/*
|
||||
Every temporary table must be always written to the binary
|
||||
log in transaction boundaries and as such we artificially
|
||||
classify them as transactional.
|
||||
|
||||
Indirectly, this avoids classifying a temporary table created
|
||||
on a non-transactional engine as unsafe when it is modified
|
||||
after any transactional table:
|
||||
my_bool trans= table->table->file->has_transactions();
|
||||
|
||||
BEGIN;
|
||||
INSERT INTO innodb_t VALUES (1);
|
||||
INSERT INTO myisam_t_temp VALUES (1);
|
||||
COMMIT;
|
||||
if (table->table->s->tmp_table)
|
||||
set_stmt_accessed_table(trans ? STMT_WRITES_TEMP_TRANS_TABLE :
|
||||
STMT_WRITES_TEMP_NON_TRANS_TABLE);
|
||||
else
|
||||
set_stmt_accessed_table(trans ? STMT_WRITES_TRANS_TABLE :
|
||||
STMT_WRITES_NON_TRANS_TABLE);
|
||||
|
||||
BINARY LOG:
|
||||
|
||||
BEGIN;
|
||||
INSERT INTO innodb_t VALUES (1);
|
||||
INSERT INTO myisam_t_temp VALUES (1);
|
||||
COMMIT;
|
||||
*/
|
||||
all_trans_write_engines= all_trans_write_engines &&
|
||||
(table->table->file->has_transactions() ||
|
||||
table->table->s->tmp_table);
|
||||
prev_write_table= table->table;
|
||||
flags_write_all_set &= flags;
|
||||
flags_write_some_set |= flags;
|
||||
|
||||
prev_write_table= table->table;
|
||||
}
|
||||
flags_some_set |= flags;
|
||||
/*
|
||||
The mixture of non-transactional and transactional tables must
|
||||
identified and classified as unsafe. However, a temporary table
|
||||
must be always handled as a transactional table. Based on that,
|
||||
we have the following statements classified as mixed and by
|
||||
consequence as unsafe:
|
||||
flags_access_some_set |= flags;
|
||||
|
||||
1: INSERT INTO myisam_t SELECT * FROM innodb_t;
|
||||
|
||||
2: INSERT INTO innodb_t SELECT * FROM myisam_t;
|
||||
|
||||
3: INSERT INTO myisam_t SELECT * FROM myisam_t_temp;
|
||||
|
||||
4: INSERT INTO myisam_t_temp SELECT * FROM myisam_t;
|
||||
|
||||
5: CREATE TEMPORARY TABLE myisam_t_temp SELECT * FROM mysiam_t;
|
||||
|
||||
The following statements are not considered mixed and as such
|
||||
are safe:
|
||||
|
||||
1: INSERT INTO innodb_t SELECT * FROM myisam_t_temp;
|
||||
|
||||
2: INSERT INTO myisam_t_temp SELECT * FROM innodb_t_temp;
|
||||
*/
|
||||
if (!trans_non_trans_access_engines && prev_access_table &&
|
||||
(lex->sql_command != SQLCOM_CREATE_TABLE ||
|
||||
if (lex->sql_command != SQLCOM_CREATE_TABLE ||
|
||||
(lex->sql_command == SQLCOM_CREATE_TABLE &&
|
||||
(lex->create_info.options & HA_LEX_CREATE_TMP_TABLE))))
|
||||
(lex->create_info.options & HA_LEX_CREATE_TMP_TABLE)))
|
||||
{
|
||||
my_bool prev_trans;
|
||||
my_bool act_trans;
|
||||
if (prev_access_table->s->tmp_table || table->table->s->tmp_table)
|
||||
{
|
||||
prev_trans= prev_access_table->s->tmp_table ? TRUE :
|
||||
prev_access_table->file->has_transactions();
|
||||
act_trans= table->table->s->tmp_table ? TRUE :
|
||||
table->table->file->has_transactions();
|
||||
}
|
||||
my_bool trans= table->table->file->has_transactions();
|
||||
|
||||
if (table->table->s->tmp_table)
|
||||
set_stmt_accessed_table(trans ? STMT_READS_TEMP_TRANS_TABLE :
|
||||
STMT_READS_TEMP_NON_TRANS_TABLE);
|
||||
else
|
||||
{
|
||||
prev_trans= prev_access_table->file->has_transactions();
|
||||
act_trans= table->table->file->has_transactions();
|
||||
}
|
||||
trans_non_trans_access_engines= (prev_trans != act_trans);
|
||||
multi_access_engine= TRUE;
|
||||
set_stmt_accessed_table(trans ? STMT_READS_TRANS_TABLE :
|
||||
STMT_READS_NON_TRANS_TABLE);
|
||||
}
|
||||
thread_temporary_used |= table->table->s->tmp_table;
|
||||
|
||||
if (prev_access_table && prev_access_table->file->ht !=
|
||||
table->table->file->ht)
|
||||
multi_access_engine= TRUE;
|
||||
|
||||
prev_access_table= table->table;
|
||||
}
|
||||
|
||||
DBUG_PRINT("info", ("flags_write_all_set: 0x%llx", flags_write_all_set));
|
||||
DBUG_PRINT("info", ("flags_write_some_set: 0x%llx", flags_write_some_set));
|
||||
DBUG_PRINT("info", ("flags_some_set: 0x%llx", flags_some_set));
|
||||
DBUG_PRINT("info", ("flags_access_some_set: 0x%llx", flags_access_some_set));
|
||||
DBUG_PRINT("info", ("multi_write_engine: %d", multi_write_engine));
|
||||
DBUG_PRINT("info", ("multi_access_engine: %d", multi_access_engine));
|
||||
DBUG_PRINT("info", ("trans_non_trans_access_engines: %d",
|
||||
trans_non_trans_access_engines));
|
||||
|
||||
int error= 0;
|
||||
int unsafe_flags;
|
||||
|
||||
/*
|
||||
Set the statement as unsafe if:
|
||||
Classify a statement as unsafe when there is a mixed statement and an
|
||||
on-going transaction at any point of the execution if:
|
||||
|
||||
. it is a mixed statement, i.e. access transactional and non-transactional
|
||||
tables, and update any of them;
|
||||
1. The mixed statement is about to update a transactional table and
|
||||
a non-transactional table.
|
||||
|
||||
or:
|
||||
2. The mixed statement is about to update a temporary transactional
|
||||
table and a non-transactional table.
|
||||
|
||||
3. The mixed statement is about to update a transactional table and
|
||||
read from a non-transactional table.
|
||||
|
||||
. an early statement updated a transactional table;
|
||||
. and, the current statement updates a non-transactional table.
|
||||
4. The mixed statement is about to update a temporary transactional
|
||||
table and read from a non-transactional table.
|
||||
|
||||
Any mixed statement is classified as unsafe to ensure that mixed mode is
|
||||
completely safe. Consider the following example to understand why we
|
||||
decided to do this:
|
||||
5. The mixed statement is about to update a non-transactional table
|
||||
and read from a transactional table when the isolation level is
|
||||
lower than repeatable read.
|
||||
|
||||
Note that mixed statements such as
|
||||
After updating a transactional table if:
|
||||
|
||||
1: INSERT INTO myisam_t SELECT * FROM innodb_t;
|
||||
6. The mixed statement is about to update a non-transactional table
|
||||
and read from a temporary transactional table.
|
||||
|
||||
7. The mixed statement is about to update a non-transactional table
|
||||
and read from a temporary transactional table.
|
||||
|
||||
2: INSERT INTO innodb_t SELECT * FROM myisam_t;
|
||||
8. The mixed statement is about to update a non-transactionala table
|
||||
and read from a temporary non-transactional table.
|
||||
|
||||
9. The mixed statement is about to update a temporary non-transactional
|
||||
table and update a non-transactional table.
|
||||
|
||||
10. The mixed statement is about to update a temporary non-transactional
|
||||
table and read from a non-transactional table.
|
||||
|
||||
11. A statement is about to update a non-transactional table and the
|
||||
option variables.binlog_direct_non_trans_update is OFF.
|
||||
|
||||
3: INSERT INTO myisam_t SELECT * FROM myisam_t_temp;
|
||||
|
||||
4: INSERT INTO myisam_t_temp SELECT * FROM myisam_t;
|
||||
|
||||
5: CREATE TEMPORARY TABLE myisam_t_temp SELECT * FROM mysiam_t;
|
||||
|
||||
are classified as unsafe to ensure that in mixed mode the execution is
|
||||
completely safe and equivalent to the row mode. Consider the following
|
||||
statements and sessions (connections) to understand the reason:
|
||||
|
||||
con1: INSERT INTO innodb_t VALUES (1);
|
||||
con1: INSERT INTO innodb_t VALUES (100);
|
||||
|
||||
con1: BEGIN
|
||||
con2: INSERT INTO myisam_t SELECT * FROM innodb_t;
|
||||
con1: INSERT INTO innodb_t VALUES (200);
|
||||
con1: COMMIT;
|
||||
|
||||
The point is that the concurrent statements may be written into the binary log
|
||||
in a way different from the execution. For example,
|
||||
|
||||
BINARY LOG:
|
||||
|
||||
con2: BEGIN;
|
||||
con2: INSERT INTO myisam_t SELECT * FROM innodb_t;
|
||||
con2: COMMIT;
|
||||
con1: BEGIN
|
||||
con1: INSERT INTO innodb_t VALUES (200);
|
||||
con1: COMMIT;
|
||||
|
||||
....
|
||||
|
||||
or
|
||||
|
||||
BINARY LOG:
|
||||
|
||||
con1: BEGIN
|
||||
con1: INSERT INTO innodb_t VALUES (200);
|
||||
con1: COMMIT;
|
||||
con2: BEGIN;
|
||||
con2: INSERT INTO myisam_t SELECT * FROM innodb_t;
|
||||
con2: COMMIT;
|
||||
|
||||
Clearly, this may become a problem in STMT mode and setting the statement
|
||||
as unsafe will make rows to be written into the binary log in MIXED mode
|
||||
and as such the problem will not stand.
|
||||
|
||||
In STMT mode, although such statement is classified as unsafe, i.e.
|
||||
|
||||
INSERT INTO myisam_t SELECT * FROM innodb_t;
|
||||
|
||||
there is no enough information to avoid writing it outside the boundaries
|
||||
of a transaction. This is not a problem if we are considering snapshot
|
||||
isolation level but if we have pure repeatable read or serializable the
|
||||
lock history on the slave will be different from the master.
|
||||
The reason for this is that locks acquired may not protected a concurrent
|
||||
transaction of interfering in the current execution and by consequence in
|
||||
the result. In particular, if there is an on-going transaction and a
|
||||
transactional table was already updated, a temporary table must be written
|
||||
to the binary log in the boundaries of the on-going transaction and as
|
||||
such we artificially classify them as transactional.
|
||||
*/
|
||||
if (trans_non_trans_access_engines)
|
||||
lex->set_stmt_unsafe(LEX::BINLOG_STMT_UNSAFE_MIXED_STATEMENT);
|
||||
else if (trans_has_updated_trans_table(this) && !all_trans_write_engines)
|
||||
lex->set_stmt_unsafe(LEX::BINLOG_STMT_UNSAFE_NONTRANS_AFTER_TRANS);
|
||||
if (in_multi_stmt_transaction_mode())
|
||||
{
|
||||
my_bool mixed_unsafe= FALSE;
|
||||
my_bool non_trans_unsafe= FALSE;
|
||||
|
||||
/* Case 1. */
|
||||
if (stmt_accessed_table(STMT_WRITES_TRANS_TABLE) &&
|
||||
stmt_accessed_table(STMT_WRITES_NON_TRANS_TABLE))
|
||||
mixed_unsafe= TRUE;
|
||||
/* Case 2. */
|
||||
else if (stmt_accessed_table(STMT_WRITES_TEMP_TRANS_TABLE) &&
|
||||
stmt_accessed_table(STMT_WRITES_NON_TRANS_TABLE))
|
||||
mixed_unsafe= TRUE;
|
||||
/* Case 3. */
|
||||
else if (stmt_accessed_table(STMT_WRITES_TRANS_TABLE) &&
|
||||
stmt_accessed_table(STMT_READS_NON_TRANS_TABLE))
|
||||
mixed_unsafe= TRUE;
|
||||
/* Case 4. */
|
||||
else if (stmt_accessed_table(STMT_WRITES_TEMP_TRANS_TABLE) &&
|
||||
stmt_accessed_table(STMT_READS_NON_TRANS_TABLE))
|
||||
mixed_unsafe= TRUE;
|
||||
/* Case 5. */
|
||||
else if (stmt_accessed_table(STMT_WRITES_NON_TRANS_TABLE) &&
|
||||
stmt_accessed_table(STMT_READS_TRANS_TABLE) &&
|
||||
tx_isolation < ISO_REPEATABLE_READ)
|
||||
/*
|
||||
By default, InnoDB operates in REPEATABLE READ and with the option
|
||||
--innodb-locks-unsafe-for-binlog disabled. In this case, InnoDB uses
|
||||
next-key locks for searches and index scans, which prevents phantom
|
||||
rows.
|
||||
|
||||
This is scenario is safe for Innodb. However, there are no means to
|
||||
transparently get this information. Therefore, we need to improve this
|
||||
and change the storage engines to report somehow when an execution is
|
||||
safe under an isolation level & binary logging format.
|
||||
*/
|
||||
mixed_unsafe= TRUE;
|
||||
|
||||
if (trans_has_updated_trans_table(this))
|
||||
{
|
||||
/* Case 6. */
|
||||
if (stmt_accessed_table(STMT_WRITES_NON_TRANS_TABLE) &&
|
||||
stmt_accessed_table(STMT_READS_TRANS_TABLE))
|
||||
mixed_unsafe= TRUE;
|
||||
/* Case 7. */
|
||||
else if (stmt_accessed_table(STMT_WRITES_NON_TRANS_TABLE) &&
|
||||
stmt_accessed_table(STMT_READS_TEMP_TRANS_TABLE))
|
||||
mixed_unsafe= TRUE;
|
||||
/* Case 8. */
|
||||
else if (stmt_accessed_table(STMT_WRITES_NON_TRANS_TABLE) &&
|
||||
stmt_accessed_table(STMT_READS_TEMP_NON_TRANS_TABLE))
|
||||
mixed_unsafe= TRUE;
|
||||
/* Case 9. */
|
||||
else if (stmt_accessed_table(STMT_WRITES_TEMP_NON_TRANS_TABLE) &&
|
||||
stmt_accessed_table(STMT_WRITES_NON_TRANS_TABLE))
|
||||
mixed_unsafe= TRUE;
|
||||
/* Case 10. */
|
||||
else if (stmt_accessed_table(STMT_WRITES_TEMP_NON_TRANS_TABLE) &&
|
||||
stmt_accessed_table(STMT_READS_NON_TRANS_TABLE))
|
||||
mixed_unsafe= TRUE;
|
||||
/* Case 11. */
|
||||
else if (!variables.binlog_direct_non_trans_update &&
|
||||
stmt_accessed_table(STMT_WRITES_NON_TRANS_TABLE))
|
||||
non_trans_unsafe= TRUE;
|
||||
}
|
||||
|
||||
if (mixed_unsafe)
|
||||
lex->set_stmt_unsafe(LEX::BINLOG_STMT_UNSAFE_MIXED_STATEMENT);
|
||||
else if (non_trans_unsafe)
|
||||
lex->set_stmt_unsafe(LEX::BINLOG_STMT_UNSAFE_NONTRANS_AFTER_TRANS);
|
||||
}
|
||||
|
||||
/*
|
||||
If more than one engine is involved in the statement and at
|
||||
@ -3904,7 +3893,7 @@ int THD::decide_logging_format(TABLE_LIST *tables)
|
||||
(flags_write_some_set & HA_HAS_OWN_BINLOGGING))
|
||||
my_error((error= ER_BINLOG_MULTIPLE_ENGINES_AND_SELF_LOGGING_ENGINE),
|
||||
MYF(0));
|
||||
else if (multi_access_engine && flags_some_set & HA_HAS_OWN_BINLOGGING)
|
||||
else if (multi_access_engine && flags_access_some_set & HA_HAS_OWN_BINLOGGING)
|
||||
lex->set_stmt_unsafe(LEX::BINLOG_STMT_UNSAFE_MULTIPLE_ENGINES_AND_SELF_LOGGING_ENGINE);
|
||||
|
||||
/* both statement-only and row-only engines involved */
|
||||
|
Reference in New Issue
Block a user