一佣盒、問題描述
最近遇到問題肺樟,版本5.7線上訪問is.columns出現(xiàn)大量的警告,我們進行了模擬逻淌,當然模擬只模擬了一個警告么伯,如下,
二卡儒、問題分析
從報錯來看其中有字節(jié) \xBA\xC3 也就是0XBAC3無法被解析(這個碼點實際上是GBK的'好'字)田柔,而對于 information_schema.columns的字段COLUMN_COMMENT字符集為UTF8類型,因此實際上就是0XBAC3無法被解析為UTF8字符骨望,下面我將網(wǎng)上找到的UTF8編碼的范圍放出來如下硬爆,
可以看到不管是任何字節(jié)的UTF8編碼中第一個字節(jié),0xBA都不是其中任何字節(jié)編碼的第一個字節(jié)擎鸠,因此導(dǎo)致了這種報錯缀磕,這個檢測的代碼在函數(shù)field_well_formed_copy_nchars中。報錯的時候?qū)o法解析為UTF8編碼的二進制按照單個字節(jié)進行了輸出劣光,函數(shù)convert_to_printable負責完成這個輸出動作袜蚕,如下,
也就是我們看到的16進制的(\xBA\xC3 ) 绢涡。當然如果這里的GBK編碼能夠正常的解析為UTF8編碼牲剃,但是實際上它并不是UTF8的編碼的時候就會出現(xiàn)顯示亂碼,而不是報錯雄可。
但是這個報錯一般是在插入數(shù)據(jù)的時候報錯的凿傅,而這里并沒有插入操作,那么我們需要簡單看看在5.7中information_schema.columns到底是什么操作数苫,實際上這個視圖是一個memory表聪舒,當進行查詢的時候其中的數(shù)據(jù)會進行填充,而不是一直保存在那里面的虐急,其可能從frm文件和table share中讀取信息过椎,如果沒有table share就需要打開frm文件獲取了,既然要進行填充就有一個插入的過程戏仓,具體可以參考填充 information_schema.columns表的回調(diào)函數(shù)get_schema_column_record疚宇,也就是在這個插入的過程中進行了字符集編碼的檢測,導(dǎo)致了這個警告赏殃。當然這里我可以猜測當建表的時候?qū)τ赾omment 在5.7中并沒有進行嚴格的編碼檢測敷待,而是直接寫入到了frm文件中。
其次訪問直接information_schema.columns可能伴隨著大量的打開表的過程仁热,可能會對當前的table cache/table share cache等緩存造成沖擊榜揖,我們可以使用where語句過濾掉不需要訪問的表勾哩,減少沖擊的可能。
三举哟、測試方法和可能的原因
這里我們簡單用一個C代碼思劳,輸出以下就可以生成語句了,我們在comment中加入兩個二進制的字符妨猩,并且這兩個字符就是0xBAC3潜叛,實際上是'好'字的GBK編碼,并且這是不能被UTF8直接解析的編碼壶硅,
這種情況可能發(fā)生在注釋的編碼不正確威兜,比如注釋給的GBK的編碼,并且set names 為UTF8庐椒,這樣注釋在沒有任何轉(zhuǎn)換的情況下進行了存儲椒舵,也就是GBK編碼當做UTF8編碼進行了存儲,剛好UTF8編碼無法解析這個GBK的編碼约谈,如果這里我們使用GBK 顯示就是發(fā)現(xiàn)這是0xBAC3 實際上是一個好字笔宿。
四、insert中的類似報錯和8.0的測試
當然這個錯誤并不一定完全在這個場景下棱诱,更多可能是插入場景措伐,比如我們?nèi)缦聹y試,
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侥加,但是這種漢字是不可能轉(zhuǎn)換為latin1字符集的,雖然latin1為單字節(jié)粪躬,但是在校驗的時候會使用unicode編碼進行校驗担败,這樣就會出現(xiàn)報錯,因為latin1無法解析unicode編碼21435(去字的unicode碼點)镰官,
上面的案例 8.0中測試提前,發(fā)現(xiàn)有了變化,當建立的時候就會拋出錯誤警告泳唠,并且出現(xiàn)亂碼狈网,代表無法識別comment的字符集。如下笨腥,
因此我們在建表等操作的時候一定要注意到comment的字符集是否正確拓哺,建議輸入源的字符集無腦UTF8即可,并且set names中的3個字符集都選用UTF8(UTF8MB4)脖母。
其他
字典信息始終使用utf8進行存儲士鸥,受到set names的影響,比如comment為gbk編碼谆级,那么set names也要使用gbk編碼烤礁,否則會出現(xiàn)不能識別的情況讼积。
同字符集檢測字符是否符合字符集標準,也就是set names和字段的字符集相同
不同字符集轉(zhuǎn)換為unicode然后進行轉(zhuǎn)換脚仔,轉(zhuǎn)換的時候檢測字符集是否符合標準勤众,也就是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);
->
轉(zhuǎn)換關(guān)鍵函數(shù)
field_well_formed_copy_nchars to_cs from_cs
->well_formed_copy_nchars to_cs from_cs
重點進行轉(zhuǎn)換
my_mb_wc_utf8mb4
my_wc_mb_utf8mb4
my_mb_wc_gbk 就是用來實現(xiàn)講gbk字符轉(zhuǎn)換成unicode字符的函數(shù)
my_wc_mb_gbk 函數(shù)則是用來講unicode字符轉(zhuǎn)換成gbk字符的函數(shù)鲤脏。
push_warning_printf
存儲過程们颜,Default_object_creation_ctx::change_env
----- 其他案例 1
[root@gdb5722 tmp]# more sql2.sql
insert into testooo1 values('t); //gbk 編碼 好 UTF8顯示錯誤無法復(fù)制
這里是一個好字的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 要設(shè)置為GBK 這樣 轉(zhuǎn)碼能夠成功,
寫入
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個字節(jié)贡蓖,GBK解析2個字節(jié)后發(fā)現(xiàn)還存在一個字節(jié)曹鸠,因此這個字符過長。
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