UTF-8 for Metadata

元数据是“数据的数据”。除了数据库的内容,其余描述数据库的任何东西都是元数据。因此列名,数据库名,用户名,版本号,以及SHOW返回的大多数字符串结果都是元数据。对于INFORMATION_SCHEMA里面的表的内容也是如此,因为这些表被定义用来存储数据库对象的信息。

元数据的表示需要满足以下条件:

  • 所有的元数据必须是同一字符集。否则INFORMATION_SCHEMA中的表应用SHOWSELECT语句都不能正常工作,因为结果集中的同一个字段的不同行将处于不同的字符集。
  • 元数据必须包含所有语言的所有字符。否则,用户可能不能用自己的语言命名表或列。

为了满足上面两个条件,MySQL使用Unicode存储元数据,即UTF-8。如果你不使用重音或非拉丁字符,那么不好有问题,但是,如果需要,要注意元数据是UTF-8。

元数据的限制意味着这些函数的返回值默认是UTF-8字符集:USER(), CURRENT_USER(), SESSION_USER(), SYSTEM_USER(), DATABASE(), VERSION()。

服务使用character_set_system系统变量来设置元数据字符集:

mysql> SHOW VARIABLES LIKE 'character_set_system';

+----------------------+-------+
| Variable_name        | Value |
+----------------------+-------+
| character_set_system | utf8  |
+----------------------+-------+
1 row in set (0.00 sec)

使用Unicode存储元数据并不是服务默认使用character_set_system字符集来返回列标题和DESCRIBE函数的结果。当你使用SELECT column1 FROM t时,column1字段名本身从服务端返回到客户端是由character_set_results系统变量决定的,默认是utf-8。如果你希望服务使用不同的字符集返回元数据,使用SET NAMES语句强制服务进行字符集转换。SET NAMES设置character_set_results和其他相关的系统变量。或者,在服务返回结果集后通过客户端程序转换,通常客户端执行转换的效率更高,但是不是所有的客户端都能转换。

如果character_set_results设置为NULL,则不会执行转换,并且服务返回元数据使用原始字符集(即character_set_system的值)。

从服务端返回到客户端的错误信息会跟元数据一样自动转换为客户端字符集。

如果你正在使用(例如)USER()函数在一个语句中来比较或赋值,不用担心,MySQL会自动转换。

SELECT * FROM t1 WHERE USER() = latin1_column;

语句有效是因为latin1_column的内容在比较之前会自动转换为UTF-8。

INSERT INTO t1 (latin1_column) SELECT USER();

这个语句会在插入之前将USER()的内容自动转换成latin1。

虽然自动转换不在SQL标准中,但是标准说了每个字符集是Unicode的子集。因为有一个家喻户晓的准则即:适用于超集的内容也适用于子集。我们相信Unicode的排序规则也能应用于非Unicode字符串的比较。

指定字符集和校对规则

默认的字符集和校对规则有四个级别:服务,数据库,表,列。接下来的描述看起来很复杂,但是在实践中发现多级别默认导致自然而明显的结果。

CHARACTER SET被用来指定字符集。CHARSET和CHARACTER SET是同义词,可以一样用。

最简单的就是:

SET NAMES 'utf8';

排序规则命名约定

MySQL排序规则名字遵循几个约定:

  • 排序规则的名字以跟他关联的字符集的名字开头,通常跟一个或多个后缀表明其特征。例如,utf8_general_ci和latin1_swedish_ci分别是utf8和latin1字符集的排序规则,个别地,二进制字符集有单个排序规则,命名为binary,没有后缀。

  • 特定语言的排序规则包含语言名。例如,utf8_turkish_ci和utf8_hungarian_ci分别使用土耳其和匈牙利的规则给utf8字符集排序。

  • 排序规则后缀表明排序规则是否区分大小写和重音,或二进制。如下表:

后缀 含义 翻译
_ai Accent insensitive 重音不敏感
_as Accent sensitive 重音敏感
_ci Case insensitive 大小写不敏感
_cs case-sensitive 大小写敏感
_bin Binary 二进制

对于未指定重音敏敏感的非二进制排序规则,那么由大小写敏感性决定。如果排序规则名字不包含_ai或者_as,那么_ci代表_ai,_cs代表_as。例如,latin1_general_ci是大小写不敏感的,隐式说明重音不敏感。latin1_general_cs是大小写敏感,隐式说明重音敏感。

对于二进制字符集的排序规则,比较基于数字字节值。对于非二进制字符集的_bin排序规则,比较基于数字字符码,与多字节字符的字节值不同(这块不太清除,手册上有章节详细介绍了,后面加上)。

对于Unicode字符集,排序规则可能包含版本号来说明当前排序规则基于的Unicode排序规则算法(UCA)的版本。基于UCA的排序规则如果没有版本号,就是基于UCA 4.0.0版本。例如:

  • utf8_unicode_520_c1是基于UCA 5.2.0的

  • utf8_unicode_ci是基于UCA 4.0.0的

对于Unicode字符集,xxx_general_mysql500_ci排序规则保留了5.1.24之前的原始的xxx_general_ci排序规则,允许对MySQL5.1.24之前创建的表升级。

服务端字符集和排序规则

MySQL有服务端字符集和排序规则,可以在服务启动的时候设置,或者放在配置文件里,在运行的时候修改。

原来,服务端字符集和排序规则依赖于启动时候的mysqld命令。你可以使用--character-set-server来设置字符集。随之而来的你可以添加--collation-server来设置排序规则。如果不指定字符集,跟--character-set-server=latin1是一样的。如果仅指定字符集(例如,latin1),不指定排序规则,那么跟--character-set-server=latin1 --collation-server=latin1_swedish_ci是一样的,因为latin1_swedish_ci是latin1的默认排序规则。因此下面三个命令是一样的:

mysqld
mysqld --character-set-server=latin1
mysqld --character-set-server=latin1 \
    --collation-server=latin1_swedish_ci

改变他的一种方式是重新编译。为了改变服务端字符集和排序规则通过从源代码构建,给CMake指定DEFAULT_CHASETDEFAULT_COLLOATION选项。例如:

cmake . -DDEFAULT_CHARSET=latin1

或者

cmake . -DDEFAULT_CHARSET=latin1 \
    -DDEFAULT_COLLATION=latin1_german1_ci

msyqld和CMake共同验证字符集和排序规则是否有效,如果错误,程序会报错并终止。

服务端字符集会在CREATE DATABASE语句没有指定字符集和排序规则的时候被使用,没有其他的用途。

当前的服务端字符集和排序规则可以通过character_set_servercollation_server系统变量设置,这些变量可以在运行时改变。

数据库字符集和排序规则

每个数据库都有字符集和排序规则。可以在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]

可以使用关键字SCHEMA替换DATABASE

所有的数据库选项都存放在位于数据库目录的文本文件db.opt中。

这使得可以在同一个MySQL服务下创建不同字符集和排序规则的数据库。

例如:

CREATE DATABASE db_name CHARACTER SET latin1 COLLATE latin1_swedish_ci;

MySQL使用下面的方式选择数据库的字符集和排序规则:

  • 如果字符集和排序规则都显示指定,那么直接应用之。

  • 如果仅指定了字符集,没有指定排序规则,那么字符集和其默认的排序规则被使用。查看默认的排序规则可以使用SHOW CHARACTER SET或查询INFORMATION_SCHEMA CHARACTER SETS表。

  • 如果指定了排序规则,没有指定字符集,那么使用与排序规则关联的字符集和指定的排序规则。

  • 如果都没有指定,使用服务端字符集和排序规则。

默认数据库的字符集和排序规则可以通过两个character_set_databasecollation_database系统变量设置,当默认数据库改变的时候,MySQL服务设置这些变量的值。如果没有默认数据库,则这些变量与对应的服务级变量有相同的值,即character_set_servercollation_server

使用如下语句查看默认的字符集和排序规则:

USE db_name;
SELECT @@character_set_database, @@collation_database;

不改变默认数据库的情况下查看:

SELECT DEFAULT_CHARACTER_SET_NAME, DEFAULT_COLLATION_NAME
FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME = 'db_name';

数据库字符集和排序规则会影响这些操作:

  • 对于CREATE TABLE语句,如果没有指定字符集和排序规则,那么会使用数据库的字符集和排序规则。可以在创建表的时候通过指定字符集和排序规则来覆盖数据库的设置。

  • 对于不包含CHARACTER SETLOAD DATA语句,服务使用由character_set_database变量指定的字符集来解析文件内容。可以通过指定CHARACTER SET语句来覆盖之。

  • 对于函数和存储过程,数据库字符集和排序规则影响函数和存储过程创建过程中声明的没有声明字符集和排序规则的参数。可以通过为参数执行字符集和排序规则来避免这个问题。