本节将介绍最直观的二进制比较方式,包括binary Charset的binary Collation与非二进制Charset的_bin Collation两类,同时它们两者也有一些不同。
BINARY、VARBINARY和BLOB数据类型使用binary Charset以及binary Collation,binary字符串是字节序列,这些字节的数值决定了排序顺序。
CHAR、VARCHAR和TEXT数据类型大多数情况下使用了非二进制的Charset,对于大多数非二进制Charset,其上支持一个_bin结尾的二进制比较Collation。例如,latin1和big5的二进制Collation分别命名为latin1_bin和big5_bin。utf8mb4是一个例外,它有两个二进制Collation,utf8mb4_bin和utf8mb4_0900_bin。
基本比较单位
binary Charset的基本比较单位是字节,非二进制Charset的基本比较单位是字符,字节和字符的差别在于字符有可能是多个字节组成的。当binary Charset的binary Collation进行比较时,会逐字节比较其数值;当非二进制Charset的_bin Collation进行比较时,会逐字符比较其数值,对于Unicode Charset而言有如下规律:
除utf8mb4_0900_bin外的_bin Collation会将字符转为Unicode码点进行比较,可能会加上前导0
utf8mb4_0900_bin会使用字符原本的二进制进行比较,因为utf-8编码顺序和Unicode码点顺序上一致,所以可以获得相同结果,同时速度会更快
下面以binary、utf8mb4_bin、utf8mb4_0900_bin三种Collation举例演示:
-- 使用以下两个字符为例
mysql> select _utf8mb4 X'64';
+----------------+
| _utf8mb4 X'64' |
+----------------+
| d |
+----------------+
mysql> select _utf8mb4 X'e18080';
+--------------------+
| _utf8mb4 X'e18080' |
+--------------------+
| က |
+--------------------+
-- 1. binary
mysql> select WEIGHT_STRING(_binary X'64');
+------------------------------------------------------------+
| WEIGHT_STRING(_binary X'64') |
+------------------------------------------------------------+
| 0x64 |
+------------------------------------------------------------+
mysql> select WEIGHT_STRING(_binary X'e18080');
+--------------------------------------------------------------------+
| WEIGHT_STRING(_binary X'e18080') |
+--------------------------------------------------------------------+
| 0xE18080 |
+--------------------------------------------------------------------+
-- 2. utf8mb4_bin
mysql> select WEIGHT_STRING(_utf8mb4 X'64' collate utf8mb4_bin);
+------------------------------------------------------------------------------------------------------+
| WEIGHT_STRING(_utf8mb4 X'64' collate utf8mb4_bin) |
+------------------------------------------------------------------------------------------------------+
| 0x000064 |
+------------------------------------------------------------------------------------------------------+
mysql> select WEIGHT_STRING(_utf8mb4 X'e18080' collate utf8mb4_bin);
+--------------------------------------------------------------------------------------------------------------+
| WEIGHT_STRING(_utf8mb4 X'e18080' collate utf8mb4_bin) |
+--------------------------------------------------------------------------------------------------------------+
| 0x001000 |
+--------------------------------------------------------------------------------------------------------------+
-- 3. utf8mb4_0900_bin
mysql> select WEIGHT_STRING(_utf8mb4 X'64' collate utf8mb4_0900_bin);
+----------------------------------------------------------------------------------------------------------------+
| WEIGHT_STRING(_utf8mb4 X'64' collate utf8mb4_0900_bin) |
+----------------------------------------------------------------------------------------------------------------+
| 0x64 |
+----------------------------------------------------------------------------------------------------------------+
mysql> select WEIGHT_STRING(_utf8mb4 X'e18080' collate utf8mb4_0900_bin);
+------------------------------------------------------------------------------------------------------------------------+
| WEIGHT_STRING(_utf8mb4 X'e18080' collate utf8mb4_0900_bin) |
+------------------------------------------------------------------------------------------------------------------------+
| 0xE18080 |
+------------------------------------------------------------------------------------------------------------------------+可以发现:
binary Charset没有字符的概念,在它看来所有字符串都是字节序列,它看到的字节序列就是binary Collation对该字符串比较、排序时使用的权重key
utf8mb4 Charset就有字符的概念,但是utf8mb4_bin和utf8mb4_0900_bin两种Collation的表现有些不同
utf8mb4_bin会将所有字符转回Unicode码点并且补齐到3 byte(因为Unicode字符可以完全使用3 byte表示),例子中一字节字符d转为了0x000064,三字节字符က转为了0x001000,因此可以清晰看出_bin Collation是以字符为基本单位进行比较的
utf8mb4_0900_bin则不能明显体现出以字符为基本单位,它的表现和binary Collation类似,直接使用utf8mb4原始编码进行比较,由于本身编码设计就已经满足多字节字符二进制首字节数值更大,所以这里不进行补齐byte直接比较也是可以达到相同目标的
大小写转换
二进制字符串没有字符大小写的概念,而非二进制字符串哪怕使用了_bin Collation也可以使用大小写转换函数,如下例所示:
mysql> SET NAMES utf8mb4 COLLATE utf8mb4_bin;
mysql> SELECT LOWER('aA'), UPPER('zZ');
+-------------+-------------+
| LOWER('aA') | UPPER('zZ') |
+-------------+-------------+
| aa | ZZ |
+-------------+-------------+
mysql> SET NAMES binary;
mysql> SELECT LOWER('aA'), LOWER(CONVERT('aA' USING utf8mb4));
+-------------+------------------------------------+
| LOWER('aA') | LOWER(CONVERT('aA' USING utf8mb4)) |
+-------------+------------------------------------+
| aA | aa |
+-------------+------------------------------------+CHAR、VARCHAR、BINARY、VARBINARY
这四种数据类型和Charset、Collation的相关性很高,因此这里介绍一些关键点:
CHAR(N)、VARCHAR(N)的N代表的是字符数,CHAR(N)预留的是“N*字符集最长字符字节数”个字节;BINARY(N)、VARBINARY(N)的N代表的是字节数,BINARY(N)预留的是N个字节。举个例子说明,假设一个列类型为CHAR(5),使用的utf8mb4字符集,由于最长字符的字节数为4,所以每个CHAR(5)都会预留5*4=20字节;但是BINARY(5)就只会预留5字节。
列定义时CHAR BINARY和VARCHAR BINARY并不意味着和BINARY类型有什么关系,而是意味着使用字符集的_bin Collation。例如当默认Charset是utf8mb4时,CHAR(5) BINARY等于是CHAR(5) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin的缩写。
CHAR和BINARY在插入数据时,如果数据不够长,会进行padding,CHAR类型padding的是0x20(空格符),BINARY类型padding的时0x00。在读取CHAR类型列的时候,MySQL会自动将所有padding的0x20摘掉,因此在MySQL看来,向CHAR列插入"a"和"a "后读取出来都是"a",丢失了原本的空格。在读取BINARY列的时候会将完整的N个字节返回(包括padding的0x00),如下例所示:
mysql> CREATE TABLE t (c BINARY(3));
mysql> INSERT INTO t SET c = 'a';
-- 字符串'a'后被补足了两个0x00,在判断时不认为该列数据等于'a',而是等于'a\0\0'
mysql> SELECT HEX(c), c = 'a', c = 'a\0\0' from t;
+--------+---------+-------------+
| HEX(c) | c = 'a' | c = 'a\0\0' |
+--------+---------+-------------+
| 610000 | 0 | 1 |
+--------+---------+-------------+VARCHAR和VARBINARY就没有上面所述padding的步骤,因此VARCHAR存入"a "后读取出来仍然是"a ",下面是一个例子:
mysql> CREATE TABLE vc (v VARCHAR(4), c CHAR(4));
mysql> INSERT INTO vc VALUES ('ab ', 'ab ');
-- VARCHAR列数据的尾部空格符能够返回,CHAR列数据的尾部空格符无法返回
mysql> SELECT CONCAT('(', v, ')'), CONCAT('(', c, ')') FROM vc;
+---------------------+---------------------+
| CONCAT('(', v, ')') | CONCAT('(', c, ')') |
+---------------------+---------------------+
| (ab ) | (ab) |
+---------------------+---------------------+