MySQL数据导入导出乱码问题

本文涉及的产品
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
RDS MySQL Serverless 高可用系列,价值2615元额度,1个月
云数据库 RDS PostgreSQL,高可用系列 2核4GB
简介: 数据库数据导入导出是经常发生的操作,但是有的时候却眼看一个看的懂的数据导入或者导出来,发现看不懂了,成了乱码,这是什么情况?

场景

  • 程序使用gbk编码,表使用的是latin1编码,而我再一次倒入数据的操作中使用了utf8的终端,指定--default-character-set='latin1'倒入的数据是乱码,而后来将终端换成gbk之后酒倒入成功了
  • 通过变换插入数据的终端,模拟我们平常需要倒入数据的终端
  • 通过变更查询数据的终端,来模拟我们程序的查询操作
  • default-character-set变更能够正确的读取中文字符

测试环境

  • mysql server和Linux是utf8的字符集
  • 使用xshell作为终端进行输入
  • 建立一张表存储字符集是latin1
  • 使用mysqlclient 进行插入和查询,查看查询到的数据是否正确

实验步骤

  1. 使用mysqlclient,--default-character-set='latin1' 这个选测进行测试,看看他到底改变了那些字符集,如下图所示
    
    [root@5kh4z42 goufu]#  mysql -u superdba -padmin -S /tmp/mysql3443.sock   -e 'show variables like "%char%"';
    
    +--------------------------+----------------------------------------------+
    
    | Variable_name            | Value                                        |
    
    +--------------------------+----------------------------------------------+
    
    | character_set_client     | utf8                                         |
    
    | character_set_connection | utf8                                         |
    
    | character_set_database   | utf8                                         |
    
    | character_set_filesystem | binary                                       |
    
    | character_set_results    | utf8                                         |
    
    | character_set_server     | utf8                                         |
    
    | character_set_system     | utf8                                         |
    
    | character_sets_dir       | /usr/local/xywy/mysql-5.5.38/share/charsets/ |
    
    +--------------------------+----------------------------------------------+
    
    [root@5kh4z42 goufu]#  mysql -u superdba -padmin -S /tmp/mysql3443.sock  --default-character-set='latin1' -e 'show variables like "%char%"';
    
    +--------------------------+----------------------------------------------+
    
    | Variable_name            | Value                                        |
    
    +--------------------------+----------------------------------------------+
    
    | character_set_client     | latin1                                       |
    
    | character_set_connection | latin1                                       |
    
    | character_set_database   | utf8                                         |
    
    | character_set_filesystem | binary                                       |
    
    | character_set_results    | latin1                                       |
    
    | character_set_server     | utf8                                         |
    
    | character_set_system     | utf8                                         |
    
    | character_sets_dir       | /usr/local/xywy/mysql-5.5.38/share/charsets/ |
    
    +--------------------------+----------------------------------------------+
    

  2. 这里可以看出具体的client和connection与results是取决于client的,默认是和文件系统统一的
  3. 创建一张latin1字符集的表进行测试,看看mysql如何对字符集进行转换,老的或者奇葩业务如何使用latin1存储中文
    CREATE TABLE `aa` (
    
    `id` int(11) DEFAULT NULL,
    
    `name` varchar(100) DEFAULT NULL
    
    ) ENGINE=InnoDB DEFAULT CHARSET=latin1

  4. 插入测试数据
    终端切换为UTF8字符集,执行命令
    mysql -u superdba -padmin -S /tmp/mysql.sock  --default-character-set='latin1' -e 'insert into  test.aa values(1,"啊啊")';
    终端切换为GBK字符集,执行命令
    mysql -u superdba -padmin -S /tmp/mysql.sock  --default-character-set='latin1' -e 'insert into  test.aa values(2,"啊啊")';
    终端切换为UTF8字符集,执行命令
    mysql -u superdba -padmin -S /tmp/mysql.sock   -e 'insert into  test.aa values(3,"啊啊")';
    终端切换为GBK字符集,执行命令
    mysql -u superdba -padmin -S /tmp/mysql.sock   -e 'insert into  test.aa values(4,"啊啊")';

  5. 查询数据
    终端切换为UTF8字符集,执行命令
    [root@5kh4z42 goufu]# mysql -u superdba -padmin -S /tmp/mysql3443.sock  --default-character-set='latin1' -e 'select * from test.aa';
    
    +------+--------+
    
    | id   | name   |
    
    +------+--------+
    
    |    1 | 啊啊 |
    
    |    2 | °¡°¡   |
    
    |    3 | ????   |
    
    |    4 | ??     |
    
    +------+--------+
    
    终端切换为GBK字符集,执行命令
    
    [root@5kh4z42 goufu]# mysql -u superdba -padmin -S /tmp/mysql3443.sock  --default-character-set='latin1' -e 'select * from test.aa';
    
    +------+--------+
    
    | id   | name   |
    
    +------+--------+
    
    |    1 | 鍟婂晩 |
    
    |    2 | 啊啊   |
    
    |    3 | ????   |
    
    |    4 | ??     |
    
    +------+--------+
    
    终端切换为UTF8字符集,执行命令
    
    [root@5kh4z42 goufu]# mysql -u superdba -padmin -S /tmp/mysql3443.sock   -e 'select * from test.aa';
    
    +------+----------------+
    
    | id   | name           |
    
    +------+----------------+
    
    |    1 | 啊啊         |
    
    |    2 | °¡°¡           |
    
    |    3 | ????           |
    
    |    4 | ??             |
    
    +------+----------------+
    
    终端切换为GBK字符集,执行命令
    
    [root@5kh4z42 goufu]# mysql -u superdba -padmin -S /tmp/mysql3443.sock   -e 'select * from test.aa';
    
    +------+----------------+
    
    | id   | name           |
    
    +------+----------------+
    
    |    1 | 氓鈥⑴犆モ€⑴?        |
    
    |    2 | 掳隆掳隆           |
    
    |    3 | ????           |
    
    |    4 | ??             |
    
    +------+----------------+

  6. 排查问题
    查看数据库底层存储的值,执行如下命令
    
    [root@5kh4z42 goufu]# mysql -u superdba -padmin -S /tmp/mysql3443.sock   -e 'select id,hex(name) from test.aa';
    
    +------+--------------+
    
    | id   | hex(name)    |
    
    +------+--------------+
    
    |    1 | E5958AE5958A |
    
    |    2 | B0A1B0A1     |
    
    |    3 | 3F3F3F3F     |
    
    |    4 | 3F3F         |
    
    +------+--------------+

    • 问题到这里其实有一些清晰了,结合我们之前的只是utf8存储汉字是3字节存储,即2^8^3,换算成十六进制就是2^4^2^3即6个16位数表示,所以'啊' 对应的编码是%E5%95%8A,同理算出并验证GBK编码的'啊' 的编码是%B0%A1,如我们所看到的3F其实就是?的编码,urf8和gbk是一样的

    • 这里结合之前的只是,得知latin1是单字节编码,如果在存储的时候他识别不了会按照单自己存储,所以将他存储的二进制码交给其他的字符集就可以进行复原,试验中换成了ascii码得到的结果也是一样的,这可能就是单字节码的特性。
  7. 既然单字节存在这种转换规律那么gbk和utf8 之间是怎样进行转换的呢
    • 首先,创建一张表结构如下

      create table cc (
      
      id int ,
      
      name varchar(100),
      
      terminal varchar(100),
      
      `client` varchar(100)
      
      ) charset=gbk;

    • 然后,我们分别使用utf8终端执行如下命令

      mysql -u superdba -pguzaneyR@cj7M6m -S /tmp/mysql3443.sock  --default-character-set='gbk' -e 'insert into  test_liuyaxin.cc values(1,"啊啊","utf8","gbk")';
      
      mysql -u superdba -pguzaneyR@cj7M6m -S /tmp/mysql3443.sock   -e 'insert into  test_liuyaxin.cc values(1,"啊啊","utf8","utf8")';
      

    • 然后在gbk终端下执行命令

      mysql -u superdba -pguzaneyR@cj7M6m -S /tmp/mysql3443.sock  --default-character-set='gbk' -e 'insert into  test_liuyaxin.cc values(2,"啊啊","gbk","gbk")';
      
      mysql -u superdba -pguzaneyR@cj7M6m -S /tmp/mysql3443.sock   -e 'insert into  test_liuyaxin.cc values(2,"啊啊","gbk","utf8")';
      

    • 最后使用utf8终端查询结果

      mysql -u superdba -pguzaneyR@cj7M6m -S /tmp/mysql3443.sock  -e 'select *,hex(name) from  test_liuyaxin.cc';
      
      +------+-----------+----------+--------+--------------+
      
      | id   | name      | terminal | client | hex(name)    |
      
      +------+-----------+----------+--------+--------------+
      
      |    1 | 鍟婂晩    | utf8     | gbk    | E5958AE5958A |
      
      |    1 | 啊啊      | utf8     | utf8   | B0A1B0A1     |
      
      |    2 | 啊啊      | gbk      | gbk    | B0A1B0A1     |
      
      |    2 | ????      | gbk      | utf8   | 3F3F3F3F     |
      
      +------+-----------+----------+--------+--------------+

    • 发现了当终端-->client-->egine这个过程中,如果进行多次编码转换,最后就会是乱码,而且无药可救,因为在第二次编码转换的时候就已经失去了原来的字符的含义,而当这个过程中只有一次转换的时候就算是乱码,他仍然可以存储下来,这样在反向解码的时候就可以还原,而单字节码如果出现不识别字符则会按照传递给他的编码进行存储,这样在反向解码的时候就可以识别出来

    • 这样也可以看出来文件系统应该是不参与解码工作的,而是让两个进程(shell session和mysqlclient)进行编码转换,自己只是在中间将二进制码进行传递,其实mysql变量character_set_filesystem的值为binary时,表示文件系统字符集只负责将传递二进制数据
  8. 回归正题
    • 既然说到导入导出的乱码问题,说到现在,其实解决这个问题的根源就在于,当你想吧备份文件进行恢复的时候,首先你需要能够正确的识别文件,即你传到服务器上的文件不是乱码,而且要与你程序所使用的字符集一致,而你所使用的倒入mysql 数据库的client 的字符集要和你底层的表的字符集相同,这样就不会出现乱码,前提是需要你使用单字节编码
    • 当然,说这么多,只是为了解释这样存储的可能性,并且解答了疑问,但是并不是代表着推荐这么做,对于数据库,当然还是希望字符集进行统一,这样也就生了很多的麻烦
相关实践学习
每个IT人都想学的“Web应用上云经典架构”实战
本实验从Web应用上云这个最基本的、最普遍的需求出发,帮助IT从业者们通过“阿里云Web应用上云解决方案”,了解一个企业级Web应用上云的常见架构,了解如何构建一个高可用、可扩展的企业级应用架构。
MySQL数据库入门学习
本课程通过最流行的开源数据库MySQL带你了解数据库的世界。   相关的阿里云产品:云数据库RDS MySQL 版 阿里云关系型数据库RDS(Relational Database Service)是一种稳定可靠、可弹性伸缩的在线数据库服务,提供容灾、备份、恢复、迁移等方面的全套解决方案,彻底解决数据库运维的烦恼。 了解产品详情: https://www.aliyun.com/product/rds/mysql 
目录
相关文章
|
4月前
|
缓存 NoSQL 关系型数据库
美团面试:MySQL有1000w数据,redis只存20w的数据,如何做 缓存 设计?
美团面试:MySQL有1000w数据,redis只存20w的数据,如何做 缓存 设计?
美团面试:MySQL有1000w数据,redis只存20w的数据,如何做 缓存 设计?
|
2月前
|
SQL 人工智能 关系型数据库
如何实现MySQL百万级数据的查询?
本文探讨了在MySQL中对百万级数据进行排序分页查询的优化策略。面对五百万条数据,传统的浅分页和深分页查询效率较低,尤其深分页因偏移量大导致性能显著下降。通过为排序字段添加索引、使用联合索引、手动回表等方法,有效提升了查询速度。最终建议根据业务需求选择合适方案:浅分页可加单列索引,深分页推荐联合索引或子查询优化,同时结合前端传递最后一条数据ID的方式实现高效翻页。
139 0
|
1月前
|
存储 关系型数据库 MySQL
在CentOS 8.x上安装Percona Xtrabackup工具备份MySQL数据步骤。
以上就是在CentOS8.x上通过Perconaxtabbackup工具对Mysql进行高效率、高可靠性、无锁定影响地实现在线快速全量及增加式数据库资料保存与恢复流程。通过以上流程可以有效地将Mysql相关资料按需求完成定期或不定期地保存与灾难恢复需求。
144 10
|
2月前
|
SQL 存储 缓存
MySQL 如何高效可靠处理持久化数据
本文详细解析了 MySQL 的 SQL 执行流程、crash-safe 机制及性能优化策略。内容涵盖连接器、分析器、优化器、执行器与存储引擎的工作原理,深入探讨 redolog 与 binlog 的两阶段提交机制,并分析日志策略、组提交、脏页刷盘等关键性能优化手段,帮助提升数据库稳定性与执行效率。
|
5月前
|
关系型数据库 MySQL Linux
在Linux环境下备份Docker中的MySQL数据并传输到其他服务器以实现数据级别的容灾
以上就是在Linux环境下备份Docker中的MySQL数据并传输到其他服务器以实现数据级别的容灾的步骤。这个过程就像是一场接力赛,数据从MySQL数据库中接力棒一样传递到备份文件,再从备份文件传递到其他服务器,最后再传递回MySQL数据库。这样,即使在灾难发生时,我们也可以快速恢复数据,保证业务的正常运行。
275 28
|
4月前
|
存储 SQL 缓存
mysql数据引擎有哪些
MySQL 提供了多种存储引擎,每种引擎都有其独特的特点和适用场景。以下是一些常见的 MySQL 存储引擎及其特点:
136 0
|
6月前
|
存储 SQL 关系型数据库
【YashanDB知识库】MySQL迁移至崖山char类型数据自动补空格问题
**简介**:在MySQL迁移到崖山环境时,若字段类型为char(2),而应用存储的数据仅为'0'或'1',查询时崖山会自动补空格。原因是mysql的sql_mode可能启用了PAD_CHAR_TO_FULL_LENGTH模式,导致保留CHAR类型尾随空格。解决方法是与应用确认数据需求,可将崖山环境中的char类型改为varchar类型以规避补空格问题,适用于所有版本。
|
6月前
|
SQL 关系型数据库 MySQL
【YashanDB知识库】字符集latin1的MySQL中文数据如何迁移到YashanDB
本文探讨了在使用YMP 23.2.1.3迁移MySQL Server字符集为latin1的中文数据至YashanDB时出现乱码的问题。问题根源在于MySQL latin1字符集存放的是实际utf8编码的数据,而YMP尚未支持此类场景。文章提供了两种解决方法:一是通过DBeaver直接迁移表数据;二是将MySQL表数据转换为Insert语句后手动插入YashanDB。同时指出,这两种方法适合单张表迁移,多表迁移可能存在兼容性问题,建议对问题表单独处理。
【YashanDB知识库】字符集latin1的MySQL中文数据如何迁移到YashanDB
|
6月前
|
缓存 NoSQL 关系型数据库
Redis和Mysql如何保证数据⼀致?
1. 先更新Mysql,再更新Redis,如果更新Redis失败,可能仍然不⼀致 2. 先删除Redis缓存数据,再更新Mysql,再次查询的时候在将数据添加到缓存中 这种⽅案能解决1 ⽅案的问题,但是在⾼并发下性能较低,⽽且仍然会出现数据不⼀致的问题,⽐如线程1删除了 Redis缓存数据,正在更新Mysql,此时另外⼀个查询再查询,那么就会把Mysql中⽼数据⼜查到 Redis中 1. 使用MQ异步同步, 保证数据的最终一致性 我们项目中会根据业务情况 , 使用不同的方案来解决Redis和Mysql的一致性问题 : 1. 对于一些一致性要求不高的场景 , 不做处理例如 : 用户行为数据 ,
|
7月前
|
SQL 关系型数据库 MySQL
基于SQL Server / MySQL进行百万条数据过滤优化方案
对百万级别数据进行高效过滤查询,需要综合使用索引、查询优化、表分区、统计信息和视图等技术手段。通过合理的数据库设计和查询优化,可以显著提升查询性能,确保系统的高效稳定运行。
266 9

推荐镜像

更多