MySQL数据库能够支持多种字符集,并且每种字符集都拥有一个或多个校对规则,那么为应用端选择一个合适的字符集和校对规则就需要DBA把握。
那么,如何查看当前使用的字符集和校对规则呢
字符集和校对规则的设定,从大的维度上来说,有两个层次:
首先是连接数据库,执行操作时所使用的字符集。
其次保存数据时所使用的字符集。
1、服务端设置默认字符集
MySQL服务启动过程中,字符集的设定就己经生效,系统会确定默认所使用的字符集和校对规则,并且,在启动时指定的字符集和校对规则,对应整个 MySQL服务的全局有效。也就是说,如果没有在更细的粒度对字符集和校对规则进行设置,那么接下来做的所有操作,其字符集的设定都会继承全局粒度所设定 的默认字符集和校对规则。
编译MySQL软件时指定的两个参数:
-DDEFAULT_CHARSET=utf8 \
-DDEFAULT_COLLATION=utf8_general_ci \
这两个参数决定了本机所运行的MySQL服务,在没有做任何其他字符集相关设定的情况下默认时的字符集和校对规则。
MySQL中指定字符集的方式太灵活了,即使是全局粒度的设置,方法也不止一种:
在编译安装MySQL (仅限源码编译安装方式)时指定。
启动MySQL服务时通过参数指定。
在参数文件中配置,启动MySQL服务时加载参数文件的方式使之生效。
在MySQL服务运行期间实时修改。
(1)编译MySQL软件时指定
编译MySQL数据库时,就可以通过default_charset和default_collation两个编译参数,指定默认的字符集和校对规则,如 上所示,我们之前设定的字符集为utf8。而且即使不明确指定,这两个参数也仍然有值,默认情况下将使用latinl字符集和 latinl_swedish_ci校对规则。
(2)在启动MySQL服务时指定
启动MySQL数据库服务时(mysqld命令或mysqld safe命令)有一堆的参数可以设定,其中与字符集相关的是下面两项系统参数:
-character_set_server:指定全局粒度的默认字符集。
-collation_server:指定全局粒度的默认校对规则。
它们是有默认值的,它们的默认值会继承编译MySQL软件时DEFAULT_CHARSET和DEFAULT_C0LLATI0N两个参数所指定的值。
(3)参数文件中配置
MySQL服务在启动时,可以通过指定一个参数文件的形式(也就是前面创建的my.cnf文件),来简化启动命令行中指定的选项,那么,同样可以将字符集 和校对规则的参数设置放在参数文件中。这两项参数放到参数文件中时,参数名与命令行的选项名相同,不过指定参数时不需要字符了,例如:
character_set_server=utf8
collation_server=utf8_general_ci
(4)MySQL服务运行期间修改
前面3种方式均可用来指定字符集和校对规则,但若想要变更字符集设置时,动静就太大了,最轻量的变更,想要生效都得重启MySQL服务。实际上大可不必如 此周折,这两项参数,在MySQL服务运行期间作为系统变量存在,而系统变量在MySQL服务运行期间,多数都是可以被实时修改的,控制字符集和校对规则 的系统变量就属于可被修改那一类。
我们可以通过SHOW VARIABLES命令查看当前的系统变量及其变量值,比如说,执行SHOW VARIABLES语句查看字符集和校对规则这两项系统变量的值:
(system@localhost)[(none)]> show global variables like '%server';
+----------------------+-----------------+
| Variable_name | Value |
+----------------------+-----------------+
| character_set_server | utf8 |
| collation_server | utf8_general_ci |
+----------------------+-----------------+
2 rows in set (0.00 sec)
若要将全局粒度默认的字符集改为gbk,那么执行下列命令即可:
(system@localhost)[(none)]> set global character_set_server=gbk;
Query OK, 0 rows affected (0.01 sec)
(system@localhost)[(none)]> show global variables like '%server';
+----------------------+----------------+
| Variable_name | Value |
+----------------------+----------------+
| character_set_server | gbk |
| collation_server | gbk_chinese_ci |
+----------------------+----------------+
2 rows in set (0.00 sec)
注意看collation_server参数的值,它的值也发生变化,这是因为校对规则是基于字符集的,因此尽管我们没有显式地修改校对规则,但是它的值也自动被修改为所设定的gbk字符集的默认校对规则。
说明:全局粒度是什么意思?
在MySQL服务运行期间,修改系统变量的值也有作用域的。MySQL中的系统变量的作用域 分为全局(global)和当前会话(session)两类。
对于全局的修改,作用于修改成功后新创建的会话,但对当前执行修改的会话无效,如果是会话级的修改(执行SET命令并且未指定global选项就是会话级修改),则只作用于当前会话,本次会话结束后,所做修改也自动结束。
此外,需要注意MySQL中即使是全局的参数修改,其作用域最多也只在当前MySQL服务的生命周期内,MySQL服务一旦重启,那么之前的设置也全部元 效(不管是全局还是会话)。因此要是希望所做的设置永久生效,那么除了在全局粒度修改外,还必须手动修改初始化参数文件, 或者是在启动MySQL服务时,在命令行中显式指定相关选项值。
2、连接时指定
客户端连接到MySQL数据库服务后所使用的字符集,与MySQL服务中设定的若干系统变量有关,就是说默认会继承和使用MySQL服务中设置的字符集, 也就是全局系统变量character_set_server和collation_server中指定的字符集和校对规则。
所有环节的字符集设置都相同,理论上就不会出现乱码的情况了。理论确实是这样,但实际操作时会面临两方面的挑战。一个是需要它不一致,有时候默认字符集确 实不能满足需求(就比如当前字符集设置的是latinl,但我们实际环境中使用的是gb2312/gbk字符集)。总之,确实出现了不一致,那我们能怎么 办呢?作为执行者,我们不仅是确保字符被正确地存储,还要确保它能被正确地显示。因此,还是需要多了解机制,搞清楚不同环节对字符集的处理。
字符集并不仅仅只有当存储字符数据时才需要,在客户端与MySQL服务器端通信时, 字符集同样起着重要的作用,很多情况下,显示的字符出现乱码,也有可能是因为客户端当前的字符集设置与MySQL服务器端保存字符时所用的字符集不相符所致。
举一个例子,大家可以先忽略语法。首先创建一个会话插入一条记录:
(system@localhost)[(none)]> show variables like 'character_set_client';
+----------------------+-------+
| Variable_name | Value |
+----------------------+-------+
| character_set_client | gbk |
+----------------------+-------+
1 row in set (0.01 sec)
(system@localhost)[(none)]> use test
Database changed
(system@localhost)[test]> show tables;
Empty set (0.00 sec)
(system@localhost)[test]> create table test1(v1 varchar(40));
Query OK, 0 rows affected (0.05 sec)
(system@localhost)[test]> insert into test1 values('我爱我的祖国');
Query OK, 1 row affected (0.30 sec)
(system@localhost)[test]> select * from test1;
+--------------+
| v1 |
+--------------+
| 我爱我的祖国 |
+--------------+
1 row in set (0.28 sec)
而后在另一个会话中读取这条记录,显示的结果有所不同:
(system@localhost)[(none)]> show variables like 'character_set_client';
+----------------------+-------+
| Variable_name | Value |
+----------------------+-------+
| character_set_client | utf8 |
+----------------------+-------+
1 row in set (0.01 sec)
(system@localhost)[test]> select * from test1;
+--------------+
| v1 |
+--------------+
| |
+--------------+
1 row in set (0.00 sec)
乱码了,可以对这个客户端所使用的字符集进行设置
(system@localhost)[test]> set character_set_results=gbk;
Query OK, 0 rows affected (0.00 sec)
(system@localhost)[test]> select * from test1;
+--------------+
| v1 |
+--------------+
| 我爱我的祖国 |
+--------------+
1 row in set (0.00 sec)
那么,MySQL服务是如何响应客户端操作的字符的字符集呢?连接创建后,用户发出的SQL语句,MySQL服务又是如何响应发送查询的结果集或错误信息 的字符集呢?与系统变量的设定有关。不过,与连接相关的字符集变量可不是character_set_server。客户端信息大致的处理过程如下:
客户端发出的SQL语句,所使用的字符集由系统变量character_set_client来指定。
MySQL服务端接收到语句后,会用到character_set_connection 和 collation_ connection两个系统变量中的设置,并且会将客户端发送的语句字符集由 character_set_client转换到character_set_connection (除非用户执行语句时,己对字符列明确指定了字符集)。对于语句中指定的字符串的比较或排序,还需要应用collation_connection中指定 的校对规则处理,而对于语句中指定的列的比较则无关collation_connection的设置了,因为对象的表列拥有自己的校对规则,它们拥有更高 的优先级。
MySQL服务端执行完语句后,会按照character_set_results系统变量设定的字符集返回结果集(或错误信息)到客户端。
查看当前这些系统变量的值,仍然是使用SHOW VARIABLES语句:
(system@localhost)[(none)]> show global variables like 'character_set\_%';
+--------------------------+--------+
| 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 | gbk |
| character_set_system | utf8 |
+--------------------------+--------+
7 rows in set (0.02 sec)
这几个系统变量的值,默认继承自服务端启动时默认的字符集设置,也就是我们编译时指定的utf8。不过有一项例外,那是因为前而我们将character_set_server的值设置为了gbk。
从这个输出结果看到的是全局的设置。当前,每个连接到MySQL服务的会话,均可以单独设置连接时的字符集,而且针对这几个系统变量,一般也都只设置会话级的变量值 那么我们再来看一下会话级的系统变量值又是什么呢.
(system@localhost)[(none)]> show variables like 'character_set\_%';
+--------------------------+--------+
| 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 |
+--------------------------+--------+
7 rows in set (0.00 sec)
标粗的几项变量值被设置为了gbk,也正是这几项在控制客户端连接和返回信息时, 默认使用的字符集。
如何在创建会话连接后,修改与字符集相关的设定。首先提醒一句,若确实需要修改客户端连接相关的字符集(这种情况很常见),那么并不需要一个个修改这几项系统变量的值,MySQL提供了专用的方法(还不止一种), 可以一次性地修改所有与连接相关的字符集变量设置。
(1)SET NAMES
SET NAMES命令的功能是指定客户端当前会话使用的字符集,语法如下
SET NAMES 'charset_name' [COLLATE 'collation_name']
例如,设置当前会话字符集为gbk,执行命令如下:
(system@localhost)[(none)]> set names gbk;
Query OK, 0 rows affected (0.00 sec)
(system@localhost)[(none)]> 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 | gbk |
| character_set_server | utf8 |
| character_set_system | utf8 |
| character_sets_dir | /usr/local/mysql/share/charsets/ |
+--------------------------+----------------------------------+
8 rows in set (0.03 sec)
从功能上理解的话,SET NAMES n相当于同时执行了下列设置
SET character_set_client = n;
SET character_set_results = n;
SET character_set_connection = n;
(2)SET CHARACTER SET
除了SET NAMES命令外,还有SET CHARACTER SET命令可以实现类似的功能,
该语句的语法也与之类似,具体如下:
SET CHARACTER SET 'charset_name';
执行SET CHARACTER SET命令,相当于设置了下列系统变量:
SET character_set_client = x;
SET character_set_results = x;
SET character_set_connection = @@character_set_database;
SET collation_connection = @@collation_database;
@@character_set_database 表示全局变量。
下面实际执行看看效果。例如,设置当前会话的字符集为latinl,执行命令如下:
(system@localhost)[(none)]> set character set latin1;
Query OK, 0 rows affected (0.00 sec)
(system@localhost)[(none)]> show variables like 'character_set\_%';
+--------------------------+--------+
| Variable_name | Value |
+--------------------------+--------+
| character_set_client | latin1 |
| character_set_connection | utf8 |
| character_set_database | utf8 |
| character_set_filesystem | binary |
| character_set_results | latin1 |
| character_set_server | utf8 |
| character_set_system | utf8 |
+--------------------------+--------+
7 rows in set (0.00 sec)
说明:
ucs2、utfl6、utf16le和utf32不能被作为客户端字符集使用,也就是说使用SET NAMES或 SET CHARACTER SET命令设置这类字符集无效。
(3)固化连接时的字符集设置
正如前面多次提到的,SET NAMES命令和SET CHARACTER SET命令都是基于会话设定的,也就是说,仅作用于当前会话,退出登录后所做设置也就失效了。如果希望设置长期有效,一方面,可以在启动MySQL服务 时,通过设置相关系统变量,达到永久生效的目的(当然这种方式欠缺灵活性);另一方面,就是在客户端进行设置,使得与MySQL相关的客户端命令在连接 时,能够自动使用我们设定好的字符集,避免每次在会话操作前再单独设定。
MySQL数据库的客户端程序,包括mysql、mysqladmin、mysqldump、mysqlimport等命令行工具,都是按照下列规则,读取连接时的默认字符集设定,优先级也是依次递增:
默认情况下,使用编译时指定的默认字符集,在本环境中当然就是了。
程序能够自动检查当前操作系统环境变量中设置的字符集,比如像本地操作系统环境变量LANG或LC_ALL设置的语言,因此用户也可以配置操作系统的环境 量,来修改客户端连接后的默认字符集,但是这里需要注意的一点是,一般来说操作系统能够支持的字符集要比MySQL多,极有可能OS层指定的字符集在DB层没有对应,这种情况下,MySQL仍然会设置编译时指定的字符集为默认字符集了。
对于支持default_character_set选项的命令行工具(常用的命令全都能够支持),可以通过该参数设置连接后的默认字符集。为了简化操 作,甚至可以将default_character_set系统变量放在my.cnf文件的[mysql]或[client]区块,这样当mysql客户 端连接到服务器后,它就能按照前面指定的字符集自动设置character_set_client、character_set_results 以 character_set_connection几个系统变量。
比如说,我们当前有一套网站系统,号称面向全球用户提供服务,当然啦,实际上主 要用户还是以中文群体为主,不过为了保证字符能被正确地识别和存储,默认字符集还是需要设置为utf8(或utf8mb4),以支持更多的语言,而不仅仅 只是中文。可是作为维护者,由于我们的日常操作环境仍是中文占主导,平时更新或维护的数据也都是中文字符,因此在操作时,就需要字符集保持为gb2312 或gbk,我们又不希望每次连接到MySQL服务后都通过SET NAMES进行修改,那么,就可以考虑在参数文件my.cnf中的[mysql]区块,增加一行:
[mysql]
default-character-set=gbk
这样,只要我们使用mysql命令行工具连接到服务器后,连接的默认字符集(即
character_set_client、character_set_resuhs 和character_set_connection几个系统变量)就都会是设定好的GBK字符集了。
涉及字符操作时,先执行SHOW VARIABLES LIKE 'charact%’看一看当前的字符集到底是什么吧,不要因为想当然而导致操作数据出现错误。
3、保存时指定
连接时指定的字符集是确保在交互过程中,字符数据被正确编码,此外,作为数据库的主要功能――存储,更要确保数据在保存时也使用正确的字符集。
在MySQL数据库中,提供4种不同的粒度,用以指定存储时数据使用的字符集:
(1) server,全局级。
(2) database,数据库级。
(3) table,表级。
(4) column,列级。
这4种级别作用域依次递减,不过优先级是依次递增。怎么理解呢?举例来说,当在server级别指定字符集后,如果在database/table /column级未设置字符集,那么默认就会继承server级别的设定,不过如果在低一级别中明确指定,比如创建表时明确指定了字符集,那么该表的字符 集又会以表级指定的为准。
(1)在数据库级指定
每个MySQL服务中的数据库都可以单独设置字符集和校对规则,这种设置既可以在创建数据库时指定,也可以在创建之后通过ALTER DATABASE语句进行修改,指定字符集和校对规则的语法特别简单,具体如下:
[[DEFAULT] CHARACTER SET charset_name]
[[DEFAULT] COLLATE collation_name]
其中[DEFAULT]关键字是可选项,就实际效果来说,指定或不指定都没有区别,大家 直接忽略它也行。
另外需要提示一点的就是,在MySQL中指定字符集,完整句法是“CHARACTER SET n”,这个写法也可以简化为“CHARSET n”,功能完全相同,后者可视作前者的同义词。
下面,我们创建一个名为“ordertable”的数据库,并指定该库的默认字符集为latinl,执 行语句如下:
(system@localhost)[(none)]> create database ordertable charset latin1;
Query OK, 1 row affected (0.73 sec)
修改数据库的字符集也非常简单,比如说修改ordertable库的字符集为utf8,执行命令如下:
(system@localhost)[(none)]> alter database ordertable charset utf8;
Query OK, 1 row affected (0.00 sec)
前面执行了两项操作,不管是创建还是修改,都只指定了字符集,而没有指定校对规则,这是因为字符集和校对规则都是可选参数,大家也可以只指定校对规则而不指定字符集,或者两个都指定,甚至两个都不指定也行,它就继承全局粒度设定的默认值呗。
不过,考虑到字符集和校对规则两个都可选的情况下,实际应用时可能存在多种情况, 下面简要说一说。一般来讲,MySQL会按照下列规则来设置默认的字符集和校对规则:
如果同时指定CHARACTER SET和COLLATE选项,那没说的,就按指定的值处理。
如果仅指定了CHARACTER SET,那么COLLATE会继承指定字符集的默认校对 规则。
如果仅指定了COLLATE选项,那么COLLATE所属的字符集就是该数据库的默认字符集了。
如果两参数均未指定,该数据库将会继承全局粒度所设定的字符集和校对规则,不过继承并不是说直接应用character_set_server,而是另有变量:character_set_database和collation_database。
数据库级的字符集设置,是保存在数据库同名的操作系统目录下的db.opt文件中:
[mysql@lee ~]$ more /data/mysqldata/3306/data/ordertable/db.optt
default-character-set=utf8
default-collation=utf8_general_ci
直接修改该文件也可以达到修改库级字符集设定的目的,但是一般不需要这么干,而且操作系统层修改并不是实时生效的,需要重启MySQL服务才能看到。
要在MySQL中查看某数据库的字符集和校对规则,可以执行如下的命令:
(system@localhost)[(none)]> show create database ordertable;
+------------+---------------------------------------------------------------------+
| Database | Create Database |
+------------+---------------------------------------------------------------------+
| ordertable | CREATE DATABASE `ordertable` /*!40100 DEFAULT CHARACTER SET utf8 */ |
+------------+---------------------------------------------------------------------+
1 row in set (0.00 sec)
数据库的字符集作用域包括在该库下创建的表和列,因此,该库下所有表默认均会继承该库所设置的字符集,甚至于使用LOAD DATA INFILE向该库下对象加载数据,如无明确指定,默认也会继承库一级的字符集。
(2)在表级指定
在创建表对象时,可以明确地给表对象指定字符集和校对规则,如果没有指定的话, 它就会继承所在数据库粒度设定的字符集。建表或修改表时与字符集相关的语法,和在数据库级操作的语法完全相同:
[[DEFAULT] CHARACTER SET charset_name]
[[DEFAULT] COLLATE collation_name]
下面创建一个名为tl的表对象,先不指定字符集,执行语句如下:
(system@localhost)[leedb]> create table tl (id int);
Query OK, 0 rows affected (0.04 sec)
再创建一个名为t2的表对象,指定该表默认字符集为latinl,执行语句如下:
(system@localhost)[leedb]> create table t2 (id int) charset latin1;
Query OK, 0 rows affected (0.03 sec)
分别来查看这两个对象的字符集:
(system@localhost)[leedb]> show create table tl;
+-------+--------------------------------------------------------------------------------------+
| Table | Create Table |
+-------+--------------------------------------------------------------------------------------+
| tl | CREATE TABLE `tl` (
`id` int(11) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8 |
+-------+--------------------------------------------------------------------------------------+
1 row in set (0.01 sec)
(system@localhost)[leedb]> show create table t2;
+-------+----------------------------------------------------------------------------------------+
| Table | Create Table |
+-------+----------------------------------------------------------------------------------------+
| t2 | CREATE TABLE `t2` (
`id` int(11) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=latin1 |
+-------+----------------------------------------------------------------------------------------+
1 row in set (0.03 sec)
如结果中所示,当创建对象时,没有指定字符集,那么它就会继承数据库粒度的字符集,如果明确指定了字符集,那么该表对象的字符集就是我们指定的字符集。
这里列出的两个示例同样没有涉及校对规则,在表粒度的校对规则继承与数据库粒度时基本相同,唯一的区别是数据库粒度时字符集是继承自全局粒度,而在表粒度默认的字符集和校对规则继承自数据库粒度的设置。
(3)在列级指定
所有字符类型的列(即列的数据类型为CHAR/VARCHAR或TEXT,另外ENUM和SET类型也适用)均可以在创建时指定字符集和校对规则,当然也可以通过ALTER TABLE命令对字符集进行修改。在通过ALTER TABLE语句定义列类型时,指定字符集和校对规则的语法如下:
col_name {CHAR | VARCHAR | TEXT} (col_length)
[CHARACTER SET charset_name]
[COLLATE collation_name]
col_name {ENUM | SET} (val_list)
[CHARACTER SET charset_name]
[COLLATE collation_name]
从语法上大家应该也看出来了,跟前面讲过的库级粒度和表级粒度一样,只不过列级的粒度最小,它能直接定义某个字符类型列的字符集。具体处理时,列粒度的字 符集和校对规则的处理跟前面库级和表级也都差不多,唯一的差别就是,列粒度的字符集和校对规则默认会继承表级粒度的设置。
修改字符集设置对己有数据的影响:
前面讲过存储相关的字符集粒度分为4级:全局、库、表、列。可以说,全局和库级粒度的字符级设置可任意修改,它们不会对现有数据造成影响,最多所能影响到 的也是修改设置后新增的数据。不过对于表粒度和列粒度中字符集的修改就需要慎重处理了,因为表和列中真正保存着数据,对现有数据的字符集进行变更,如果操 作不慎,是会丢失数据的。
当我们执行ALTER TABLE语句,修改表对象或表中某个字符类型列的字符集时,MySQL都要尝试,将字符从原有字符集转换成新指定的字符集进行编码保存,在这个过程中, 如果字符集之间不兼容,就会丢失数据。这部分数据如果是在转换过程中丢失的,那么就没有回滚的可能,也就是说在没有备份的情况下,丢失的数据就永远丢失 了,而不是说你再把字符集修改回原样,它就还能回去。
什么情况下会丢失数据,简单概括就两句话: 子集到超集的转换没有问题,但超级转换成子集会有问题。
举例来说,latin1字符集中的西方字符在gb2312字符集中都有对应(支持),那么将字符集从latinl转换到gb2312就是安全的,同理,gb2312到gbk也是安全的,gbk到utf8也是安全的,当然latinl到utf8更是安全的了。
反之,utf8转换到gbk就不一定,比如说utf8字符集中能够支持俄文字符,但是gbk不支持呀,这种情况下,将utf8字符集的表或列转换成gbk 字符集,就有可能会丢失数据。可是既然并不能完全支持,为什么这里又说有可能呢,这因为尽管不是完全支持,但必须还是有能够支持的字符嘛,比如表或列的字 符集尽管是utf8,但是如果其中存储的都是gbk能够支持的中文字符,并没有不被支持的字符,这种情况超集到子集的转换也是安全的。
本文出自 “Lee” 博客,转载请与作者联系!