Mysql 字符集转换的过程
一、Mysql处理字符串的机制
mysql提供了丰富的字符串处理类型,我们可以通过mysql对字符串进行排序、校验、比较,
在mysql中处理字符串分两种方式:
1. 二进制的字符串
2. 非二进制的字符串
下面看一下这两种类型有什么区别?
1、二进制字符串类型
二进制的字符串字段类型有binary、varbinary、blob
二进制类型主要是用来保存声音、图像等等的二进制数据,
Mysql保存的大小是受硬盘空间影响的,只要硬盘空间足够大,Mysql是没有大小容量限制的,
所以我们可以保存声音、图像这样的数据,
当然不建议把声音和图像保存到表里面,一般是把声音、图像的地址以字符串的形式保存到表里面。
Mysql可以完全的把图像数据以及声音数据,以二进制流的形式存到mysql的数据表当中,
把图像和声音存储到数据库当中,这个字段一定要指定为二进制的类型,
为什么要指定二进制类型呢?
因为mysql二进制字段类型是与字符集无关。
2、举一个例子
比如图像都是一些二进制数据,这样的0xaa 0xbb ,
如果通过PHP把图像发送给客户端的时候,没有指定文件头 header("content-type:image/jpeg");,
没有指定头,在客户端显示的时候都是乱码,乱码是怎么产生的呢?
因为从服务器端发送过来的"图像数据",都是一些二进制的文件,比如 0xaa 0xbb 0xaf 0xfe,
这些二进制数据以HTML的形式发送给客户端的时候,如果没有指定它是一个图像类型,那么客户端(浏览器)会把图像当做普通文本来显示。
我们知道凡是文本都有字符集的概念,字符集是一些字符组成的集合。
客户端(浏览器)会把图像的二进制数据,强制的组合成字符集,这时候在客户端浏览器上会看到一些乱码。
这些乱码就是把图像的二进制数据强制转换为字符,按照HTML页面指定的字符集标准,转换成相应的字符集所能体现的文字。
我们没有明确的客户端(浏览器)发过来的是二进制的图像数据,浏览器就会按照默认的字符集(uft8、gbk...),把这些二进制流转换成文本的格式,呈现在我们面前的时候就是一堆乱码。
这个原理用到数据库上,如果数据库字段选择的不是二进制数据类型,而是普通的非二进制数据类型的时候,
把图像的二进制数据存到非二进制字段的时候,也会把图像二进制数据转换成乱码,因为有字符集的问题。
所以存储图像或者声音这样的数据时候,如果存储到数据库,这个字段一定不能有字符集的特性,而非二进制字段都有字符集、字符集校对规则这样的特性。
3、非二进制字符串类型
非二进制的字符串字段类型有char、varchar、text
如果把图像存到非二进制字段的时候,因为有字符集,会把二进制数据转换成标准的文字,可能转换成一些我们不认识的一些特殊字符。
虽然看到的是我们不认识的字符(乱码),但是这些字符确实是属于字符集范畴的(
这个字符集可能是utf8、gbk、gb2312、big5...)
如果要把图像存储到数据库,一定要选择这些 binary、varbinary、blob 二进制的字段类型,而不要选择非二进制的类型,
因为非二进制的字段有字符集的特性,把二进制的数据存储到非二进制的时候,会把图像二进制数据转换成相应字符集里面的字符,而且会转换成功。
乱码的产生是通过字符集转换成的字符,只不过对于我们用户来说这些字符没有任何意义,但是确实成功的把这些二进制的数据转换成了字符集的文本了,
所以存储图像和声音的时候,要保障存进去的时候是二进制数据,取出来的时候也是二进制数据,
这个二进制数据流不能破坏掉,破坏了取出来就不是一个图像了。
既然图像和声音本身就是二进制的数据,没有字符集的特性,存的时候一定要选择二进制的字段类型来进行保存。
4、总结
这就是二进制和非二进制类型字段的一些区别:
二进制是以"字节"来进行保存的。
非二进制有"字符集"和"字符的校对规则"这样的特性。
我们在存储二进制数据的时候要选择合适的二进制字段类型。
存储非二进制数据,比如文章、字符、中文、拼音、俄文、发文、德文,等等这样标准字符的时候,需要字符集的时候,要选择这种 char、varchar、text 非二进制的字段类型。
二、字符集的概念
通过 字 符 集 三个字的字面意义大概能知道,字符集就是一堆字符的集合。
字符集的种类非常多,比如常用的有gdk、gb2312、utf8,每一个字符集里面包含的文字数量、符号数量、各个语言的字符是不同的。
比如在文档中输出一个字母a,文档字符集是gbk的,就从gbk这个字符集中取出了这个字符a,然后显示到光标所在的位置。
或者这么来理解,把每个字符集想象成一个字典:
简体的新华字典
繁体的新华字典
把每一个字典想象成一个字符集,比如简体的想象成gb2312、繁体的big5,
每一个中文字、繁体字还有每一个英文字母都存在于字符集的库里面。
早期win98系统的时候,有一些特别特殊的字,是输入不进去的,因为在当时使用的字符集里面,根本就每有这个字。
把字符集想象成是一个仓库,每一个字符集里面保存了成千上万的文字,有普通的文字,有特殊的符号,可能有一些字符集里面包含了一些少数民族的文字,比如蒙文、藏文……
每一种字符集都是一个仓库,仓库里存了成千上万个"字",包括普通的汉字,字母,
计算机底层是不认识这些简体、繁体普通的文字,在底层保存的时候都是以二进制的方式进行处理的,计算机只认识这些二进制的数据类型(二进制流)。
比如一个字是以二进制的方式 oxfe oxfa 这种数据进行保存,这两个 oxfe oxfa 字节组合成一个字
1. 通过输入法进行输入的时候,最终会产生这个字的编码(oxfe oxfa 二进制的数据)
2. 然后到字符集中(仓库中),根据这个编码(oxfe oxfa)把这个字提取出来
3. 然后通过图像技术,显示到屏幕上
这就是字符集的概念,一个存储字符的仓库,仓库之间有不同,有的仓库大存的多,有的仓库少
有的仓库不仅能存中文,还能存德文、日文、发文...
所以要根据需要选择合适的字符集
gb2312 : 比较早的简体字符集,
大概包含6700多个日常汉字、罗马字符、特殊字符、日文片假名、俄文字母,
2个字节存储,比如一个中文字,要占用两个字节的空间
big : 繁体字符集,13000多个汉字,一个字也是2个字节存储
gbk : 大概包含21000多个汉字,包括简体和繁体、日文片假名、俄文… 2个字节存储
utf-8 : utf8字符集编码基于unicode,万国码可以涵盖多种语言,
可以在一个页面体现多个语种,多个国家的文字内容,
储存长度是可变得1 ~ 3字节,保存一个英文字母占用一个字节,一个中文占用三个字节。
unicode : 国际标准化组织制定一套涵盖世界上所有语种,所有符号的编码方案
看一下mysql都支持那些字符集
show character set;
列表比较长,N多个字符集gb2312、utf8....
创建一个表,直观的看一下字符集长度的差别,
uft8保存的是可变长度,
gbk、gb2312、big5是双字节的
create database webclass; use webclass; create table demo( name1 varchar(30) character set utf8, -- name1字段,指定可变类型varchar(30),设置字符集utf8 name2 varchar(30) character set gbk -- name2字段,指定可变类型varchar(30),设置gbk字符集 ); show create table demo; show create table demo\G
*************************** 1. row ***************************
Table: demo
Create Table: CREATE TABLE `demo` (
`name1` varchar(30) DEFAULT NULL, -- name1字段,字符集设置的是utf8,由于默认表的字符集就是utf8,所以这里没有体现出来
`name2` varchar(30) CHARACTER SET gbk DEFAULT NULL -- name2字段,符集设置的是gbk
) ENGINE=InnoDB DEFAULT CHARSET=utf8
name1字段是utf8,name2字段是gbk,下面插入数据
insert into demo (name1, name2) values ("兰", "兰"); select * from demo;
+-------+-------+
| name1| name2|
+-------+-------+
| 兰 | 兰 |
+-------+-------+
通过length()函数,查看同样的一个简体字,不同字符集保存的长度
select length(name1), length(name2) from demo;
name1字段是utf8字符集,占用了3个字节
name2字段是gbk字符集,占用了2个字节
+---------------+---------------+
| length(name1) | length(name2) |
+---------------+---------------+
| 3 | 2 |
+---------------+---------------+
utf8在保存同样一个中文的时候要比gbk多占一个字节
下面插入一个英文字母a,同样看一下两个字段的长度
insert into demo (name1, name2) values ("a", "a"); select * from demo;
+-------+-------+
| name1| name2|
+-------+-------+
| 兰 | 兰 |
| a | a |
+-------+-------+
再看一下占用空间长度
select length(name1), length(name2) from demo;
英文字母a两字段的长度都是1,也就是说在处理单字节文字的时候,utf8和gbk是没有差异的
+---------------+---------------+
| length(name1) | length(name2) |
+---------------+---------------+
| 3 | 2 |
| 1 | 1 |
+---------------+---------------+
length()函数是读取字段里面内容,所占用空间的字符个数,
是按照字节长度进行读取的,一个中文utf8占三个字节,gbk占两个字节。
mysql还为我们提供了一个char_length()函数
length()函数: 按照"字节"计算
char_length()函数:不考虑字节,只考虑到底读取有几个字,按照"字符"进行计算,
char_length()函数按字符来算,字段里有多少个字符就是多少
select char_length(name1), char_length(name2) from demo;
英文字母是1个文字,一个简体中文也是1个文字
+--------------------+--------------------+
| char_length(name1) | char_length(name2) |
+--------------------+--------------------+
| 1 | 1 |
| 1 | 1 |
+--------------------+--------------------+
三、字符集校对规则
我们选用非二进制字段类型的时候,不仅有字符集还有字符集校对规则
字符集校对规则主要是用于排序和比较使用的
看一下Mysql支持的所有字符集,utf8是基于unicode编码的
show character set;
+----------+-----------------------------+---------------------+--------+
| Charset | Description | Default collation | Maxlen|
+----------+-----------------------------+---------------------+--------+
| big5 | Big5 Traditional Chinese | big5_chinese_ci | 2 |
| gb2312 | GB2312 Simplified Chinese| gb2312_chinese_ci | 2 |
| utf8 | UTF-8 Unicode | utf8_general_ci | 3 |
……
utf8是基于unicode编码的,utf8编码支持多语种、多字符,也就是在一个页面中可以同时体现多个国家的语言,utf8_general_ci 是默认的校对规则,ci 的意思是不区分大小写的比对,
当然utf8不只这一种校对规则,它还有很多种校对规则。
我来看一下 字符集校对规则
show collation;
+------------------------------+----------+-----+---------+----------+---------+
| Collation | Charset | Id | Default | Compiled| Sortlen |
+------------------------------+----------+-----+---------+----------+---------+
| big5_chinese_ci | big5 | 1 | Yes | Yes | 1 |
| big5_bin | big5 | 84 | | Yes | 1 |
| gbk_chinese_ci | gbk | 28 | Yes | Yes | 1 |
| gbk_bin | gbk | 87 | | Yes | 1 |
| gb2312_chinese_ci | gb2312 | 24 | Yes | Yes | 1 |
| gb2312_bin | gb2312 | 86 | | Yes | 1 |
| utf8_general_ci | utf8 | 33 | Yes | Yes | 1 |
| utf8_bin | utf8 | 83 | | Yes | 1 |
| utf8_unicode_ci | utf8 | 192 | | Yes | 8 |
| utf8_icelandic_ci | utf8 | 193 | | Yes | 8 | 冰岛语
| utf8_latvian_ci | utf8 | 194 | | Yes | 8 | 拉脱维亚
| utf8_romanian_ci | utf8 | 195 | | Yes | 8 | 罗马尼亚语
| utf8_spanish_ci | utf8 | 199 | | Yes | 8 | 西班牙
| utf8_swedish_ci | utf8 | 200 | | Yes | 8 | 瑞典语
……
big5只有两种:
big5_chinese_ci 是不区分大小写的字符集校对规则
big5_bin 基于二进制的,二进制是区分大小的。
gbk的也只有两种:
gb2312_chinese_ci 不用区分大小写的
gb2312_bin 二进制的字符集校对规则,
utf8有很多种,罗马的、德文的、瑞典的、丹麦的多个语种,
一般情况下我们选择通用的 utf8_general_ci 就可以了,
以为你每一个国家的字符集在校对的时候,根据不同国家文字的方式,会有一些多多少少的出入,
utf8_general_ci 不区分大小写的字符集校对
utf8_bin 基于二进制的校对,是区分大小写的字符集校对
utf8_general_ci 这行第四列的Default字段下是Yes,代表它是默认的字符集校对规则,
也就说是在定义数据库,定义表,定义字段的时候,指定了字符集,没有指定字符集校对规则,会默认采用 utf8_general_ci 不区分大小的校对规则,一般情况下的默认都代表着大多数的应用。
下面重新创建一张表,表里面建立两个字段name1,name2
name1字段:设置utf8字符集,校对规则是 utf8_bin
name2字段:设置utf8字符集,校对规则是 utf8_general_ci
已经知道了utf8与gbk字符集的区别,utf8能保存更多的语言,下面例子的意图是,字符集校对区分大小写 和 不区分小写的区别
create table demo2( name1 varchar(30) character set utf8 collate utf8_bin, -- 字符集uft8,校对规则utf8_bin name2 varchar(30) character set utf8 collate utf8_general_ci -- 字符集uft8,校对规则utf8_general_ci ); -- show create table demo2; show create table demo2\G
*************************** 1. row ***************************
Table: demo2
Create Table: CREATE TABLE `demo2` (
`name1` varchar(30) CHARACTER SET utf8 COLLATE utf8_bin DEFAULT NULL, -- name1字符集校对规则是区分大小写的,也就是说是基于二进制的比对
`name2` varchar(30) CHARACTER SET utf8 DEFAULT NULL -- name2是不区分大小写的,因为表的默认就是utf8_general_ci所以没体现出来
) ENGINE=InnoDB DEFAULT CHARSET=latin1
name1字段、name2字段同样都是utf8字符集,只不过在排序、比较的时候,name1字段是区分大小写的,name2字段是不区分大小写的,
下面比较两个字段区别在哪里
1、字母大小写的区别
分别往两个字段里面,插入三条数据
insert into demo2 (name1, name2) values ('a', 'a'), ('b', 'b'),('A', 'A'); select * from demo2;
+-------+-------+
| name1| name2|
+-------+-------+
| a | a |
| b | b |
| A | A |
+-------+-------+
一定要清楚
第一个字段name1是区分大小写的校对规则,
第二个字段name2是不区分大小写的校对规则
查询第二个字段name2等小写字母a的
select * from demo2 where name2 = 'a';
小写的a相等查询出来了,大写的A也查询出来了,因为name2字段定义的规则是不区分大小写
+-------+-------+
| name1| name2|
+-------+-------+
| a | a |
| A | A |
+-------+-------+
如果用第一个字段name1来进行检索
select * from demo2 where name1 = 'a';
只查询出来一条数据,因为第一个字段name1定义的字符集校对规则,字符比对的时候是按照区分大小写来比对的,是基于二进制的比对
+-------+-------+
| name1| name2|
+-------+-------+
| a | a |
+-------+-------+
我们一般指定字符集校对规则的时候,都会选择不区分大小写的 utf8_general_ci
2、字母排序的区别
原始的排序,大写的A最后插入排在最后
select * from demo2;
+-------+-------+
| name1| name2 |
+-------+-------+
| a | a |
| b | b |
| A | A |
+-------+-------+
按照第二个字段name2来进行排序的时候,小写的a和大写的A靠到一起了
select * from demo2 order by name2;
+-------+-------+
| name1| name2|
+-------+-------+
| a | a |
| A | A |
| b | b |
+-------+-------+
因为这个字段name2定义的字符集校对是不区分大小写的,
所以系统在排序的时候把a、A当成一样的,所以放到一起
再插入一条数据,大写的字母B
insert into demo2 (name1, name2) values ('B', 'B');
现在有四条记录
select * from demo2;
+-------+-------+
| name1 | name2|
+-------+-------+
| a | a |
| b | b |
| A | A |
| B | B |
+-------+-------+
字段name1是区分大小写的,基于二进制的比对,是基于字节的比对,
字段name2是不区分大小写
按照字段name2,不区分大小写的来排序
select * from demo2 order by name2;
a和A、b和B靠在了一起
+-------+-------+
| name1 | name2|
+-------+-------+
| a | a |
| A | A |
| b | b |
| B | B |
+-------+-------+
按照区分大小写的name1字段来排序
select * from demo2 order by name1;
这次按照字节进行排序,A和a、B和b没有放到一起,name1字段是区分大小写的
+-------+-------+
|name1 | name2|
+-------+-------+
| A | A |
| B | B |
| a | a |
| b | b |
+-------+-------+
这就是校对规则的一个特性,在排序和比较的时候
select * from demo2 where name1 = 'a'; -- 按照区分大小的检索,能够查询出一个a select * from demo2 where name2 = 'a'; -- 按不区分大小写校对规,则能查到两条数据
我们一般在使用的的时候都使用不区分大小写的校对规则
创建一个表demo3,给表指定字符集是utf8
字段name1是二进制数据类型 binary(3)
字段name2是非二进制的数据类型 varchar(3),是基于字符集的
create table demo3( name1 binary(3), name2 varchar(3) )default character set utf8; show create table demo3\G
*************************** 1. row ***************************
Create Table: CREATE TABLE `demo3` (
`name1` binary(3) DEFAULT NULL, -- 二进制的数据类型
`name2` varchar(3) DEFAULT NULL -- 非二进制数据类型
) ENGINE=InnoDB DEFAULT CHARSET=utf8 -- 表字符集是utf8
两个字段分别,插入小写字母a
insert into demo3 (name1, name2) values ('a', 'a'); select * from demo3;
两个字段插入成功
+-------+-------+
| name1| name2|
+-------+-------+
| a | a |
+-------+-------+
插入一行中文
insert into demo3 (name1, name2) values ('莉', '莉'); select * from demo3;
也插入成果
+-------+-------+
|name1 | name2|
+-------+-------+
| a | a |
| 莉 | 莉 |
+-------+-------+
如果插入两个中文字 就不一样了,出现一个警告 Query OK, 1 row affected, 1 warning (0.00 sec)
insert into demo3 (name1, name2) values ('莉莉', '莉莉'); select * from demo3;
字段name1只插入了一个中文字
+-------+-------+
| name1 | name2|
+-------+-------+
| a | a |
| 莉 | 莉 |
| 莉? | 莉莉 |
+-------+-------+
插入三个中文,出现1个警告信息 Query OK, 1 row affected, 1 warning (0.006 sec)
insert into demo3 (name1, name2) values ('数据库', '数据库'); select * from demo3;
+-------+--------+
| name1| name2|
+-------+--------+
| a | a |
| 兰 | 兰 |
| 莉? | 莉莉 |
| 数? | 数据库 |
+-------+--------+
看一下表结构
desc demo3;
+-------+------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------+------------+------+-----+---------+-------+
| name1| binary(3) | YES | | NULL | |
| name2| varchar(3) | YES | | NULL | |
+-------+------------+------+-----+---------+-------+
name1字段是二进制的binary类型,二进制定义的3是按字节来进行数据保存。
name2字段是非二进制的基于字符集的字段类型,保存的时候3代表的是具体能存几个文字,定义的是3能存三个中文。
我们插4个中字,出现2个警告信息 Query OK, 1 row affected, 2 warnings (0.006 sec)
insert into demo3 (name1, name2) values ('好好学习', '好好学习'); select * from demo3;
这次字段name2储存不进来了,因为varchar(3)定义的是3个,第四个字"习"存不进来了
+-------+--------+
| name1 | name2|
+-------+--------+
| a | a |
| 兰 | 兰 |
| 莉? | 莉莉 |
| 数? | 数据库 |
| 好? | 好好学 |
+-------+--------+
如果是二进制binary(3)类型,存储的时候是按字节来存储,定义长度3是字节的单位。
如果是非二进制的数据类型varchar(3),3这个长度表示的是这个字段能存的是具体字符的个数。
大部分使用的都是"非二进制"的字符串数据类型,既然是非二进制的字符串数据类型,就会牵扯到"字符集"还有"字符集校对规则"
非二进制的数据类型,比如char、varchar、text,这些字段类型都会有字符集和字符集校对规则,即使不指定它也会有。
如果建立一个字段的时候,字段指定了字符集,没有指定校对规则,
比如选的是utf8字符集,会采用默认的utf8_general_ci,utf8有N多个校对规则会,指定了字符集没有指定校对规则会选择默认的。
如果指定了校对规则,没有指定字符集,会使用校对规则的字符集,比如选择utf8_bin的校对规则,但是没有选择字符集,字符集就会使用utf8。
如果字段没有指定校对规则,也没有指定字符集,它会向上找到表,依据表的字符集与校对规则,
如果数据表没有指定字符集与校对规则,会继续向上找到数据库的字符集与校对规则,
如果数据库也没有指定,会找到安装mysql数据库软件的时候,默认的字符集与校对规则,
在操作的时候,必须在创建表这个环节,就应该选择好字符集与校对规则。
整理时间: 20201101
四、字符集设置
复习上一节课的内容:
字符集就是把每一个字进行编码来进行存储,多个字符组成的集合构成一个字符集,比如gbk、utf8...里面有n多个文字,
每一个字符集涵盖的文字数量不同,涵盖的语言不同,涵盖的特殊字符不同,涵盖的少数民族语言不同,涵盖的简体繁体不同,
utf8是万国码,包含了更多国家的语种,包含的字符数量也更多,他是可变字符,中文处理的时候占用字节会比gbk、gb2312大一写。
刚开始就知道用下面这种方式设置字符集
set names gbk;
这种方式比较初级、有潜在的注入漏洞问题,学完本章后就不要用这种方式设置字符集了
还有由于addslashes()转义函数使用不当带来的问题
查看当前的mysql服务器,能支持哪些字符集
show character set;
+----------+-----------------------------+---------------------+--------+
| Charset | Description | Default collation | Maxlen|
+----------+-----------------------------+---------------------+--------+
| big5 | Big5 Traditional Chinese | big5_chinese_ci | 2 |
| gb2312 | GB2312 Simplified Chinese| gb2312_chinese_ci| 2 |
| gbk | GBK Simplified Chinese | gbk_chinese_ci | 2 |
| utf8 | UTF-8 Unicode | utf8_general_ci | 3 |
......
建表、建库想指定一个字符集的时候,应该确认这个字符集是存在的,
当然我们常用的字符集gbk、gb2312、utf8基本上都是存在的,Default collation列(第三列)是字符集的默认校对规则,
一个字符集的校对规则不只一个,其中有一个是默认的,
如果指定了字符集,而没有选择校对规则,会以默认的形式来进行处理。
查看字符集的校对规则
show collation;
+------------------------------+----------+-----+---------+----------+---------+
| Collation | Charset | Id | Default | Compiled | Sortlen |
+------------------------------+----------+-----+---------+----------+---------+
| big5_chinese_ci | big5 | 1 | Yes | Yes | 1 |
| gbk_chinese_ci | gbk | 28 | Yes | Yes | 1 |
| gbk_bin | gbk | 87 | | Yes | 1 |
| gb2312_bin | gb2312 | 86 | | Yes | 1 |
| utf8_general_ci | utf8 | 33 | Yes | Yes | 1 |
| utf8_bin | utf8 | 83 | | Yes | 1 |
| utf8_unicode_ci | utf8 | 192 | | Yes | 8 |
......
列表很长
字符集的校对是依赖于字符集的,也就是说每一个字符集可能有不同的校对规则
查看有关于 字符集的环境变量
show variables like "%character%";
+--------------------------+--------------------------------+
| Variable_name | Value |
+--------------------------+--------------------------------+
| character_set_client | gbk |
| character_set_connection | utf8 |
| character_set_database | utf8 |
| character_set_filesystem| binary |
| character_set_results | gbk |
| character_set_server | latin1 |
| character_set_system | utf8 |
| character_sets_dir | D:\xampp\mysql\share\charsets\ |
+--------------------------+--------------------------------+
查看关于字符集的 校对规则
show variables like "%collation%";
+----------------------+-------------------+
| Variable_name | Value |
+----------------------+-------------------+
| collation_connection| utf8_general_ci |
| collation_database | utf8_general_ci |
| collation_server | latin1_swedish_ci|
+----------------------+-------------------+
上面字符集环境变量的列表很多,校对规则的就相对少些,
通过这两个表的对比,我们知道一个原理,就是有些字符集不需要校对规则。
校对规则是"比较"和"排序"使用的,有一些字符集的设置参与不到比较和排序,所以就不需要校对规则,
比如,返回结果集的时候(character_set_results=gbk),返回结果集就是把结果集返回给客户端,这个时候就不会用到校对规则
下面对字符集的环境变量,一个一个的做一下说明
+--------------------------+--------------------------------+
| Variable_name | Value |
+--------------------------+--------------------------------+
| character_set_client | gbk |
| character_set_connection | utf8 |
| character_set_database | utf8 | 当前选中的数据库字符集(建表的时候就要指定)
| character_set_filesystem| binary | 文件系统的字符(文件系统二进制形式保存)
| character_set_results | gbk |
| character_set_server | latin1 | 默认的操作字符集
| character_set_system | utf8 | 系统元数据字符集
| character_sets_dir | D:\xampp\mysql\share\charsets\ | mysql字符设置目录
+--------------------------+--------------------------------+
下面三个是有关联的,如果这三个字符集设置不当,会对数据库带来注入的危险
+--------------------------+--------------------------------+
| character_set_client | gbk |
| character_set_connection| utf8 |
| character_set_results | gbk |
+--------------------------+--------------------------------+
1、默认字符集 character_set_server
字符集是有优先级的
字段的上一层是表,表上一层是数据库,数据库上一层是MYSQL服务器,
字段、表、数据库、mysql服务器都有字符集的存在
character_set_server 是默认的字符集,什么是默认操作字符集呢?
默认字符集就是在创建字段、表、数据库的时候都没有设置字符集,存储到这个字段里面的字符串,既然是非二进制的字符串肯定会有一个字符集
如果创建字段、表、库的时候都没指定字符集,这个字符集就会继承MYSQ服务器配置的默认字符集。
比如,创建一个数据库dbtest,不给指定字符集
create database dbtest;
查看这个dbtest数据库创建信息
show create database dbtest;
默认字符集是latin1西欧的字符集
+----------+------------------------------------------------------------------+
| Database| Create Database |
+----------+------------------------------------------------------------------+
| dbtest | CREATE DATABASE `dbtest` /*!40100 DEFAULT CHARACTER SET latin1 */ |
+----------+------------------------------------------------------------------+
再创建一个数据库dbbox,指定utf8字符集
create database dbbox default character set utf8;
查看dbbox库的字符集是utf8
show create database dbbox;
+----------+----------------------------------------------------------------+
| Database| Create Database |
+----------+----------------------------------------------------------------+
| dbbox | CREATE DATABASE `dbbox` /*!40100 DEFAULT CHARACTER SET utf8 */ |
+----------+----------------------------------------------------------------+
当创建数据库的的时候,没有指定字符集,就会继承mysql服务器的默认字符集,
如果创建数据时候指定字符集了,就不往上一层找字符集了。
把刚刚建立的两个库删掉
drop database dbtest; drop database dbbox;
再创建数据库dbtest,不指定字符集
create database dbtest;
然后使用dbtest库,创建一张user表,不给表指定字符集,字段name也不指定字符集
use dbtest; -- 选择dbtest库 create table user( name char(30) );
查看user表的创建信息
show create table user;
user表的字符集是latin1,西欧的字符集
+-------+---------------------------------------------------------------------------------------------+
| Table | Create Table |
+-------+---------------------------------------------------------------------------------------------+
| user | CREATE TABLE `user` (
`name` char(30) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=latin1 |
+-------+---------------------------------------------------------------------------------------------+
这是为什么呢?
建立数据库,数据表,字段都没指定字符集,首先库会继承默认字符集,表会继承库的,然后字段会继承表的字符集,
这样做的坏处是,如果服务器的默认字符集不恰当,就会影响在mysql中操作字符串。
查看user表的结构,name字段没有指定字符集,字段会继承自表,表没有设置字符集会继承数据库的,库继承自默认字符集,整个运行环境是依赖于默认的字符集
desc user;
+-------+----------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------+----------+------+-----+---------+-------+
| name | char(30) | YES | | NULL | |
+-------+----------+------+-----+---------+-------+
字符集可能包含中文,也可能不包含中文
比如,使用默认字符集latin1,插入一些内容,出现了一个warning警告 Query OK, 1 row affected, 1 warning (0.00 sec)
insert into user (name) values ('简体中文china');
有点小问题, 但是数据确实插进去了
查看插入的记录
select * from user;
看到 简体中文 四个字变成了4个问好
+-----------+
| name |
+-----------+
| ????china |
+-----------+
出现这样的情况就是在建立表、库、字段的时候,都没有指定合适的字符集,继承了默认的字符集,
但是默认latin1是西欧的字符集,不支持中文的多字节的文字形式,也就是说"简体中文"这个字符串无法转换成latin1,
无法转换在转换的过程中就会把中文劈开,这样就产生了问好乱码的字符。
最好在建立表的时候就把字符集设置好,就不会有这样的乱码问题了,
这是第一个产生乱码的问题,由于字符集指定不当,有多字节、双子字节向单字节转换的时候,转换不成功,
因为在字符集里面根本就没有这个字,转换不了所以就产生了一个问好,这是默认字符集的使用场景。
2、当前选中的数据库字符集 character_set_database
这个character_set_database的值会经常变得
比如,查看当前选中的库
select database();
当前使用的库是dbtest
+------------+
| database() |
+------------+
| dbtest |
+------------+
查看当前使用的数据库dbtest的字符集
show variables like '%character%';
+--------------------------+--------------------------------+
| Variable_name | Value |
+--------------------------+--------------------------------+
| character_set_client | gbk |
| character_set_connection | utf8 |
| character_set_database | latin1 | 当前库是latin1西欧字符集
| character_set_filesystem| binary |
| character_set_results | gbk |
| character_set_server | latin1 |
| character_set_system | utf8 |
| character_sets_dir | D:\xampp\mysql\share\charsets\ |
+--------------------------+--------------------------------+
再建一个库dbbox,指定gbk的字符集
create database dbbox default character set gbk;
选择(使用)dbbox数据库,再看一下环境变量
use dbBox; show variables like '%character%'; -- 查看dbBox环境变量
character_set_database 就是当前操作库的字符集
+--------------------------+--------------------------------+
| Variable_name | Value |
+--------------------------+--------------------------------+
| character_set_client | gbk |
| character_set_connection | gbk |
| character_set_database | gbk | 当前操作的数据库的字符集变成gbk
| character_set_filesystem | binary |
| character_set_results | gbk |
| character_set_server | latin1 |
| character_set_system | utf8 |
| character_sets_dir | D:\xampp\mysql\share\charsets\ |
+--------------------------+--------------------------------+
3、系统元数据字符集 character_set_system
什么是元数据?
比如,操作删除一个数据库dbbox,
1). 删除语句(命令)drop database if exists dbbox;
2). 在执行删除命令的时候,用到了一个字符串 "dbbox"
3). 既然 "dbbox" 是字符串,他肯定有字符集,
他的字符集是 character_set_system,用来指定的就是这个的字符集。
再创建一个数据库
1). 创建数据库dbi,指定字符集utf8
2). 选择库dbi
3). 创建表user,指定数据表的字符集utf8
4). 创建name字段,指定字段的字符集gbk
create database dbi default character set utf8; use dbi; create table user( name char(30) character set gbk )default character set utf8;
查看user表的创建信息
show create table user\G
*************************** 1. row ***************************
Table: user
Create Table: CREATE TABLE `user` (
`name` char(30) CHARACTER SET gbk DEFAULT NULL -- 字段name指定的gbK字符集,是字段里面数据的字符集
) ENGINE=InnoDB DEFAULT CHARSET=utf8 -- user表的字符集是utf8
user表的字符集是utf8,字段name的字符集是gbk,
我们要清楚的知道,指定name字段的字符集,是给插入到字段里面的数据用的
查询user表时候的sql语句 select * from user;
1). 执行查询命令里面的 "user" 是字符串
2). 包括"select * from" 这些都是字符串,
3). 既然是字符串,肯定也有字符集,这些字符集是由这个character_set_system,系统元数据的字符集指定的。
存储函数、存储过程,这些函数的名称等等,字符集都是由系统元数据字符集指定的,
元数据字符集一般是只读的,我们没必要去改变它。
4、Mysql字符集设置目录 character_sets_dir
character_sets_dir | D:\xampp\mysql\share\charsets\
可以在目录里面可以查看,有一堆字符集的设置规则
5、文件系统的字符集 character_set_filesystem
character_set_filesystem = binary;
文件系统肯定用二进制来保存是比较合理的。
以上几个字符集设置都不用去管,
无非选择当前库 character_set_database 字符集可能会造成些到影响,解决办法就是在建表的时候就要指定字符集。
五、乱码解决方案
看剩下的三个
+--------------------------+--------------------------------+
| Variable_name | Value |
+--------------------------+--------------------------------+
| character_set_client | gbk |
| character_set_connection | utf8 |
| character_set_results | gbk |
+--------------------------+--------------------------------+
上面说的 set names gbk; 就是来设置这三个的,
不建议用这种方式设置,用另外一种方式设置。
刚参加工作用 set names gbk 方式来设置字符集,用addslashes()函数方式来转义没问题,
如果工作一段时间后,再用这种方式来设置就说不过去了,是经验太少了。
这三个字符集是怎么使用的?
回到命令行(cmd):
命令行就是一个客户端,当我们输完一个命令之后,命令行会把这个命令发送给服务器端,
以后可能会通过Mysql桌面软件,也可能通过PHP脚本来发送,也可能通过JAVA脚本来发送,
比如,发送这样一条查询语句 select * from user where name = "李四"; 到Mysql服务器
第一步:成功链接msyql
第二步:mysql会对sql语句进行解析
第三步:解析正确之后(如果是正确的),会交给存储引擎
第四步:存储引擎(如果有索引,根据索引引擎),按照索引规则把数据读取出来
第五步:数据读取出来之后,再返回给客户端(CMD)
链接成功msyql --> mysql对sql语句解析(解析正确) --> 交给存储引擎 --> 存储引擎把数据读取出来 --> 再返回给客户端(CMD)
从客户端发送sql语句的时候,字符串"李四"依据的字符集,是依据客户端的字符集,
客户端是PHP文件、或是命令行、或者是一个桌面软件,非二进制的字符串都有字符集的问题,
显然"李四"是一个非二进的字符串,它依赖客户端的字符集,
假如"李四"是从PHP页面发送到Mysql服务器的
1). PHP页面的字符集是gbk,
2). "李四"的字符集也是gbk,是双字节的字符
3). select语句查找user表,user表是utf8的字符集,如果user表里面有一万条记录,
"李四"会一条一条的比对(当然为了效率,会建立索引),凡是name列的值等于"李四"的都取出来
在一条一条比对的时候就产生问题了!
1). 发送过来的"李四"是gbk的,而user表里面的数据是utf8的
2). utf8是多字节的(每一个中文是三个字节),gbk是两个字节的
3). "李四"两个字节的gbk,是没法和utf8进行比对的,因为他俩是不同的东西,必须把他两转化成相同的东西再进行比对,
就像现实生活中一个房子和一辆车子进行比对,怎么比对呢?
要把他俩转一下,转换成一个可以比对的标量,转换成人民币
发送过来的"李四"字符集是gbk,要和表里面的10000条数据进行比对,要转一下字符集,
想一下:
转换的时候要是把"李四"gbk,转换成utf8?
还是把表里面的10000条数据的utf8,转换成gbk呢?
很简单,肯定是要把"李四"的gbk转成utf8,这样只转"李四"转一次都够了,
如果把user表里面的10000条数据,都和"李四"进行比对要转10000次,一次和10000次之间肯定选择转的少的那个,
所以mysql在转换的时候会转换客户端的字符集。
6、客户端字符集 set character_set_client=gbk
set character_set_client=gbk 代表客户端的字符集,
客户端在发送的时候,会带着客户端字符集过来,不然没法比对,
不确定客户端是西欧文字的、还是utf8、还是gbk、还是big5码,肯定要知道客户端是什么字符集才能检索。
带着客户端的字符集的"李四",是一个双字节的gbk字符,
客户端发送过来之后,不会直接转换成字段,先要转换成一个链接字符集,
然后在由链接字符集转换成与字段一样的字符集,
比如,数据表的name字段是utf8
1). "李四"通过客户端发出来,字符集是gbk,
2). 字段name的字符集是utf8(一般连接字符集和客户端字符集设置成一致的)
3). 连接字符集把"李四",再转化成字段的utf8字符集,把"李四"变成utf8
"李四"通过客户端字符集是gbk -> 转换成连接字符集 -> 然后在转换成字段字符集
"李四"现在是链接字符集了,链接字符集在跟数据字段进行比对的时候,就会根据字段是什么字符集,再把"李四"转换成与字段一样的字符集,
字段是utf8经过转换"李四"也是utf8了
7、链接字符集 set character_set_connection=utf8
把字段name的字符集从gbk改成utf8
alter table user modify name char(30) character set utf8; show create table user\G
*************************** 1. row ***************************
Table: user
Create Table: CREATE TABLE `user` (
`name` char(30) CHARACTER SET utf8 DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=latin1
现在"李四"和数据表name字段都是一致的字符集utf8了,肯定能比对的了,
插入表里两条数据
insert into user (name) values ('张三'),('李四'); select * from user;
+------+
| name|
+------+
| 张三 |
| 李四 |
+------+
检索name字段里有"李四"的
select name from user where name = "李四";
+------+
| name|
+------+
| 李四 |
+------+
客户端(命令行)是gbk的
先把客户端的gbk转换成链接字符集gbk,
再把链接字符集"李四",转换成字段name的utf8字符集,读取出数据
读取的数据的字符集是utf8,现在要把utf8的数据返回去,
比如返回给PHP文件,PHP文件再返回给客户端的浏览器,最终呈现在用户面前。
读取的数据集是utf8,返回给的客户端,客户端是gbk的,
utf8硬转gbk的话肯定是乱码,因为gbk是双字节的,utf8是三个字节的,
这个时候需要一个返回结果的字符集
8、返回结果字符集 set character_set_results=gbk
读取出的数据,读取出来之后,再转换成一个客户端的字符集,然后在把结果集返回给客户端。
客户端如果是PHP,PHP还要返回给客户端浏览器HTML(一般HTML与PHP是一样的字符集,减少一个中间转换的过程)
第一步:客户端字符集向服务器发送数据,带着gbk字符集发送
第二步:先发送到连接字符集,先转换成链接字符集,再由链接字符集转换跟成字段一样字符集utf8
第三步:与数据库中的数据比对,读取完之后转换成客户端字符集,然后返回结果集给客户端
如果有第四步,就要返回给HTML
经过这几步转换,如果某一步出现问题就是乱码,
比如,我们的命令行客户端是gbk的,但是我们告诉服务器(告诉错了),告诉服务器是utf8,结果就会产生乱码,
如果服务器返回的结果集不对,也会是乱码。
set names utf8; 这一条命令
1). 相当于把客户端(CMD)、链接字符集、返回结果集的字符集三个都变成了uft8
2). 我们进行查询 select name from user where name = "李四";
3). 返回 Empty set (0.00 sec),什么数据也没查询出来
再改回gbk set names gbk;
又能查询到数据 select name from user where name = "李四";
+------+
| name|
+------+
| 李四 |
+------+
因为"李四"这两个字是有字符集,字符集是gbk,客户端的字符集是gbk,
实际上客户端发过来的"李四"是gbk,但是告诉服务器发过来的是utf8,
到链接字符集(connection)这里就不转换了,客户端发过来的"李四"一个汉字占两个字节,两个字占四个字节的,字段里面一个汉字占三个字节的,两个汉字占六个字节,用四个字节的在字段里找六个字节,肯定找不到结果就是空的。
如果数据量大用模糊查询,碰巧能找到几个字符,返回的也是乱码或者跟想象的不一样。
PS:ubuntu、苹果等系统,在发送语句的时候,会把客户端命令行的字符集硬转,可能看不到乱码的结果,但是要知道这个原理。
返回的结果集是utf8,客户端是gbk,肯定是要转一下,
set character_set_resultes 返回结果字符集是必须要用的,结果集要和客户端一致,转成正确的结果集反回去,
比如
单独设置返回字符集是utf8
set character_set_results = utf8;
看一下字符集环境
show variables like '%character%';
+--------------------------+--------------------------------+
| Variable_name | Value |
+--------------------------+--------------------------------+
| character_set_client | gbk | 客户端字符集是对的
| character_set_connection | gbk | 连接字符集是对的
| character_set_database | utf8 |
| character_set_filesystem| binary |
| character_set_results | utf8 | 返回字符集 不对
| character_set_server | utf8mb4 |
| character_set_system | utf8 |
| character_sets_dir | D:\xampp\mysql\share\charsets\ |
+--------------------------+--------------------------------+
查看数据表能找到数据,因为连接字符集设置的没有错
select name from user;
但是返回的是utf8,客户端是gbk,必然是乱码
+-----------+
| name |
+-----------+
| 寮犱笁 |
| 鏉庡洓 |
+-----------+
客户端是gbk两个字节,返回的是三个字节的utf8肯定是乱码,某一个环节出错都不行。
把链接字符集改成utf8会怎么样?
客户端字符集、连接字符集、返回字符集,先全部恢复成gbk
set names gbk;
更改链接字符集为utf8
set character_set_connection=utf8;
查看字符集环境
show variables like '%character%';
+--------------------------+--------------------------------+
| Variable_name | Value |
+--------------------------+--------------------------------+
| character_set_client | gbk | 客户端字符集
| character_set_connection | utf8 | 连接字符集
| character_set_database | utf8 |
| character_set_filesystem| binary |
| character_set_results | gbk | 返回字符集
| character_set_server | utf8mb4 |
| character_set_system | utf8 |
| character_sets_dir | D:\xampp\mysql\share\charsets\ |
+--------------------------+--------------------------------+
1). 发送客户端的字符集gbk是正确的,
字符串"李四"是gbk的字符集,发送到服务器,
2). "李四"是gbk,然后转换成链接字符集utf8,
3). 链接字符集再转成字段字符集是utf8,
4). 然后字段返回结果集是gbk
select name from user where name = "李四";
所以能看到正确的数据
+-----------+
| name |
+-----------+
| 李四 |
+-----------+
我们说 链接字符集 基本上跟 客户端一致就可以了,为什么这么说呢?
因为我们可能操作的是多个数据库,或者操作多个数据表。
比如,再建设一个数据库Fang,指定字符集gbk
create database Fang default character set gbk;
创建表jing,不指定表的字符集继承数据的,表字符集是gbk
use Fang; -- 选择(使用)Fang库 create table jing( name char(30) );
查看创建表的信息,集成数据库的字符集gbk
show create table jing;
+-------+------------------------------------------------------------------------------------------+
| Table | Create Table |
+-------+------------------------------------------------------------------------------------------+
| jing | CREATE TABLE `jing` (
`name` char(30) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=gbk |
+-------+------------------------------------------------------------------------------------------+
看一下字符集环境
show variables like '%character%';
+--------------------------+--------------------------------+
| Variable_name | Value |
+--------------------------+--------------------------------+
| character_set_client | gbk | 客户端是gbk
| character_set_connection | utf8 | 链接字符集是utf8
| character_set_database | gbk |
| character_set_filesystem| binary |
| character_set_results | gbk | 返回客户端的字符集是gbk
| character_set_server | latin1 |
| character_set_system | utf8 |
| character_sets_dir | D:\xampp\mysql\share\charsets\ |
+--------------------------+--------------------------------+
现在选择的数据库是Fang
select database();
+------------+
| database() |
+------------+
| fang |
+------------+
插入两条数据到jing表
insert into jing (name) values ("李四"),("王五"); select * from jing;
回归一下字符集设置
1). 客户端是gbk
2). 从客户端发送过来是gbk,转换成链接字符集转成utf8,转换了一次,
3). 数据表是gbk的,从链接字符集utf8,又转换成表的字符集gbk,转换了两次
+------+
| name|
+------+
| 李四 |
| 王五 |
+------+
查找"李四"
select * from lili where name = "李四";
+------+
| name|
+------+
| 李四 |
+------+
client(GBK) -> connection(UTF8) -> field(GBK) -> result(GBK)
显然链接字符集connection(UTF8)是多余的,
如果把链接字符集设置成一样的gbk,就能减少这样一个步骤,
所以这就是为什么,一般时候链接字符集和客户端字符集要一样。
还要记住如果结果集返回错了是乱码,但是链接(connection)设置不同字符集,就不一定是乱码(是不一定但也会有乱码的情况)。
有时候链接字符集会有一个多余的工作,所以一般的时候,我们链接字符集就跟客户端指定成一样的,这样大部分时候会少一个转换的过程。
这样设置 set names gbk; 我们就保证了,
客户端是gbk、链接是gbk、返回结果集是gbk,然后jing数据表也是gbk,就变成了全是gbk就不用转了,
不用转了就剩一点事
想一个问题:
为什么客户端和链接字符集是一样的,
为什么还要有一个客户端字符集呢?
为什么mysql不把中间层"链接字符集"给省略掉?
结论是,链接字符集不能省略掉,因为客户端可能不让它按照gbk来发,可能按照二进制来发过来,让客户端用二进制的形式发过来,
为什么用二进制?为了安全防止注入。
20201104
人间非净土,各有各的苦,同是红尘悲伤客,莫笑谁是可怜人。