主从同步失败---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 废弃使用存储过程

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