Go to comments

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脚本来发送,

客户端发送语句到mysql服务器.jpg

比如,发送这样一条查询语句 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


人间非净土,各有各的苦,同是红尘悲伤客,莫笑谁是可怜人。



Leave a comment 0 Comments.

Leave a Reply

换一张