最近一直在弄一个有关mysql proxy的项目,中间涉及到对sql语句的解析。之前对mysql了解的并不是太多,随着项目的推进,不得不补补了。今天突然看到mysql的charset设置,自己很多地方都没法解释清楚,对一些相关的sql语句背后的工作机制一知半解,于是找了点资料补习下,顺便做点儿笔记。
一、字符集和校对规则
字符集是一套符号和编号。校对规则是在字符集内用于比较字符的一套规则。
字符(character)是人类 语言中最小的表义符号。例如字母’A’、’B’等;给定一系列字符,对每个字符赋予一个数值,用数值来代表对应的字符,这一数值就是字符的编码(Encoding)。例如,我们给字符’A’赋予数值0,给字符’B’赋予数值1,则0就是字符’A’的编码,1就是字符’B’的编码;给定一系列字符并赋予对应的编码后,所有这些字符和编码对组成的集合就是字符集(Character Set)。
校对规则(collation)是指在同一字符集内字符之间的比较规则;确定校对规则后,才能在一个字符集上定义什么是等价的字符,以及字符之间的大小关系;每个校对规则唯一对应一种字符集,但一个字符集可以对应多种校对规则,其中有一个是默认校对规则(Default Collation)。
mysql能够做这些事情:
两个不同的字符集不能有相同的校对规则;另外,mysql中的校对规则名称遵从命名惯例:它们以对应的字符集名称开头;以_ci(大小写不敏感)、_cs(大小写敏感)或_bin(按编码值比较)结尾。
mysql的字符集和校对规则有4个级别的默认设置:服务器级、数据库级、表级和连接级。
1、服务器字符集和校对
mysql服务器有一个服务器字符集和一个服务器校对规则,它们均不能设置为空。
mysql按照如下方法确定服务器字符集和服务器校对规则:
当启动mysqld时,根据使用的初始选项设置来确定服务器字符集和校对规则。可以使用--default-character-set设置字符集,并且可以在字符集后面为校对规则添加--default-collation。如果没有指定一个字符集,那就与--default-character-set=latin1相同。如果仅指定了一个字符集(例如,latin1),但是没有指定一个校对规则,那就与--default-charset=latin1 --default- collation=latin1_swedish_ci相同,因为latin1_swedish_ci是latin1的默认校对规则。因此,以下三个命令有相 同的效果:
shell> mysqld
shell> mysqld --default-character-set=latin1
shell> mysqld --default-character-set=latin1 --default-collation=latin1_swedish_ci
如果希望在从源程序构建时更改默认服务器字符集和校对规则,可以通过重新编译,使用:--with-charset和--with-collation作为configure的参量。例如:
shell> ./configure --with-charset=latin1
或者:
shell> ./configure --with-charset=latin1 --with-collation=latin1_german1_ci
mysqld和configure都验证字符集/校对规则组合是否有效。如果无效,每个程序都显示一个错误信息,然后终止。
当前的服务器字符集和校对规则可以用作character_set_server和collation_server系统变量的值。在运行时能够改变这些变量的值,从而改变运行时的服务器字符集和校对规则。
2、数据库字符集和校对
每一个数据库有一个数据库字符集和一个数据库校对规则,它不能够为空。
create database和alter database语句有一个可选的子句来指定数据库字符集和校对规则:
create database db_name
[[default] CHARACTER SET charset_name] [[default] COLLATE collation_name];
alter database db_name
[[default] CHARACTER SET charset_name] [[DEFAULT] COLLATE collation_name];
例如:
create database db_name default CHARACTER SET latin1 COLLATE latin1_swedish_ci;
mysql这样选择数据库字符集和数据库校对规则:
mysql的create database ... default CHARACTER SET ...语法与标准sql的create table ... CHARACTER SET ...语法类似。因此,可以在同一个mysql服务器上创建使用不同字符集和校对规则的数据库。
如果在create table语句中没有指定表字符集和校对规则,则使用数据库字符集和校对规则作为默认值。
默认数据库的字符集和校对规则可以用作character_set_database和 collation_database系统变量。无论何时默认数据库更改了,服务器都设置这两个变量的值。如果没有默认数据库,这两个变量与相应的服务器级别的变量(character_set_server和collation_server)具有相同的值。
3、表字符集和校对
每一个表有一个表字符集和一个校对规则,它不能为空。
为指定表字符集和校对规则,create table和alter table语句有一个可选的子句:
create table tbl_name ( column_list )
[default CHARACTER SET charset_name [COLLATE collation_name ]];
alter table tbl_name
[default CHARACTER SET charset_name] [COLLATE collation_name ]
例如:
create table t1 ( ... ) default CHARACTER SET latin1 COLLATE latin1_danish_ci;
mysql按照下面的方式选择表字符集和校对规则:
如果在列定义中没有指定列字符集和校对规则,则默认使用表字符集和校对规则。表字符集和校对规则是mysql的扩展;在标准SQL中没有。
4、列字符集和校对
每一个“字符”列(即,CHAR、VARCHAR或TEXT类型的列)有一个列字符集和一个列校对规则,它不能为空。列定义语法有一个可选子句来指定列字符集和校对规则:
col_name {CHAR | VARCHAR | TEXT} ( col_length )
[CHARACTER SET charset_name [COLLATE collation_name ]]
例如:
create table table1
(
column1 VARCHAR(5) CHARACTER SET latin1 COLLATE latin1_german1_ci
);
mysql按照下面的方式选择列字符集和校对规则:
CHARACTER SET和COLLATE子句是标准的SQL。
5、连接字符集和校对
一些字符集和校对规则系统变量与客户端和服务器的交互有关,前面已经提到:
在客户端和服务器的连接处理中也涉及了字符集和校对规则变量。每一个客户端有一个连接相关的字符集和校对规则变量。
考虑什么是一个“连接”:它是连接服务器时所作的事情。客户端发送SQL语句,例如查询,通过连接发送到服务器。服务器通过连接发送响应给客户端,例如结果集。对于客户端连接,这样会导致一些关于连接的字符集和校对规则的问题,这些问题均能够通过系统变量来解决:
服务器使用character_set_client变量作为客户端发送的查询中使用的字符集。
转换时,服务器使用character_set_connection和collation_connection系统变量。它将客户端发送的查询从character_set_client所指示的字符集转换到character_set_connection所指示的字符集(除非字符串文字具有象_latin1 或_utf8的引介词)。collation_connection对比较文字字符串是重要的。对于列值的字符串比较,它不重要,因为列具有更高的校对规则优先级。
进行内部操作前将请求数据从character_set_connection转换为内部操作字符集,其确定方法如下:
a、使用每个数据字段的CHARACTER SET设定值;
b、若上述值不存在,则使用对应数据表的DEFAULT CHARACTER SET设定值(mysql扩展,非sql标准);
c、若上述值不存在,则使用对应数据库的DEFAULT CHARACTER SET设定值;
d、若上述值不存在,则使用character_set_server设定值。
character_set_results变量指示服务器返回查询结果到客户端使用的字符集。包括结果数据,例如列值和结果元数据(如列名)。
能够调整这些变量的设置,或可以依赖默认值。
有两个语句影响连接字符集:
set names ' charset_name ' SET CHARACTER SET charset_name;
set names显示客户端发送的sql语句中使用什么字符集。因此,set names 'cp1251'语句告诉服务器“将来从这个客户端传来的信息采用字符集cp1251”。它还为服务器发送回客户端的结果指定了字符集。(例如,如果你使用一个SELECT语句,它表示列值使用了什么字符集)
set names ' x '语句与这三个语句等价:
set character_set_client = x ;
set character_set_results = x ;
set character_set_connection = x ;
将x设置为character_set_connection也就设置了collation_connection是x的默认校对规则。
set CHARACTER SET语句是类似的,但是为默认数据库设置连接字符集和校对规则。set CHARACTER SET x语句与这三个语句等价:
SET character_set_client = x ;
SET character_set_results = x ;
SET collation_connection = @@collation_database;
当一个客户端连接时,它向服务器发送希望使用的字符集名称。服务器为那个字符集设置 character_set_client、character_set_results和 character_set_connection变量。(实际上,服务器为使用该字符集执行一个set names操作。)
对于mysql客户端,如果你希望使用与默认字符集不同的字符集,不需要每次启动时执行set names语句。可以在mysql语句行中或者选项文件中添加一个--default-character-set选项设置。例如,你每次运行mysql时,以下的选项文件设置把三个字符集变量修改为utf8:
[mysql]
default-character-set=utf8
例如:假设column1定义为CHAR(5) CHARACTER SET latin2。如果没有设定set names或set CHARACTER SET,那么对于select column1 from t,当连接后,服务器使用客户端指定的字符集返回列column1的所有值。另一方面,如果你设定set names 'latin1'或set CHARACTER SET latin1,那么发送结果之前,服务器转换latin2值到latin1。转换可能会丢失那些不属于两种字符集的字符。
如果不希望服务器执行任何转换,设置character_set_results为NULL:
mysql> SET character_set_results = NULL;
6、字符串文字字符集和校对
每一字符串字符文字有一个字符集和一个校对规则,它不能为空。
一个字符串文字可能有一个可选的字符集引介词和COLLATE子句:
[_ charset_name ] ' string ' [COLLATE collation_name ]
例如:
select ' string ';
select _latin1 ' string ';
select _latin1' string ' COLLATE latin1_danish_ci;
对于简单的语句select ' string ',字符串使用由character_set_connection和collation_connection系统变量定义的字符集和校对规则。
_ charset_name表达式正式称做一个引介词。它告诉解析程序,“后面将要出现的字符串使用字符集X。”引介词在标准十六进制字母和数字十六进制符号(x ' literal '和 0x nnnn )中是合法的。
例如:
select _latin1 x'AABBCC';
select _latin1 0xAABBCC;
MySQL这样确定一个文字字符集和校对规则:
例如:
select_latin1'Müller' COLLATE latin1_german1_ci;
select_latin1'Müller';
select 'Müller';
字符集引介词和COLLATE子句是根据标准SQL规范实现的。
COLLATE可以用于多种SQL语 句中:order by、as、group by、聚合函数、distinct、where、having等。COLLATE子句有较高的优先级(高于||)。
BINARY操作符是COLLATE子句的一个速记符。BINARY ' x '等价与' x ' COLLATE y ,这里y是字符 集' x '二元校对规则的名字。每一个字符集有一个二元校对规则。例如,latin1字符集的二元校对规则是latin1_bin,因此,如果列a是字符集latin1,以下两个语句有相同效果:
select * from t1 order by BINARY a;
select * from t1 order by a COLLATE latin1_bin;
7、校对规则的“可压缩性”
在绝大多数查询中,mysql使用哪种校对规则进行比较是很显然的。例如,在下列情况中,校对规则明显的是“列x的列校对规则”:
select x from T order by x;
select x from T where x = x;
select distinct x from T;
但是,当涉及多个操作数时,可能不明确。例如:
select x from T where x = 'Y';
这个查询应该使用列x的校对规则,还是字符串文字'Y'的校对规则?
标准化SQL使用“可压缩性”规则解决这种问题。基本上,这个意思是:既然x和'Y'都有 校对规 则,哪个校对规则优先?这可能比较难解决,但是以下规则适合大多数情况:
上述可压缩性值是mysql当前所用的。
这样上述规则可以模糊解决:
使用COERCIBILITY()函数确定一个字符串表达式的可压缩性:
mysql> SELECT COERCIBILITY('A' COLLATE latin1_swedish_ci);
-> 0
mysql> SELECT COERCIBILITY(VERSION());
-> 3
mysql> SELECT COERCIBILITY('A');
-> 4