Commit f4d5dacf authored by unknown's avatar unknown

Merge the following patch from MySQL 5.6.10, in order to make perfschema.binlog_* tests work.

revno: 4559
committer: Marc Alff <marc.alff@oracle.com>
branch nick: mysql-5.6-bug14741537-v4
timestamp: Thu 2012-11-08 22:40:31 +0100
message:
  Bug#14741537 - MYSQL 5.6, GTID AND PERFORMANCE_SCHEMA
  
  Before this fix, statements using performance_schema tables:
  - were marked as unsafe for replication,
  - did cause warnings during execution,
  - were written to the binlog, either in STATEMENT or ROW format.
  
  When using replication with the new GTID feature,
  unsafe warnings are elevated to errors,
  which prevents to use both the performance_schema and GTID together.
  
  The root cause of the problem is not related to raising warnings/errors
  in some special cases, but deeper: statements involving the performance
  schema should not even be written to the binary log in the first place,
  because the content of the performance schema tables is 'local' to a server
  instance, and may differ greatly between nodes in a replication
  topology.
  
  In particular, the DBA should be able to configure (INSERT, UPDATE, DELETE)
  or flush (TRUNCATE) performance schema tables on one node,
  without affecting other nodes.
  
  This fix introduces the concept of a 'non-replicated' or 'local' table,
  and adjusts the replication logic to ignore tables that are not replicated
  when deciding if or how to log a statement to the binlog.
  
  Note that while this issue was detected using the performance_schema,
  other tables are also affected by the same problem.
  
  This fix define as 'local' the following tables, which are then never
  replicated:
  - performance_schema.*
  - mysql.general_log
  - mysql.slow_log
  - mysql.slave_relay_log_info
  - mysql.slave_master_info
  - mysql.slave_worker_info
  
  Existing behavior for information_schema.* is unchanged by this fix,
  to limit the scope of changes.
  
  Coding wise, this fix implements the following changes:
  
  1)
  
  Performance schema tables are not using any replication flags,
  since performance schema tables are not replicated.
  
  2)
  
  In open_table_from_share(),
  tables with no replication capabilities (performance_schema.*),
  tables with TABLE_CATEGORY_LOG (logs)
  and tables with TABLE_CATEGORY_RPL_INFO (replication)
  are marked as non replicated, with TABLE::no_replicate
  
  3)
  
  A new THD member, THD::m_binlog_filter_state,
  indicate if the current statement is written to the binlog
  (normal cases for most statements), or is to be discarded
  (because the statements affects non replicated tables).
  
  4)
  
  In THD::decide_logging_format(), the replication logic
  is changed to take into account non replicated tables.
  
  Statements that affect only non replicated tables are
  executed normally (no warning or errors), but not written
  to the binlog.
  
  Statements that affect (i.e., write to) a replicated table
  while also using (i.e., reading from or writing to) a non replicated table
  are executed normally in MIXED and ROW binlog format,
  and cause a new error in STATEMENT binlog format.
  
  THD::decide_logging_format() uses THD::m_binlog_filter_state
  to indicate if a statement is to be ignored, when writing to
  the binlog.
  
  5)
  
  In THD::binlog_query(), statements marked as ignored
  are not written to the binary log.
  
  6)
  
  For row based replication, the existing test for 'table->no_replicate',
  has been moved from binlog_log_row() to check_table_binlog_row_based().
parent 3039a7c2
......@@ -7,8 +7,6 @@ count(*) > 0
update performance_schema.setup_instruments set enabled='NO'
where name like "wait/synch/rwlock/sql/%"
and name not in ("wait/synch/rwlock/sql/CRYPTO_dynlock_value::lock");
Warnings:
Note 1592 Unsafe statement written to the binary log using statement format since BINLOG_FORMAT = STATEMENT. The statement is unsafe because it uses the general log, slow query log, or performance_schema table(s). This is unsafe because system tables may differ on slaves.
select count(*) > 0 from performance_schema.events_waits_current;
count(*) > 0
1
......@@ -20,28 +18,19 @@ insert into test.t1
select thread_id from performance_schema.events_waits_current;
Warnings:
Note 1592 Unsafe statement written to the binary log using statement format since BINLOG_FORMAT = STATEMENT. The statement is unsafe because it uses the general log, slow query log, or performance_schema table(s). This is unsafe because system tables may differ on slaves.
Note 1592 Unsafe statement written to the binary log using statement format since BINLOG_FORMAT = STATEMENT. Mixing self-logging and non-self-logging engines in a statement is unsafe.
insert into test.t2
select name from performance_schema.setup_instruments
where name like "wait/synch/rwlock/sql/%"
and name not in ("wait/synch/rwlock/sql/CRYPTO_dynlock_value::lock");
Warnings:
Note 1592 Unsafe statement written to the binary log using statement format since BINLOG_FORMAT = STATEMENT. The statement is unsafe because it uses the general log, slow query log, or performance_schema table(s). This is unsafe because system tables may differ on slaves.
Note 1592 Unsafe statement written to the binary log using statement format since BINLOG_FORMAT = STATEMENT. Mixing self-logging and non-self-logging engines in a statement is unsafe.
drop table test.t1;
drop table test.t2;
update performance_schema.setup_instruments set enabled='YES'
where name like "wait/synch/rwlock/sql/%"
and name not in ("wait/synch/rwlock/sql/CRYPTO_dynlock_value::lock");
Warnings:
Note 1592 Unsafe statement written to the binary log using statement format since BINLOG_FORMAT = STATEMENT. The statement is unsafe because it uses the general log, slow query log, or performance_schema table(s). This is unsafe because system tables may differ on slaves.
include/show_binlog_events.inc
Log_name Pos Event_type Server_id End_log_pos Info
master-bin.000001 # Query # # BEGIN
master-bin.000001 # Query # # use `test`; update performance_schema.setup_instruments set enabled='NO'
where name like "wait/synch/rwlock/sql/%"
and name not in ("wait/synch/rwlock/sql/CRYPTO_dynlock_value::lock")
master-bin.000001 # Query # # COMMIT
master-bin.000001 # Query # # use `test`; DROP TABLE IF EXISTS `t1` /* generated by server */
master-bin.000001 # Query # # use `test`; DROP TABLE IF EXISTS `t2` /* generated by server */
master-bin.000001 # Query # # use `test`; create table test.t1 (thread_id integer)
......@@ -58,8 +47,3 @@ where name like "wait/synch/rwlock/sql/%"
master-bin.000001 # Query # # COMMIT
master-bin.000001 # Query # # use `test`; DROP TABLE `t1` /* generated by server */
master-bin.000001 # Query # # use `test`; DROP TABLE `t2` /* generated by server */
master-bin.000001 # Query # # BEGIN
master-bin.000001 # Query # # use `test`; update performance_schema.setup_instruments set enabled='YES'
where name like "wait/synch/rwlock/sql/%"
and name not in ("wait/synch/rwlock/sql/CRYPTO_dynlock_value::lock")
master-bin.000001 # Query # # COMMIT
......@@ -5383,6 +5383,7 @@ static bool check_table_binlog_row_based(THD *thd, TABLE *table)
if (table->s->cached_row_logging_check == -1)
{
int const check(table->s->tmp_table == NO_TMP_TABLE &&
! table->no_replicate &&
binlog_filter->db_ok(table->s->db.str));
table->s->cached_row_logging_check= check;
}
......@@ -5490,8 +5491,6 @@ static int binlog_log_row(TABLE* table,
const uchar *after_record,
Log_func *log_func)
{
if (table->no_replicate)
return 0;
bool error= 0;
THD *const thd= table->in_use;
......
......@@ -10207,7 +10207,7 @@ open_log_table(THD *thd, TABLE_LIST *one_table, Open_tables_backup *backup)
DBUG_ASSERT(table->s->table_category == TABLE_CATEGORY_LOG);
/* Make sure all columns get assigned to a default value */
table->use_all_columns();
table->no_replicate= 1;
DBUG_ASSERT(table->no_replicate);
}
else
thd->restore_backup_open_tables_state(backup);
......
......@@ -1314,6 +1314,7 @@ void THD::init(void)
tx_read_only= variables.tx_read_only;
update_charset();
reset_current_stmt_binlog_format_row();
reset_binlog_local_stmt_filter();
set_status_var_init();
bzero((char *) &org_status_var, sizeof(org_status_var));
......@@ -1972,6 +1973,14 @@ void THD::cleanup_after_query()
auto_inc_intervals_in_cur_stmt_for_binlog.empty();
rand_used= 0;
}
/*
Forget the binlog stmt filter for the next query.
There are some code paths that:
- do not call THD::decide_logging_format()
- do call THD::binlog_query(),
making this reset necessary.
*/
reset_binlog_local_stmt_filter();
if (first_successful_insert_id_in_cur_stmt > 0)
{
/* set what LAST_INSERT_ID() will return */
......@@ -4916,6 +4925,8 @@ int THD::decide_logging_format(TABLE_LIST *tables)
DBUG_PRINT("info", ("lex->get_stmt_unsafe_flags(): 0x%x",
lex->get_stmt_unsafe_flags()));
reset_binlog_local_stmt_filter();
/*
We should not decide logging format if the binlog is closed or
binlogging is off, or if the statement is filtered out from the
......@@ -4958,6 +4969,28 @@ int THD::decide_logging_format(TABLE_LIST *tables)
A pointer to a previous table that was accessed.
*/
TABLE* prev_access_table= NULL;
/**
The number of tables used in the current statement,
that should be replicated.
*/
uint replicated_tables_count= 0;
/**
The number of tables written to in the current statement,
that should not be replicated.
A table should not be replicated when it is considered
'local' to a MySQL instance.
Currently, these tables are:
- mysql.slow_log
- mysql.general_log
- mysql.slave_relay_log_info
- mysql.slave_master_info
- mysql.slave_worker_info
- performance_schema.*
- TODO: information_schema.*
In practice, from this list, only performance_schema.* tables
are written to by user queries.
*/
uint non_replicated_tables_count= 0;
#ifndef DBUG_OFF
{
......@@ -4980,14 +5013,38 @@ int THD::decide_logging_format(TABLE_LIST *tables)
if (table->placeholder())
continue;
if (table->table->s->table_category == TABLE_CATEGORY_PERFORMANCE ||
table->table->s->table_category == TABLE_CATEGORY_LOG)
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->table->no_replicate)
{
/*
The statement uses a table that is not replicated.
The following properties about the table:
- persistent / transient
- transactional / non transactional
- temporary / permanent
- read or write
- multiple engines involved because of this table
are not relevant, as this table is completely ignored.
Because the statement uses a non replicated table,
using STATEMENT format in the binlog is impossible.
Either this statement will be discarded entirely,
or it will be logged (possibly partially) in ROW format.
*/
lex->set_stmt_unsafe(LEX::BINLOG_STMT_UNSAFE_SYSTEM_TABLE);
if (table->lock_type >= TL_WRITE_ALLOW_WRITE)
{
non_replicated_tables_count++;
continue;
}
}
replicated_tables_count++;
if (table->lock_type >= TL_WRITE_ALLOW_WRITE)
{
if (prev_write_table && prev_write_table->file->ht !=
......@@ -5163,6 +5220,30 @@ int THD::decide_logging_format(TABLE_LIST *tables)
}
}
if (non_replicated_tables_count > 0)
{
if ((replicated_tables_count == 0) || ! is_write)
{
DBUG_PRINT("info", ("decision: no logging, no replicated table affected"));
set_binlog_local_stmt_filter();
}
else
{
if (! is_current_stmt_binlog_format_row())
{
my_error((error= ER_BINLOG_STMT_MODE_AND_NO_REPL_TABLES), MYF(0));
}
else
{
clear_binlog_local_stmt_filter();
}
}
}
else
{
clear_binlog_local_stmt_filter();
}
if (error) {
DBUG_PRINT("info", ("decision: no logging since an error was generated"));
DBUG_RETURN(-1);
......@@ -5788,6 +5869,15 @@ int THD::binlog_query(THD::enum_binlog_query_type qtype, char const *query_arg,
show_query_type(qtype), (int) query_len, query_arg));
DBUG_ASSERT(query_arg && mysql_bin_log.is_open());
if (get_binlog_local_stmt_filter() == BINLOG_FILTER_SET)
{
/*
The current statement is to be ignored, and not written to
the binlog. Do not call issue_unsafe_warnings().
*/
DBUG_RETURN(0);
}
/*
If we are not in prelocked mode, mysql_unlock_tables() will be
called after this binlog_query(), so we have to flush the pending
......
......@@ -1852,7 +1852,45 @@ class THD :public Statement,
return current_stmt_binlog_format == BINLOG_FORMAT_ROW;
}
enum binlog_filter_state
{
BINLOG_FILTER_UNKNOWN,
BINLOG_FILTER_CLEAR,
BINLOG_FILTER_SET
};
inline void reset_binlog_local_stmt_filter()
{
m_binlog_filter_state= BINLOG_FILTER_UNKNOWN;
}
inline void clear_binlog_local_stmt_filter()
{
DBUG_ASSERT(m_binlog_filter_state == BINLOG_FILTER_UNKNOWN);
m_binlog_filter_state= BINLOG_FILTER_CLEAR;
}
inline void set_binlog_local_stmt_filter()
{
DBUG_ASSERT(m_binlog_filter_state == BINLOG_FILTER_UNKNOWN);
m_binlog_filter_state= BINLOG_FILTER_SET;
}
inline binlog_filter_state get_binlog_local_stmt_filter()
{
return m_binlog_filter_state;
}
private:
/**
Indicate if the current statement should be discarded
instead of written to the binlog.
This is used to discard special statements, such as
DML or DDL that affects only 'local' (non replicated)
tables, such as performance_schema.*
*/
binlog_filter_state m_binlog_filter_state;
/**
Indicates the format in which the current statement will be
logged. This can only be set from @c decide_logging_format().
......
......@@ -2698,9 +2698,22 @@ int open_table_from_share(THD *thd, TABLE_SHARE *share, const char *alias,
bzero((char*) bitmaps, bitmap_size*3);
#endif
outparam->no_replicate= outparam->file &&
test(outparam->file->ha_table_flags() &
HA_HAS_OWN_BINLOGGING);
if (share->table_category == TABLE_CATEGORY_LOG)
{
outparam->no_replicate= TRUE;
}
else if (outparam->file)
{
handler::Table_flags flags= outparam->file->ha_table_flags();
outparam->no_replicate= ! test(flags & (HA_BINLOG_STMT_CAPABLE
| HA_BINLOG_ROW_CAPABLE))
|| test(flags & HA_HAS_OWN_BINLOGGING);
}
else
{
outparam->no_replicate= FALSE;
}
thd->status_var.opened_tables++;
thd->lex->context_analysis_only= save_context_analysis_only;
......
......@@ -1147,6 +1147,9 @@ struct TABLE
*/
bool key_read;
bool no_keyread;
/**
If set, indicate that the table is not replicated by the server.
*/
bool locked_by_logger;
bool no_replicate;
bool locked_by_name;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment