最近在做sqlserver到mysql的数据迁移 无可避免的遇到了乱码问题 看了N多资料之后 终于解决了问题
并且对mysql的乱码问题解决找到了一个完整的套路 这里分享给大家,希望也能帮到你们。
我们从三个部分来解决mysql的乱码问题。
预备篇:我的环境(供参考)
一:出现乱码的原因,原理
二:从环境参数方面排除乱码原因
三:从数据源方面(代码)排除乱码原因
打完收工~!
预备篇: 环境:
CentOS Linux release 6.0
mysql 5.6.14
handlersocket 1.1.1
脚本语言perl 5.10.1
因为我是在window 7 中通过pietty 访问远程linux系统 所以用到的工具也说明一下
pietty 0.3.26 很好用的工具 需要的同学可以到这里下载 http://download.csdn.net/detail/q383965374/6431981
mysql客户端
mysql workbench 6.0
我是在window7中写好perl脚本 然后 通过SSH Secure File Transfer Client3.2.9(下载SSH Secure File Transfer Client) 传到 linux中运行
一:出现乱码的原因,原理
linux系统编码:
Windows的默认编码为GBK,Linux的默认编码为UTF-8。
检查linux的系统编码,确定系统是否支持中文。
mysql数据传输:
mysql有几个字符集:
character_set_client:客户端的字符集
character_set_results:结果字符集
character_set_connection:连接字符集
这三个系统参数的作用:
信息输入路径: client--connection--server (数据传送方向从左到右)
信息输出路径: server--connection--results (数据传送方向从左到右)
故这几个系统参数的值须相同,不然以不同的编码方式传送数据,若编码方式不兼容,则容易造成乱码的问题。
数据来源影响编码的因素:
1) 命令行参数和标准输入。
从命令行参数或标准输入(STDIN)来的字符串, 它的编码跟linux系统locale有关。如果你的locale是zh_CN或zh_CN.gb2312, 那么进来的字符串就是gb2312编码, 如果你的locale是zh_CN.gbk, 那么进来的编码就是gbk, 如果你的编码是zh_CN.UTF8, 那进来的编码就是utf8。
2) 你的源代码里的字符串。
这要看你编写源代码时用的是什么编码。 在记事本里, 你可以通过“文件”->“另存为”查看和更改编码。 在
linux下, 你可以cat一个源代码文件, 如果中文正常显示, 说明源代码的编码跟locale是一致的。
如果你的源代码里含有中文, 那么你最好遵循这个原则: 1) 编写代码时使用utf8编码, 2)在文件的开头加上“use utf8;”语句。这样, 你源代
码里的字符串就都会是utf8编码的。
3) 从文件读入。
这个毫无疑问, 你的文件是什么编码, 读进来就是什么编码了。
4) 抓取网页。
网页是什么编码就是什么编码。网站的编码可以从响应头里或者html的<head>标签里获得。也有可能出现响应头和html head里都没说明编码的情况, 这个就是做的很不礼貌的网页了。在perl中的判断网页编码的方法在文末PS中给出。
数据输出时的影响因素:
输出
字符串在程序内被正确地处理后, 要展现给用户。这时我们需要把字符串从perl internal form转化成用户能接受的形式。简单地说, 就是把字符串从utf8编码转换成输出的编码或表现界面的编码。这时候, 我们使用$str = Encode::encode('charset', $str);。同样可以分为几种情况。
1) 标准输出。标准输出的编码跟locale一致。输出的时候utf8 flag应该关闭, 不然就会出现我们前面看到的那行警告:
Wide character in print at unicode.pl line 10.
2) GUI程序。这个应该是不用干什么, utf8编码, utf8 flag开启就行。没有实际测试过。
3) 做http post。看网页表单要求什么编码。utf8 flag开或关无所谓, 因为http post发送出去的只是字符串中的数据部分, 不管utf8 flag。
perl语言方面的影响:
在Perl看来, 字符串只有两种形式。 一种是octets, 即8位序列, 也就是我们通常说的字节数组. 另一种utf8编码的字符串, perl管它叫string。 也就是说: Perl只认识两种编码: Ascii(octets)和utf8(string)。
那么perl如何确定一个字符串是octets还是utf8编码的字符串呢? perl可没有什么智能, 他完全是靠字符串上的utf8 flag。在perl内部, 字符串结构由两部分组成: 数据和utf8 flag。
如果utf8 flag是On的话, perl就会把中国当成utf8字符串来处理, 如果utf8 flag为Off, perl就会把他当成octets来处理。所有字符串相关的函数包括正则表达式都会受utf8 flag的影响。
这里我们使用Encode模块的_utf8_on函数和_utf8_off函数来开关字符串“中国”的utf8 flag。可以看到, utf8 flag打开的时候,“中国”被当成utf8字符串处理, 所以其长度是2。 utf8 flag关闭的时候,“中国”被当成octets(字节数组)处理, 出来的长度是6(我的编辑器用的是utf8编码, 如果你的编辑器用的是gb2312编码, 那么长度应该是4)。
如果你有一个字符串“中国”, 它是gb2312编码的。 如果它的utf8 flag是关闭的, 它就会被当成octets来处理, length()会返回4, 这通常不是你想要的。 而如果你开启它的utf8 flag, 则它会被当做utf8编码的字符串来处理。由于它本来的编码是gb2312的, 不是utf8的,这就可能导致错误发生。由于gb2312和utf8内码范围部分重叠, 所以很多时候, 不会有错误报出来, 但是perl可能已经错误地拆解了字符。严重的时候, perl会报警, 说某个字节不是合法的utf8内码。(如果你有很多中文,有一部分是正常的 有一部分乱码 可能就是这个原因)
字符串连接
. 是字符串连接操作符。连接两个字符串时, 如果两个字符串的utf8 flag都是Off, 那么结果字符串也是Off. 如果其中任何一个字符串的utf8flag是On的话, 那么结果字符串的utf8 flag将是On。连接字符串并不会改变它们原来的编码, 所以如果你把两个不同编码的字符串连在一起, 那么以后不管对这个字符串怎么转码, 都总会有一段是乱码。这种情况一定要避免, 连接两个字符串之前应该确保它们编码一致。如有必要, 先进行转码, 再连接字符串。
utf8 flag打开方法:
来将其转化为utf8编码并开启utf8 flag。如果你的字符串编码本来就是utf8, 只是utf8 flag没有打开, 那么你可以使用以下三种方式中的任一种来开启utf8 flag:
程序代码:
$str = Encode::decode_utf8($str);
$str = Encode::decode("utf8", $str);
Encode::_utf8_on($str);
(也就是说我们要保证字符串是utf8 on 其实只要把字符串进行一次utf8转码就行了)
PerlIO
PerlIO为我们的输入/输出转码提供了便利。它可以针对某个文件句柄, 输入的时候自动帮你转码并开启utf8 flag, 输出的时候, 自动帮你转码并关闭utf8 flag。假设你的终端locale是gb2312, 看下面的例子:
程序代码:
use strict;
binmode(STDIN, ":encoding(gb2312)");
binmode(STDOUT, ":encoding(gb2312)");
while (<>) {
chomp;
print $_, length, "\n";
}
这样我们就省去了输入和输出时转码的麻烦。PerlIO可以作用于任何文件句柄, 具体请参考perldoc PerlIO。
总结来说:
我们要解决乱码问题 要从两个方面入手:一个是系统环境和mysql环境参数问题 二是 要入库的字符串编码 以及 连接编码的问题
二:从环境参数方面排除乱码原因
两个内容:
⑴检查linux系统编码
⑵检查mysql中的编码
⑴检查linux系统编码
检查linux的系统编码,确定系统是否支持中文。在linux系统的终端中输入命令:locale,就会看到打印出的系统编码信息。如果打印出来的信息如下,则说明系统的编码不支持中文:
LANG=en_US.UTF-8
LC_CTYPE=”en_US.UTF-8″
LC_NUMERIC=”en_US.UTF-8″
LC_TIME=”en_US.UTF-8″
LC_COLLATE=”en_US.UTF-8″
LC_MONETARY=”en_US.UTF-8″
LC_MESSAGES=”en_US.UTF-8″
LC_PAPER=”en_US.UTF-8″
LC_NAME=”en_US.UTF-8″
LC_ADDRESS=”en_US.UTF-8″
LC_TELEPHONE=”en_US.UTF-8″
LC_MEASUREMENT=”en_US.UTF-8″
LC_IDENTIFICATION=”en_US.UTF-8″
支持中文的系统编码打印出来的信息应该如下图所示:
LANG=zh_CN
LC_CTYPE="zh_CN"
LC_NUMERIC="zh_CN"
LC_TIME="zh_CN"
LC_COLLATE="zh_CN"
LC_MONETARY="zh_CN"
LC_MESSAGES="zh_CN"
LC_PAPER="zh_CN"
LC_NAME="zh_CN"
LC_ADDRESS="zh_CN"
LC_TELEPHONE="zh_CN"
LC_MEASUREMENT="zh_CN"
LC_IDENTIFICATION="zh_CN"
修改两种方法:
方法1:
vi /etc/sysconfig/i18n
默认为:
LANG="en_US.UTF-8"
SYSFONT="latarcyrheb-sun16"
修改为:
LANG="zh_CN.UTF-8"
SUPPORTED="zh_CN.UTF-8:zh_CN:zh"
SYSFONT="latarcyrheb-sun16"
方法2:
vi /etc/profile
export LC_ALL="zh_CN.UTF-8"
export LANG="zh_CN.UTF-8"
重启linux后再locale发现已经支持中文环境。
如果想将utf8改为gbk 用下面的命令
export LANG=zh_CN.GBK
⑵检查mysql中环境的编码
1、创建数据库的时候设置好数据的字符集为utf8:
CREATE DATABASE `test`
CHARACTER SET 'utf8'
COLLATE 'utf8_general_ci';
2、建表的时候设置好表的字符集为utf8
CREATE TABLE `database_user` (
`ID` varchar(40) NOT NULL default '',
`UserID` varchar(40) NOT NULL default '',
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
这3个设置好了,基本就不会出问题了,即建库和建表时都使用相同的编码格式。
但是如果你已经建了库和表可以通过以下方式进行查询。
1.查看默认的编码格式:
mysql> show variables like "%char%";
+--------------------------+---------------+
| Variable_name | Value |
+--------------------------+---------------+
| character_set_client | gbk |
| character_set_connection | gbk |
| character_set_database | utf8 |
| character_set_filesystem | binary |
| character_set_results | gbk |
| character_set_server | utf8 |
| character_set_system | utf8 |
+--------------------------+-------------+
注:以前2个来确定,可以使用set names utf8,set names gbk设置默认的编码格式;
设置utf-8方法
方法一:
1.修改/etc/my.cnf文件,改成这样:
[mysqld]
datadir=/var/lib/mysql
socket=/var/lib/mysql/mysql.sock
default-character-set=utf8
[mysql.server]
user=mysql
basedir=/var/lib
[mysqld_safe]
err-log=/var/log/mysqld.log
pid-file=/var/run/mysqld/mysqld.pid
注意:就是加入了一句default-character-set=utf8。
2./etc/init.d/mysqld restart 重新启动mysql;
方法二:
执行SET NAMES utf8的效果等同于同时设定如下:
SET character_set_client='utf8';
SET character_set_connection='utf8';
SET character_set_results='utf8';
设置完后除了show variables like "%char%" 也可以用 status 命令来查看字符集是否已经为utf8
2.查看test数据库的编码格式:
mysql> show create database test;
+------------+------------------------------------------------------------------------------------------------+
| Database | Create Database |
+------------+------------------------------------------------------------------------------------------------+
| test | CREATE DATABASE `test` /*!40100 DEFAULT CHARACTER SET gbk */ |
+------------+------------------------------------------------------------------------------------------------+
设置utf-8方法:
设置数据库db_name默认为utf8:
ALTER DATABASE `db_name` DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci;
3.查看yjdb数据表的编码格式:
先选择数据库
mysql> use test
mysql> show create table yjdb;
| yjdb | CREATE TABLE `yjdb` (
`sn` int(5) NOT NULL AUTO_INCREMENT,
`type` varchar(10) NOT NULL,
`brc` varchar(6) NOT NULL,
`teller` int(6) NOT NULL,
`telname` varchar(10) NOT NULL,
`date` int(10) NOT NULL,
`count` int(6) NOT NULL,
`back` int(10) NOT NULL,
PRIMARY KEY (`sn`),
UNIQUE KEY `sn` (`sn`),
UNIQUE KEY `sn_2` (`sn`)
) ENGINE=MyISAM AUTO_INCREMENT=1826 DEFAULT CHARSET=gbk ROW_FORMAT=DYNAMIC |
设置表tb_name默认编码为utf8:
ALTER TABLE `tb_name` DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci;
如果linux环境已支持中文,mysql的字符集已设为utf8 mysql中的数据库 数据表也已经设为utf8 但还是乱码 这时候我们就要检查 代码 以及 数据源字符串的编码了 详见第三部分
三:从数据源方面(代码)排除乱码原因
步骤一:把脚本代码文本修改为utf8
perl脚本代码中print“中文”时为乱码:
首先设置客户端的编码为utf8 我这里用的 pietty 也就是 设置pietty的编码为 utf8
发现仍然为乱码 这时候我们要修改 代码文本 pl的 编码。
用记事本打开后另存为 utf8格式的 pl
再运行 发现 代码中 print “中文”已经能显示了
步骤二: 检测数据源字符串的代码 并进行转化为 utf8
做完上述操作之后 我从数据库中读出的数据插入mysql后仍然为乱码(在workbench中查看或者 在在代码中print出来都是乱码 )
##测试的时候,查看数据的语句。 print "\n",$data_str,"\n";
但是奇怪的是 用IO输出到 txt中却是正常的。
perl代码中的输出txt命令:
open(FILE,">export_data.txt"); syswrite(FILE,"$data_str\n"); close(FILE);
IO输出为txt其实是判断字符串 编码的一种方式。 我们可以新建各种格式的txt,分别把字符串输入到文本中,看哪一个文本的中文能正常显示,则说明该字符串是这种编码。
我们打开中文显示正常的那份txt 另存为时可以看到它本身的编码是ANSI:
在简体中文系统下,ANSI 编码代表 GB2312 编码,《信息交换用汉字编码字符集》是由我国国家标准总局1980年发布,1981年5月1日开始实施的一套国家 汉字编码字符集标准,标准号是GB 2312—1980。它是计算机可以识别的编码,适用于汉字处理、汉字通信等系统之间的信息交换。基本集共收入汉字6763个和非汉字图形字符682个。整个字符集分成94个区,每区有94个位。每个区位上只有一个字符,因此可用所在的区和位来对汉字进行编码
终于知道原因是因为从sqlserver中提出来的字符串是GBK 但mysql数据库的环境为utf8 不符合。 所以 我们只要在代码中 对要插入数据库的字符串进行转码即可:
perl中的转码方法:
$data_str=encode("utf8",decode("gb2312",$data_str));
搞掂了
PS: 插入时用的 perl中的handlerscoket 代码如下: 在其他语言中可以在连接时设置编码 但是handlersocket没找到设置的,应该是默认用的系统编码 所以 前面的环境和入库字符串编码设置好了之后 应该是ok的。
if($data_str ne "") { $data_str="[$data_str]"; my $args = { host => $aim_ip, port => $hs_port }; my $hs = new Net::HandlerSocket($args); my $res = $hs->open_index($gid, $aim_db_name, $table_name, 'PRIMARY', "$insert_columns"); die $hs->get_error() if $res != 0; $res = $hs->execute_multi(eval($data_str)); die $hs->get_error() if $hs->get_error() != 0; $hs->close(); } undef $data_str;
PS:在把sqlserver中的数据提出来时发现有一些符号没转换之前就已经是乱码了 比如说点号·变成了?, 或者㎡变成了■。 因为从sqlserver数据库出来的是gbk,而用来执行perl的linux的系统是utf8所以有些会不兼容。 所以 提取sqlserver数据的linux系统必须设为gbk,如果想要utf8的可以 在perl脚本中转码。
PS:
一:在perl中的判断网页编码的方法
程序代码:
use Encode; use LWP::Simple qw(get); use strict; my $str = get "http://www.sina.com.cn"; eval {my $str2 = $str; Encode::decode("gbk", $str2, 1)}; print "not gbk: $@\n" if $@; eval {my $str2 = $str; Encode::decode("utf8", $str2, 1)}; print "not utf8: $@\n" if $@; eval {my $str2 = $str; Encode::decode("big5", $str2, 1)}; print "not big5: $@\n" if $@; 输出: 程序代码: not utf8: utf8 "\xD0" does not map to Unicode at /usr/local/lib/perl/5.8.8/Encode.pm line 162. not big5: big5-eten "\xC8" does not map to Unicode at /usr/local/lib/perl/5.8.8/Encode.pm line 162.