一、问题描述
最近遇到问题,版本5.7线上访问is.columns出现大量的警告,我们进行了模拟,当然模拟只模拟了一个警告,如下,

二、问题分析
从报错来看其中有字节 \xBA\xC3 也就是0XBAC3无法被解析(这个码点实际上是GBK的'好'字),而对于 information_schema.columns的字段COLUMN_COMMENT字符集为UTF8类型,因此实际上就是0XBAC3无法被解析为UTF8字符,下面我将网上找到的UTF8编码的范围放出来如下,

可以看到不管是任何字节的UTF8编码中第一个字节,0xBA都不是其中任何字节编码的第一个字节,因此导致了这种报错,这个检测的代码在函数field_well_formed_copy_nchars中。报错的时候将无法解析为UTF8编码的二进制按照单个字节进行了输出,函数convert_to_printable负责完成这个输出动作,如下,

也就是我们看到的16进制的(\xBA\xC3 ) 。当然如果这里的GBK编码能够正常的解析为UTF8编码,但是实际上它并不是UTF8的编码的时候就会出现显示乱码,而不是报错。
但是这个报错一般是在插入数据的时候报错的,而这里并没有插入操作,那么我们需要简单看看在5.7中information_schema.columns到底是什么操作,实际上这个视图是一个memory表,当进行查询的时候其中的数据会进行填充,而不是一直保存在那里面的,其可能从frm文件和table share中读取信息,如果没有table share就需要打开frm文件获取了,既然要进行填充就有一个插入的过程,具体可以参考填充 information_schema.columns表的回调函数get_schema_column_record,也就是在这个插入的过程中进行了字符集编码的检测,导致了这个警告。当然这里我可以猜测当建表的时候对于comment 在5.7中并没有进行严格的编码检测,而是直接写入到了frm文件中。
其次访问直接information_schema.columns可能伴随着大量的打开表的过程,可能会对当前的table cache/table share cache等缓存造成冲击,我们可以使用where语句过滤掉不需要访问的表,减少冲击的可能。
三、测试方法和可能的原因
这里我们简单用一个C代码,输出以下就可以生成语句了,我们在comment中加入两个二进制的字符,并且这两个字符就是0xBAC3,实际上是'好'字的GBK编码,并且这是不能被UTF8直接解析的编码,


这种情况可能发生在注释的编码不正确,比如注释给的GBK的编码,并且set names 为UTF8,这样注释在没有任何转换的情况下进行了存储,也就是GBK编码当做UTF8编码进行了存储,刚好UTF8编码无法解析这个GBK的编码,如果这里我们使用GBK 显示就是发现这是0xBAC3 实际上是一个好字。

四、insert中的类似报错和8.0的测试
当然这个错误并不一定完全在这个场景下,更多可能是插入场景,比如我们如下测试,
mysql> CREATE TABLE `ttname` (
-> `name` varchar(20) DEFAULT NULL
-> ) ENGINE=InnoDB DEFAULT CHARSET=latin1;
Query OK, 0 rows affected (0.11 sec)
mysql> insert into ttname values('去');
ERROR 1366 (HY000): Incorrect string value: '\xE5\x8E\xBB' for column 'name' at row 1
而这里我输入的是UTF8的去字,其编码就是0XE58EBB,但是这种汉字是不可能转换为latin1字符集的,虽然latin1为单字节,但是在校验的时候会使用unicode编码进行校验,这样就会出现报错,因为latin1无法解析unicode编码21435(去字的unicode码点),

上面的案例 8.0中测试,发现有了变化,当建立的时候就会抛出错误警告,并且出现乱码,代表无法识别comment的字符集。如下,


因此我们在建表等操作的时候一定要注意到comment的字符集是否正确,建议输入源的字符集无脑UTF8即可,并且set names中的3个字符集都选用UTF8(UTF8MB4)。
其他
字典信息始终使用utf8进行存储,受到set names的影响,比如comment为gbk编码,那么set names也要使用gbk编码,否则会出现不能识别的情况。
同字符集检测字符是否符合字符集标准,也就是set names和字段的字符集相同
不同字符集转换为unicode然后进行转换,转换的时候检测字符集是否符合标准,也就是set names和字段的字符集不同。
set names 更改接口
set_var_collation_client::update
thd->variables.character_set_client= character_set_client;
thd->variables.character_set_results= character_set_results;
thd->variables.collation_connection= collation_connection;
->charset_is_system_charset
分别和
charset_is_system_charset = String::needs_conversion(0, variables.character_set_client, system_charset_info,¬_used);
charset_is_collation_connection = !String::needs_conversion(0,variables.character_set_client, variables.collation_connection,¬_used);
charset_is_character_set_filesystem = !String::needs_conversion(0,variables.character_set_client,variables.character_set_filesystem,¬_used);
->
转换关键函数
field_well_formed_copy_nchars to_cs from_cs
->well_formed_copy_nchars to_cs from_cs
重点进行转换
my_mb_wc_utf8mb4
my_wc_mb_utf8mb4
my_mb_wc_gbk 就是用来实现讲gbk字符转换成unicode字符的函数
my_wc_mb_gbk 函数则是用来讲unicode字符转换成gbk字符的函数。
push_warning_printf
存储过程,Default_object_creation_ctx::change_env
----- 其他案例 1
[root@gdb5722 tmp]# more sql2.sql
insert into testooo1 values('t); //gbk 编码 好 UTF8显示错误无法复制
这里是一个好字的GBK编码。't好t'。
mysql> set names utf8;
Query OK, 0 rows affected (0.00 sec)
mysql> source /tmp/sql2.sql
ERROR 1366 (HY000): Incorrect string value: '\xBA\xC3t' for column 'name' at row 1
mysql> show create table testooo1 \G
*************************** 1. row ***************************
Table: testooo1
Create Table: CREATE TABLE `testooo1` (
`name` varchar(20) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
1 row in set (0.00 sec)
mysql> set names gbk
-> ;
Query OK, 0 rows affected (0.00 sec)
mysql> source /tmp/sql2.sql
Query OK, 1 row affected (0.00 sec)
可以看到当以GBK 当做UTF8 插入到UTF8表中的时候还是要做UTF8编码的检测的,但是‘去’ 字的GBK 却能插入,因为他是UTF8编码的,检测通过
mysql> insert into test.testooo1 values('去');
Query OK, 1 row affected (0.00 sec)
mysql> select hex(name) from test.testooo1;
+------------+
| hex(name) |
+------------+
| C8A5 |
+------------+
6 rows in set (0.01 sec)
----- 其他案例 2
comment 如果是GBK编码那么set names 要设置为GBK 这样 转码能够成功,
写入
GBK -> GBK -> UTF8
输出
UTF8->GBK ->GBK
或者输出
UTF8->GBK ->GBK
mysql> select hex(COLUMN_COMMENT) from information_schema.columns where table_schema='t1' and table_name='test1' \G
*************************** 1. row ***************************
hex(COLUMN_COMMENT): 74E5A5BD74
1 row in set (0.00 sec)
mysql> select COLUMN_COMMENT from information_schema.columns where table_schema='t1' and table_name='test1' \G
*************************** 1. row ***************************
COLUMN_COMMENT: t好t
1 row in set (0.00 sec)
----- TODO:另外一个案例
5.7/8.0
mysql> show create table tm1123 \G
*************************** 1. row ***************************
Table: tm1123
Create Table: CREATE TABLE `tm1123` (
`name` varchar(20) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
1 row in set (0.00 sec)
set names gbk;
mysql> insert into tm1123(name) values('去');
ERROR 1406 (22001): Data too long for column 'name' at row 1
mysql> show variables like '%char%';
+--------------------------+----------------------------------------+
| Variable_name | Value |
+--------------------------+----------------------------------------+
| character_set_client | gbk |
| character_set_connection | gbk |
| character_set_database | utf8mb4 |
| character_set_filesystem | binary |
| character_set_results | gbk |
| character_set_server | utf8mb4 |
| character_set_system | utf8 |
| character_sets_dir | /opt/mysql5740/install/share/charsets/ |
+--------------------------+----------------------------------------+
可能原因,讲UTF8 当做GBK去解析,UTF8中文3个字节,GBK解析2个字节后发现还存在一个字节,因此这个字符过长。
UTF8->GBK->UTF8
#0 my_wc_mb_gbk (cs=0x2c47940 <my_charset_gbk_chinese_ci>, wc=21435, s=0x7fff4c01b071 "def\002t1\006tm1123\006tm1123\004name\004name\f\034",
e=0x7fff4c01b077 "\006tm1123\006tm1123\004name\004name\f\034") at /opt/mysql-5.7.40/strings/ctype-gbk.c:10695
#1 0x0000000001da7542 in my_convert_internal (to=0x7fff4c01b071 "def\002t1\006tm1123\006tm1123\004name\004name\f\034", to_length=6, to_cs=0x2c47940 <my_charset_gbk_chinese_ci>,
from=0x7fff4c3d581d "", from_length=3, from_cs=0x2cb0ba0 <my_charset_utf8mb4_general_ci>, errors=0x7fff705d7684) at /opt/mysql-5.7.40/strings/ctype.c:1006
#2 0x0000000001da7680 in my_convert (to=0x7fff4c01b071 "def\002t1\006tm1123\006tm1123\004name\004name\f\034", to_length=6, to_cs=0x2c47940 <my_charset_gbk_chinese_ci>, from=0x7fff4c3d581a "去",
from_length=3, from_cs=0x2cb0ba0 <my_charset_utf8mb4_general_ci>, errors=0x7fff705d7684) at /opt/mysql-5.7.40/strings/ctype.c:1082
#3 0x0000000000ead792 in copy_and_convert (to=0x7fff4c01b071 "def\002t1\006tm1123\006tm1123\004name\004name\f\034", to_length=6, to_cs=0x2c47940 <my_charset_gbk_chinese_ci>,
from=0x7fff4c3d581a "去", from_length=3, from_cs=0x2cb0ba0 <my_charset_utf8mb4_general_ci>, errors=0x7fff705d7684) at /opt/mysql-5.7.40/include/sql_string.h:135
#4 0x000000000143ff1f in Protocol_classic::net_store_data (this=0x7fff4c012c68, from=0x7fff4c3d581a "去", length=3, from_cs=0x2cb0ba0 <my_charset_utf8mb4_general_ci>,
to_cs=0x2c47940 <my_charset_gbk_chinese_ci>) at /opt/mysql-5.7.40/sql/protocol_classic.cc:116
#5 0x00000000014425f0 in Protocol_classic::store_string_aux (this=0x7fff4c012c68, from=0x7fff4c3d581a "去", length=3, fromcs=0x2cb0ba0 <my_charset_utf8mb4_general_ci>,
tocs=0x2c47940 <my_charset_gbk_chinese_ci>) at /opt/mysql-5.7.40/sql/protocol_classic.cc:1285
#6 0x00000000014427f2 in Protocol_text::store (this=0x7fff4c012c68, from=0x7fff4c3d581a "去", length=3, fromcs=0x2cb0ba0 <my_charset_utf8mb4_general_ci>,
tocs=0x2c47940 <my_charset_gbk_chinese_ci>) at /opt/mysql-5.7.40/sql/protocol_classic.cc:1314
#7 0x000000000144482e in Protocol_text::store (this=0x7fff4c012c68, from=0x7fff4c3d581a "去", length=3, cs=0x2cb0ba0 <my_charset_utf8mb4_general_ci>)
at /opt/mysql-5.7.40/sql/protocol_classic.h:229
网友评论