Commit bd6c4ef4 authored by unknown's avatar unknown
Browse files

Fix for BUG#2983 "If statement was killed on master, slave errors despite replicate-wild-ignore-t"

We introduce a new function mysql_test_parse_for_slave().
If the slave sees that the query got a really bad error on master
(killed e.g.), then it calls this function to know if this query
can be ignored because of replicate-*-table rules (do not worry
about replicate-*-db rules: they are checked so early that they have
no bug). If the answer is yes, it skips the query and continues. If
it's no, then it stops and say "fix your slave data manually" (like it
did before this change).


mysql-test/r/rpl_error_ignored_table.result:
  result update
mysql-test/t/rpl_error_ignored_table-slave.opt:
  ignore more tables
mysql-test/t/rpl_error_ignored_table.test:
  we test if a killed query on the master, is ignored on the slave
  if the tables it involves should be excluded because of replicate-*-table
  rules.
sql/log_event.cc:
  If the query got a really bad error on the master (thread killed etc),
  parse it to test the table names: if the replicate-*-do|ignore-table
  rules say "this query must be ignored" then we exit gracefully;
  otherwise we warn about the bad error and tell DBA to check/fix it.
  Before this change, we always warned and stopped.
sql/mysql_priv.h:
  new function
sql/slave.cc:
  don't print error immediately as we need to do one more test to
  be sure.
sql/sql_parse.cc:
  we add a function mysql_test_parse_for_slave(), to be used only
  by the slave if it wants to know if the query should be ignored or not;
  so this function only parses the query, does not execute it.
parent b1e0a38f
Loading
Loading
Loading
Loading
+25 −0
Original line number Diff line number Diff line
@@ -13,3 +13,28 @@ Master_Host Master_User Master_Port Connect_retry Master_Log_File Read_Master_Lo
show tables like 't1';
Tables_in_test (t1)
drop table t1;
select get_lock('crash_lock%20C', 10);
get_lock('crash_lock%20C', 10)
1
create table t2 (a int primary key);
insert into t2 values(1);
create table t3 (id int);
insert into t3 values(connection_id());
 update t2 set a = a + 1 + get_lock('crash_lock%20C', 10);
select (@id := id) - id from t3;
(@id := id) - id
0
kill @id;
drop table t2,t3;
Server shutdown in progress
show binlog events from 79;
Log_name	Pos	Event_type	Server_id	Orig_log_pos	Info
master-bin.001	79	Query	1	79	use `test`; create table t1 (a int primary key)
master-bin.001	149	Query	1	149	use `test`; insert into t1 values (1),(1)
master-bin.001	213	Query	1	213	use `test`; drop table t1
master-bin.001	261	Query	1	261	use `test`; create table t2 (a int primary key)
master-bin.001	331	Query	1	331	use `test`; insert into t2 values(1)
master-bin.001	390	Query	1	390	use `test`; create table t3 (id int)
master-bin.001	449	Query	1	449	use `test`; insert into t3 values(connection_id())
master-bin.001	522	Query	1	522	use `test`; update t2 set a = a + 1 + get_lock('crash_lock%20C', 10)
master-bin.001	613	Query	1	613	use `test`; drop table t2,t3
+1 −1
Original line number Diff line number Diff line
--replicate-ignore-table=test.t1
--replicate-ignore-table=test.t1 --replicate-ignore-table=test.t2 --replicate-ignore-table=test.t3
+30 −0
Original line number Diff line number Diff line
@@ -23,3 +23,33 @@ drop table t1;
save_master_pos;
connection slave;
sync_with_master;

# Now test that even critical errors (connection killed)
# are ignored if rules allow it.
# The "kill" idea was copied from rpl000001.test.

connection master1;
select get_lock('crash_lock%20C', 10);

connection master;
create table t2 (a int primary key);
insert into t2 values(1);
create table t3 (id int);
insert into t3 values(connection_id());
send update t2 set a = a + 1 + get_lock('crash_lock%20C', 10);

connection master1;
sleep 2;
select (@id := id) - id from t3;
kill @id;
drop table t2,t3;
connection master;
--error 1053;
reap;
connection master1;
show binlog events from 79;
save_master_pos;
connection slave;
# SQL slave thread should not have stopped (because table of the killed
# query is in the ignore list).
sync_with_master;
+87 −72
Original line number Diff line number Diff line
@@ -53,6 +53,14 @@ static void pretty_print_str(FILE* file, char* str, int len)

#ifndef MYSQL_CLIENT

static void clear_all_errors(THD *thd, struct st_relay_log_info *rli)
{
  thd->query_error = 0;
  thd->clear_error();
  *rli->last_slave_error = 0;
  rli->last_slave_errno = 0;
}

inline int ignored_error_code(int err_code)
{
  return ((err_code == ER_SLAVE_IGNORED_TABLE) ||
@@ -1803,8 +1811,7 @@ int Query_log_event::exec_event(struct st_relay_log_info* rli)
#else
  rli->future_group_master_log_pos= log_pos;
#endif
  thd->query_error= 0;			// clear error
  thd->clear_error();
  clear_all_errors(thd, rli);

  if (db_ok(thd->db, replicate_do_db, replicate_ignore_db))
  {
@@ -1817,16 +1824,36 @@ int Query_log_event::exec_event(struct st_relay_log_info* rli)
    VOID(pthread_mutex_unlock(&LOCK_thread_count));
    thd->slave_proxy_id = thread_id;		// for temp tables
	
    /*
      Sanity check to make sure the master did not get a really bad
      error on the query.
    */
    if (ignored_error_code((expected_error = error_code)) ||
	!check_expected_error(thd,rli,expected_error))
    {
    mysql_log.write(thd,COM_QUERY,"%s",thd->query);
    DBUG_PRINT("query",("%s",thd->query));
    if (ignored_error_code(expected_error = error_code) ||
        !check_expected_error(thd,rli,expected_error))
      mysql_parse(thd, thd->query, q_len);
    else
    {
      /*
        The query got a really bad error on the master (thread killed etc),
        which could be inconsistent. Parse it to test the table names: if the
        replicate-*-do|ignore-table rules say "this query must be ignored" then
        we exit gracefully; otherwise we warn about the bad error and tell DBA
        to check/fix it.
      */
      if (mysql_test_parse_for_slave(thd, thd->query, q_len))
        /* Can ignore query */
        clear_all_errors(thd, rli);
      else
      {
        slave_print_error(rli,expected_error, 
                          "query '%s' partially completed on the master \
(error on master: %d) \
and was aborted. There is a chance that your master is inconsistent at this \
point. If you are sure that your master is ok, run this query manually on the\
 slave and then restart the slave with SET GLOBAL SQL_SLAVE_SKIP_COUNTER=1;\
 START SLAVE; .", thd->query, expected_error);
        thd->query_error= 1;
      }
      goto end;
    }

    /*
      Set a flag if we are inside an transaction so that we can restart
@@ -1867,12 +1894,7 @@ Default database: '%s'",
    */
    else if (expected_error == actual_error ||
             ignored_error_code(actual_error))
      {
	thd->query_error = 0;
        thd->clear_error();
	*rli->last_slave_error = 0;
	rli->last_slave_errno = 0;
      }
      clear_all_errors(thd, rli);
    /*
      Other cases: mostly we expected no error and get one.
    */
@@ -1886,15 +1908,9 @@ Default database: '%s'",
                        print_slave_db_safe(db));
      thd->query_error= 1;
    }
    } 
    /* 
       End of sanity check. If the test was wrong, the query got a really bad
       error on the master, which could be inconsistent, abort and tell DBA to
       check/fix it. check_expected_error() already printed the message to
       stderr and rli, and set thd->query_error to 1.
    */
  } /* End of if (db_ok(... */

end:
  VOID(pthread_mutex_lock(&LOCK_thread_count));
  thd->db= 0;	                        // prevent db from being freed
  thd->query= 0;			// just to be sure
@@ -1939,8 +1955,7 @@ int Load_log_event::exec_event(NET* net, struct st_relay_log_info* rli,
  thd->db= (char*) rewrite_db(db);
  DBUG_ASSERT(thd->query == 0);
  thd->query = 0;				// Should not be needed
  thd->query_error = 0;
  thd->clear_error();
  clear_all_errors(thd, rli);

  if (!use_rli_only_for_errors)
  {
+1 −0
Original line number Diff line number Diff line
@@ -353,6 +353,7 @@ int quick_rm_table(enum db_type base,const char *db,
bool mysql_rename_tables(THD *thd, TABLE_LIST *table_list);
bool mysql_change_db(THD *thd,const char *name);
void mysql_parse(THD *thd,char *inBuf,uint length);
bool mysql_test_parse_for_slave(THD *thd,char *inBuf,uint length);
void mysql_init_select(LEX *lex);
bool mysql_new_select(LEX *lex);
void mysql_init_multi_delete(LEX *lex);
Loading