MySQL:主从复制转换错误问题

2023年 8月 15日 27.2k 0

作者简介:高鹏,笔名八怪。《深入理解MySQL主从原理》图书作者,同时运营个人公众号“MySQL学习”,持续分享遇到的有趣case以及代码解析!

一、关于错误

总的来说这个错误还是比较常见,我们需要做的就是比较主从表的结构,这里主要对他的处理流程进行一个简单的解析。

二、错误抛错点

这个错误的抛错点为table_def::compatible_with函数,抛错类型为ER_SLAVE_CONVERSION_FAILED,抛错的时机为row log event在从库应用的时候会进行类型检查,见栈1。下列是抛错的部分:

<code class="language-js_darkmode__6">        rli->report(report_level, ER_SLAVE_CONVERSION_FAILED, //转换出错报错
                            ER(ER_SLAVE_CONVERSION_FAILED),
                            col, db_name, tbl_name,
                            source_type.c_ptr_safe(), target_type.c_ptr_safe());

这里我知道了上面的报错语句中varchar(80(bytes))为主库的类型和长度而varchar(20(bytes) latin1为从库的类型和长度,这里稍微观察一下可以发现,主库的信息中不包含字符集信息,而从库却包含了,但是从长度我们可以猜测出这里的80(bytes)可能为utf8mb4类型。主库的这些信息来自binlog,我们可以发现binlog中并没有每个字段的字符集信息,因此这里使用长度进行判定。

三、table_def的信息来源

这个信息在比较字段类型的时候起到了关键作用,如下:

  ulong m_size;           // Number of elements in the types array
  unsigned char *m_type;  // 字段类型  
  uint m_field_metadata_size;  
  uint16 *m_field_metadata; //可变字段长度信息  
  uchar *m_null_bits; // 字段是否可以为空的一个位图  
  uint16 m_flags;         // Table flags  
  uchar *m_memory;

我们发现这里实际上就是我们map event的信息,那么可以确认从库中本结构体的信息来源为map event,其中m_field_metadata为可变长度信息,那么这个信息的来源就是map event调用Field_varstring::do_save_field_metadata填入的,实际上他来自field_length这样一个值,这个值在建表的时候就已经加入字符集进行了计算,Create_field::create_length_to_internal_length负责进行转换见栈2,下面是一个varchar类型的转换:

<code class="language-js_darkmode__14">  case MYSQL_TYPE_VARCHAR:
      length*= charset->mbmaxlen;//这里就是进行了转换,如果是utf8mb4那么charset->mbmaxlen为4    
      key_length= length;    
      pack_length= calc_pack_length(sql_type, length);

四、比较的方式

上面我们已经知道通过map event读取了主库端表的信息,那么接下来就可以获取从库的字段信息与其进行比较了,如果比较出现问题,则抛错。

<code class="language-js_darkmode__18">table_def::compatible_with 函数调入

->通过从库表的信息,获取字段的总长度
  ->循环每一个字段
      ->调用can_convert_field_to,通过上面的table_def信息(map event代表主库的信息)和从库当前表的字段进行比较
             ->比较类型是否相同
                      ->如果相等,且没有metadata,则说明不是可变字段,则直接返回true         
                      ->如果存在metadata则比较长度,如果不相等则返回false
                                 我们刚才已经说了字段的长度已经加入了字符集,因此如果字符集不同即便同样的长度           
                                 这里也会比较不成功,当然这里受到参数slave_type_conversions的影响,但是这个参数            
                                 一般不会设置因此忽略掉,这里通过order字段返回比较是否相等。       
                       ->如果字段类型不同,但是metadata为0,也就是非可变类型,主要对timestamp,datetime,time几个类型进行转换判断 
                                如果能够转换也返回true       
                       ->最后对其他情况进行判定,但是其他情况中如果slave_type_conversions没有设置则直接返回为false    
               ->如果can_convert_field_to返回为false,那么则进入报错流程,进行报错信息的组装,单上从库表的字符集,一同报错。

这里我们主要讨论的是slave_type_conversions没有设置的情况,这也是通常的情况,如果设置了slave_type_conversions还有其他逻辑。其中is_conversion_ok函数主要是slave_type_conversions参数的实现,而order则是在slave_type_conversions设置了的情况下需要继续判断的一个返回值。

这里对于字段的长度比较也比较简单直接调了Field::compatible_field_size,简单做compare函数。

附录:栈

栈1:

#0  Field::compatible_field_size (this=0x7fff95812310, field_metadata=20, rli_arg=0x41eeb50, mflags=1, order_var=0x7fffd8082994)
    at /opt/percona-server-locks-detail-5.7.22/sql/field.cc:1805
#1  0x000000000182c074 in can_convert_field_to (field=0x7fff95812310, source_type=MYSQL_TYPE_VARCHAR, metadata=20, rli=0x41eeb50, mflags=1, order_var=0x7fffd8082994)
    at /opt/percona-server-locks-detail-5.7.22/sql/rpl_utility.cc:411
#2  0x000000000182c74f in table_def::compatible_with (this=0x7fff9572a660, thd=0x7fff958630e0, rli=0x41eeb50, table=0x7fff954f2d80, conv_table_var=0x7fffd8082ba8)
    at /opt/percona-server-locks-detail-5.7.22/sql/rpl_utility.cc:622
#3  0x00000000017e7e11 in Rows_log_event::do_apply_event (this=0x347e160, rli=0x41eeb50) at /opt/percona-server-locks-detail-5.7.22/sql/log_event.cc:11332
#4  0x00000000017f7c6e in Log_event::do_apply_event_worker (this=0x347e160, w=0x41eeb50) at /opt/percona-server-locks-detail-5.7.22/sql/log_event.cc:765
#5  0x00000000018720b0 in Slave_worker::slave_worker_exec_event (this=0x41eeb50, ev=0x347e160) at /opt/percona-server-locks-detail-5.7.22/sql/rpl_rli_pdb.cc:1890
#6  0x000000000187414e in slave_worker_exec_job_group (worker=0x41eeb50, rli=0x4081020) at /opt/percona-server-locks-detail-5.7.22/sql/rpl_rli_pdb.cc:2671
#7  0x000000000184bc29 in handle_slave_worker (arg=0x41eeb50) at /opt/percona-server-locks-detail-5.7.22/sql/rpl_slave.cc:6239
#8  0x00000000018ce0b2 in pfs_spawn_thread (arg=0x403d020) at /opt/percona-server-locks-detail-5.7.22/storage/perfschema/pfs.cc:2190
#9  0x00007ffff7bc6ea5 in start_thread () from /lib64/libpthread.so.0
#10 0x00007ffff66748dd in clone () from /lib64/libc.so.6

栈2:

#0  Create_field::create_length_to_internal_length (this=0x7fff94006c50) at /opt/percona-server-locks-detail-5.7.22/sql/field.cc:10397
#1  0x00000000015f9b85 in mysql_prepare_create_table (thd=0x7fff94000b70, error_schema_name=0x7fff94006b40 "testmap", error_table_name=0x7fff94006578 "test7", create_info=0x7fffdac349a0,
     alter_info=0x7fffdac34410, tmp_table=false, db_options=0x7fffdac33f58, file=0x7fff94006ee0, key_info_buffer=0x7fffdac34280, key_count=0x7fffdac3427c, select_field_count=0)
     at /opt/percona-server-locks-detail-5.7.22/sql/sql_table.cc:3882
#2  0x00000000015fdf3c in create_table_impl (thd=0x7fff94000b70, db=0x7fff94006b40 "testmap", table_name=0x7fff94006578 "test7", error_table_name=0x7fff94006578 "test7", 
    path=0x7fffdac34070 "./testmap/test7", create_info=0x7fffdac349a0, alter_info=0x7fffdac34410, internal_tmp_table=false, select_field_count=0, no_ha_table=false,
     is_trans=0x7fffdac342fe, key_info=0x7fffdac34280, key_count=0x7fffdac3427c) at /opt/percona-server-locks-detail-5.7.22/sql/sql_table.cc:5394
#3  0x00000000015feb09 in mysql_create_table_no_lock (thd=0x7fff94000b70, db=0x7fff94006b40 "testmap", table_name=0x7fff94006578 "test7", create_info=0x7fffdac349a0, 
    alter_info=0x7fffdac34410, select_field_count=0, is_trans=0x7fffdac342fe) at /opt/percona-server-locks-detail-5.7.22/sql/sql_table.cc:5675
#4  0x00000000015fec31 in mysql_create_table (thd=0x7fff94000b70, create_table=0x7fff940065b8, create_info=0x7fffdac349a0, alter_info=0x7fffdac34410)
    at /opt/percona-server-locks-detail-5.7.22/sql/sql_table.cc:5723
#5  0x000000000156aec5 in mysql_execute_command (thd=0x7fff94000b70, first_level=true) at /opt/percona-server-locks-detail-5.7.22/sql/sql_parse.cc:3429
#6  0x0000000001571bed in mysql_parse (thd=0x7fff94000b70, parser_state=0x7fffdac355b0) at /opt/percona-server-locks-detail-5.7.22/sql/sql_parse.cc:5901
#7  0x000000000156673d in dispatch_command (thd=0x7fff94000b70, com_data=0x7fffdac35d90, command=COM_QUERY) at /opt/percona-server-locks-detail-5.7.22/sql/sql_parse.cc:1490
#8  0x00000000015655c5 in do_command (thd=0x7fff94000b70) at /opt/percona-server-locks-detail-5.7.22/sql/sql_parse.cc:1021
#9  0x00000000016a635c in handle_connection (arg=0x3b9f360) at /opt/percona-server-locks-detail-5.7.22/sql/conn_handler/connection_handler_per_thread.cc:312
#10 0x00000000018ce0b2 in pfs_spawn_thread (arg=0x416c750) at /opt/percona-server-locks-detail-5.7.22/storage/perfschema/pfs.cc:2190
#11 0x00007ffff7bc6ea5 in start_thread () from /lib64/libpthread.so.0#12 0x

相关文章

Oracle如何使用授予和撤销权限的语法和示例
Awesome Project: 探索 MatrixOrigin 云原生分布式数据库
下载丨66页PDF,云和恩墨技术通讯(2024年7月刊)
社区版oceanbase安装
Oracle 导出CSV工具-sqluldr2
ETL数据集成丨快速将MySQL数据迁移至Doris数据库

发布评论