diff --git a/mysql-test/suite/perfschema/r/binlog_stmt.result b/mysql-test/suite/perfschema/r/binlog_stmt.result index c3420296794..97899435987 100644 --- a/mysql-test/suite/perfschema/r/binlog_stmt.result +++ b/mysql-test/suite/perfschema/r/binlog_stmt.result @@ -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 diff --git a/sql/handler.cc b/sql/handler.cc index e1aad607803..2dfc929864c 100644 --- a/sql/handler.cc +++ b/sql/handler.cc @@ -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; diff --git a/sql/sql_base.cc b/sql/sql_base.cc index 60f9c4d5cea..1adaa20cd94 100644 --- a/sql/sql_base.cc +++ b/sql/sql_base.cc @@ -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); diff --git a/sql/sql_class.cc b/sql/sql_class.cc index 7aa4d703732..f7b6fc529b2 100644 --- a/sql/sql_class.cc +++ b/sql/sql_class.cc @@ -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 diff --git a/sql/sql_class.h b/sql/sql_class.h index 48799a62bb2..e38196c2aac 100644 --- a/sql/sql_class.h +++ b/sql/sql_class.h @@ -1852,7 +1852,45 @@ public: 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(). diff --git a/sql/table.cc b/sql/table.cc index 90ff98b55bf..a7d330636c9 100644 --- a/sql/table.cc +++ b/sql/table.cc @@ -2698,9 +2698,22 @@ partititon_err: 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; diff --git a/sql/table.h b/sql/table.h index aad0117b26a..2508c5f76da 100644 --- a/sql/table.h +++ b/sql/table.h @@ -1147,6 +1147,9 @@ public: */ 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;