主從同步失敗---Illegal mix of collations

故障現象:

主從同步報錯,Error 'Illegal mix of collations (utf8mb4_unicode_ci,COERCIBLE), (latin1_swedish_ci,IMPLICIT), (utf8mb4_unicode_ci,COERCIBLE) for operation 'concat'' on query. Default database: 'hhrchina'. Query: 'INSERT INTO t_register_address (addressType, addressName)  VALUES ('sh_jincheng', concat('上海市金山區海豐路65號', NAME_CONST('i',4701),'室'))'

從庫手工執行該SQL,同樣報錯;
去主庫查找當前pos的binlog,確實是這條SQL,在主庫上執行,同樣報錯


原因分析:

版本爲官方mysql5.6.20,分別測試了以下幾種場景

mysql> SELECT NAME_CONST('TESasdT', 1);
+---------+
| TESasdT |
+---------+
|       1 |
+---------+
1 row in set (0.00 sec)


mysql> SELECT NAME_CONST('TESa阿斯頓sdT', 1);
+------------------+
| TESa阿斯頓sdT    |
+------------------+
|                1 |
+------------------+
1 row in set (0.00 sec)


mysql> select concat('我', NAME_CONST('TESa阿斯頓sdT', 1));
ERROR 1267 (HY000): Illegal mix of collations (utf8_general_ci,COERCIBLE) and (latin1_swedish_ci,IMPLICIT) for operation 'concat'
mysql> select concat('123', NAME_CONST('TESa阿斯頓sdT', 1));
+--------------------------------------------------+
| concat('123', NAME_CONST('TESa阿斯頓sdT', 1))    |
+--------------------------------------------------+
| 1231                                             |
+--------------------------------------------------+
1 row in set (0.00 sec)

看結果,只有第三種情況會出現亂碼,也就是concat和name_const混合使用,並且總監夾雜中英文

繼續分析,發現這條SQL實際是存儲過程中的SQL,但實際內容並不一樣,當主庫存儲過程執行完以後,寫到binlog裏的時候才添加了name_const,用於存儲過程變量值的替換。存儲過程定義如下

DECLARE i INT DEFAULT 4700;# can not be 0  
  
WHILE i<4800  
DO  
INSERT INTO t_register_address (addressType, addressName)  VALUES ('sh_jincheng', concat('上海市金山區海豐路65號',i,'室'));
SET i=i+1;  
END WHILE ;  
commit;  
  
END

分析到這裏,基本可以確定是存儲過程調用BUG,翻看了下源碼註釋,或許有一些幫助

/*
  StoredRoutinesBinlogging
  This paragraph applies only to statement-based binlogging. Row-based
  binlogging does not need anything special like this except for a special
  case that is mentioned below in section 2.1


  Top-down overview:


  1. Statements


  Statements that have is_update_query(stmt) == TRUE are written into the
  binary log verbatim.
  Examples:
    UPDATE tbl SET tbl.x = spfunc_w_side_effects()
    UPDATE tbl SET tbl.x=1 WHERE spfunc_w_side_effect_that_returns_false(tbl.y)


  Statements that have is_update_query(stmt) == FALSE (e.g. SELECTs) are not
  written into binary log. Instead we catch function calls the statement
  makes and write it into binary log separately (see #3).


  2. PROCEDURE calls



  CALL statements are not written into binary log. Instead
  * Any FUNCTION invocation (in SET, IF, WHILE, OPEN CURSOR and other SP
    instructions) is written into binlog separately.


  * Each statement executed in SP is binlogged separately, according to rules
    in #1, with the exception that we modify query string: we replace uses
    of SP local variables with NAME_CONST('spvar_name', <spvar-value>) calls.
    This substitution is done in subst_spvars().


  2.1 Miscellaneous case: DDLs (Eg: ALTER EVENT) in StoredProcedure(SP) uses
      its local variables


  * Irrespective of binlog format, DDLs are always binlogged in statement mode.
    Hence if there are any DDLs, in stored procedure, that uses SP local
    variables,  those should be replaced with NAME_CONST('spvar_name', <spvar-value>)
    even if binlog format is 'row'.

 4. Miscellaneous issues.


  4.1 User variables.


  When we call mysql_bin_log.write() for an SP statement, thd->user_var_events
  must hold set<{var_name, value}> pairs for all user variables used during
  the statement execution.
  This set is produced by tracking user variable reads during statement
  execution.


  For SPs, this has the following implications:
  1) thd->user_var_events may contain events from several SP statements and
     needs to be valid after execution of these statements was finished. In
     order to achieve that, we
     * Allocate user_var_events array elements on appropriate mem_root (grep
       for user_var_events_alloc).
     * Use is_query_in_union() to determine if user_var_event is created.


  2) We need to empty thd->user_var_events after we have wrote a function
     call. This is currently done by making
     reset_dynamic(&thd->user_var_events);
     calls in several different places. (TODO consider moving this into
     mysql_bin_log.write() function)

解決方法:

方法有兩個

1 將binlog_format改爲row

2 廢棄使用存儲過程

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章