前言
讲字符串类型之前,必须要讲明白的事情是:字符集编码(character set)与字符串排序规则(Collation)
之后再来讲讲CHAR等字符串,为什么需要BINARY类型,以及相对少见的ENUM和SET
字符规则
为了方便表述,这里将「字符集编码」和「字符串排序规则」统称为「字符规则」
1、字符集编码(character set)
character-set-server = utf8mb4
推荐把 MySQL 的默认字符集设置为 UTF8MB4,因为某些 emoji 表情字符无法在 UTF8 字符集下存储,比如 emoji 笑脸表情,在字符集为 UTF8 的列上插入 emoji 表情字符, MySQL 会抛出如下错误信息:
ERROR 1366 (HY000): Incorrect string value: 'xF0x9Fx98x8E' for column 'a' at row 1
UTF8MB4 字符集 1 个字符最大存储 4 个字节。
所以从底层存储内核看,在多字节字符集下,CHAR 和 VARCHAR 底层的实现完全相同,都是变长存储。
CHAR(1) 既可以存储 1 个 ‘a’ 字节,也可以存储 4 个字节的 emoji 笑脸表情。
查看字符集:SHOW
mysql> SHOW CHARSET LIKE 'utf8%';
-- SHOW CHARACTER SET LIKE 'utf8%'; 可以简写为 CHARSET
+---------+---------------+--------------------+--------+
| Charset | Description | Default collation | Maxlen |
+---------+---------------+--------------------+--------+
| utf8 | UTF-8 Unicode | utf8_general_ci | 3 |
| utf8mb4 | UTF-8 Unicode | utf8mb4_0900_ai_ci | 4 |
+---------+---------------+--------------------+--------+
其中Default collation
代表默认的排序比较规则,Maxlen
是一个字符对应的最大字节数
查看某列的字符集
show create table [表名]
修改字符集:ALTER
1、只修改新增列
ALTER TABLE emoji_test CHARSET utf8mb4;
修改字符集的CHARSET属性,新增列默认为UTF8MB4
,但对于已经存在的列,其默认字符集并不做修改,因为这个属性不属于之前的某个字段,而属于TABLE,在「字符规则的四个维度 」就会认识到表CHARSET的意义。
这么做和创建表时指定的CHARSET = UTF8MB4无异
ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
2、修改所有列,包括已有列
ALTER TABLE emoji_test CONVERT TO CHARSET utf8mb4;
使用 CONVERT TO
,已经存在的列的字符集也会被修改。
3、修改某一列的字符集
使用 MODIFY
ALTER TABLE t MODIFY col1 CHAR(50) CHARACTER SET utf8mb4;
注意如果字段本身指定了NUT NULL,DEFAULT属性,ALTER时需要再次指定。
不同字符集的拼接
MySQL在可能的情况下提供了字符集的隐式转化,使得存在子集关系的字符集能够正常工作:
CREATE TABLE t1 (
c1 CHAR(1) CHARACTER SET latin1,
c2 CHAR(1) CHARACTER SET ascii
);
INSERT INTO t1 VALUES ('a','b');
SELECT CONCAT(c1,c2) FROM t1;
此时能够正常返回,因为ascii是latin1的子集。
2、字符串排序规则(Collation)
每个字符集都会有默认的排序规则
查看Collation
- 使用
SHOW CHARSET LIKE 'xxx%'
查看字符集和默认的排序规则(Collation) - 使用
SHOW COLLATION LIKE 'xxx%'
字符集所有支持的排序规则
mysql> SHOW COLLATION LIKE 'utf8mb4%';
-- SHOW COLLATION WHERE Charset = 'utf8mb4';
+------------------------------+-----+---------+----------+---------+---------+-----+
| Collation | Charset | Id | Default | Compiled | Sortlen | Pad_attribute |
+------------------------------+-----+---------+----------+---------+---------+-----+
| utf8mb4_0900_ai_ci | utf8mb4 | 255 | Yes | Yes | 0 | NO PAD |
| utf8mb4_0900_as_ci | utf8mb4 | 305 | | Yes | 0 | NO PAD |
| utf8mb4_0900_as_cs | utf8mb4 | 278 | | Yes | 0 | NO PAD |
| utf8mb4_0900_bin | utf8mb4 | 309 | | Yes | 1 | NO PAD |
| utf8mb4_bin | utf8mb4 | 46 | | Yes | 1 | PAD SPACE |
-- 非常多 就不一一列了
关于Collation含义的解释
Suffix | Meaning |
---|---|
_ai |
Accent-insensitive 重音不敏感 |
_as |
Accent-sensitive 重音敏感 |
_ci |
Case-insensitive 大小写不敏感 |
_cs |
Case-sensitive 大小写敏感 |
_ks |
Kana-sensitive |
_bin |
Binary 二进制,请注意区别_bin 与BINARY类型 |
Pad_attribute:尾部空格的处理
Pad_attribute决定了在排序时,是否要将末尾的空格参与字符串的排序和比较
NO PAD
:将尾部空格视为比较中的重要字符,就像其他字符一样。PAD SPACE
在比较中将拖尾空格视为不重要字符;在比较字符串时不考虑拖尾空格。
字符规则的五个作用范围
前四个在MySQL内部:
1、服务器
一个MySQL Server 有自己的字符集(character set)和排序规则(collation)
8.0版本的默认值为:utf8mb4
and utf8mb4_0900_ai_ci
可以通过如下命令查看:
show variables like 'character_set_server';
show variables like 'collation_server';
意义:创建数据库时,如果没有指定数据库的字符规则,会用服务器的作为默认值。
一般不需要修改默认值,但也是可以修改的:
cmake . -DDEFAULT_CHARSET=latin1
-DDEFAULT_COLLATION=latin1_german1_ci
2、数据库
每个数据库都有自己的字符集(character set)和排序规则(collation)
意义:创建表时,如果没有指定表的字符规则,会用数据库的作为默认值。
CREATE DATABASE db_name CHARACTER SET latin1 COLLATE latin1_swedish_ci;
查看数据库字符集
SHOW VARIABLES LIKE 'character_set_database';
SHOW VARIABLES LIKE 'collation_database';
character_set_database
: 默认数据库的字符集。在创建数据库时设置,新创建的表将继承该字符集。
修改
ALTER DATABASE [database_name] CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
3、表
每个表都有自己的字符集(character set)和排序规则(collation)
意义:当表中的字符串列,没有显式指定character set和collation,就会用表的作为默认值。
CREATE TABLE t1 ( ... )
CHARACTER SET latin1 COLLATE latin1_danish_ci;
查看
SHOW CREATE TABLE [表名] -- 也可以看列
修改
ALTER TABLE [表名] CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
4、列
真正落实到某个具体的字符串。一般包括CHAR,VARCHAR,TEXT。
-- 创建时指定
CREATE TABLE t1
(
col1 VARCHAR(5) CHARACTER SET latin1 COLLATE latin1_german1_ci
);
-- 修改时指定
ALTER TABLE t1 MODIFY
col1 VARCHAR(5) CHARACTER SET latin1 COLLATE latin1_swedish_ci;
5、连接
连接有三个参数,在8.0版本一般也不需要修改:
# 服务器解码请求时使用的字符集
SHOW VARIABLES LIKE 'character_set_client';
# 处理请求时会把字符串从character_set_client转为character_set_connection
SHOW VARIABLES LIKE 'character_set_connection';
# 返回数据时使用的字符集
SHOW VARIABLES LIKE 'character_set_results';
# 在8.0版本中默认值均为utf8mb4,一般这三个值也是要设置成一样的
网上找来的图,侵删
修改
SET NAMES 字符集名;
字符/字符串类型
1、CHAR:定长字符串
CHAR(N) 用来保存固定长度的字符,N 的范围是 0 ~ 255,请牢记,N 表示的是字符,而不是字节。
超出CHAR长度的处理
即当我们对char(4)插入了一个超过4字节的字符串,MySQL会如何处理呢?这取决于sql_mode
。
- 如果
sql_mode
为 strict 严格模式,会直接报错,插入失败 - 否则,会截断多余的字符并插入,如下:
Value | CHAR(4) |
Storage Required |
---|---|---|
'abcdefgh' |
'abcd' |
4 bytes |
上述例子不考虑NULL,字符集假定为ASCII,即一个字符对应一个字节
CHAR(0)的意义
CHAR(0)在有些情况是有用的,CHAR(0)可以用来表达是否为NULL,只能取「NULL」和「空字符串」。
由于NULL的底层实现,使用CHAR(0)可能不会带来额外的空间开销。
大于等于768字节的CHAR
在InnoDB中,如果CHAR的「最大字节数」大于等于768字节,CHAR即可以被off-page存储。
什么是off-page?即在聚簇索引下,叶节点不再存储真实数据,而只存储一个20字节的指针,指向溢出页。
至于到底会不会off-page?当行记录过长时,会选择最长的列进行页面外存储(off-page),直到符合要求。
InnoDB encodes fixed-length fields greater than or equal to 768 bytes in length as variable-length fields, which can be stored off-page. For example, a CHAR(255) column can exceed 768 bytes if the maximum byte length of the character set is greater than 3, as it is with utf8mb4.
InnoDB 会将长度大于或等于 768 字节的固定长度字段编码为可变长度字段,这些字段可以在页面外存储。例如,如果字符集的最大字节长度大于 3,CHAR(255) 列的长度就会超过 768 字节,utf8mb4 就是如此。
CHAR与空格
存储 CHAR 值时,会用空格将其填充到指定的长度。
查询 CHAR 值时,除非启用 PAD_CHAR_TO_FULL_LENGTH SQL
模式,否则会删除尾部空格。
这与VARCHAR不同,VARCHAR在存储和查询时,会保留尾部空格,除非超过列的最大长度。
下面用官网的例子来理解一下:
-- SQL:
CREATE TABLE vc (v VARCHAR(4), c CHAR(4));
INSERT INTO vc VALUES ('ab ', 'ab ');
-- RESULT:
SELECT CONCAT('(', v, ')'), CONCAT('(', c, ')') FROM vc;
+---------------------+---------------------+
| CONCAT('(', v, ')') | CONCAT('(', c, ')') |
+---------------------+---------------------+
| (ab ) | (ab) |
+---------------------+---------------------+
但请注意,在InnoDB的Compact家族行格式的优化下,这个结论不是一定对的。
仅在Redundant行格式下,该结论基本正确,稍后在「变长字符集对CHAR的影响」中我们就会看到。
变长字符集对CHAR的影响
CHAR在满足一定条件时,底层存储和VARCHAR完全一致,推荐直接阅读我的另一篇文章:你知道MySQL的CHAR也会变长存储吗
如utf8mb4,就是一个变长字符集。在InnoDB的Compact行格式家族中,对变长字符集的CHAR有特殊优化:
- 如果CHAR(N)的实际字节数小于等于N,不会在尾部追加空格直到达到字符集的最大字符数*N(以utf8mb4为例,就是4 * N),而是追加到N,就停止。
- 如果CHAR(N)的实际字节数大于N,不会在尾部 追加空格
同时,由于优化后CHAR的字节长度不固定了,也需要在行格式的前面占用字节来表达CHAR的字节长度。
为什么仍然至少为N
即便在compact紧凑行格式下,CHAR都至少占N个字节,既然都已经是变长了,为啥还至少要N个字节呢?官方给出的解释是这样的:
Reserving the minimum space N in many cases enables column updates to be done in place without causing index page fragmentation.
即在大部分情况下,预留N个字节能够避免「索引页分裂」问题。这个留到索引章节再说。
小结
通常使用InnoDB引擎,及Dynamic行格式,因此在底层CHAR就是变长的,在字节长度小于N时,CHAR占用的实际空间还会大于VARCHAR。
2、VARCHAR:变长字符串
VARCHAR(N) 用来保存变长字符,N 的范围为 0 ~ 65535, N 表示字符。
超过65535的VARCHAR
在未开启strict sql mode
情况下:
如果VARCHAR长度超过65535,MySQL会隐式的将该列的数据类型转化为TEXT。
类似的,VARBINARY长度超过65535,会被隐式转化为BLOB。
加强版的CHAR
GBK、UTF8MB4都属于变长字符集,CHAR(1) 既可以存储 1 个 ‘a’ 字节,也可以存储 4 个字节的 emoji 笑脸表情
其本质是一样的,都是变长,设计时完全可以用 VARCHAR 替代 CHAR;
3、TEXT:极长字符串
小于或等于 40 字节的 TEXT 和 BLOB 列以行存储。
TEXT有四类如下,它们分别对应了1,2,3,4个字节的前缀,来表达TEXT的字节长度。
类型 | 范围(单位字符) |
---|---|
TINYTEXT | 0到2^8-1(255B) |
TEXT | 0到2^16-1(64KB) |
MEDIUMTEXT | 0到2^24-1(16MB) |
LONGTEXT | 0到2^32-1 (4GB) |
在大多数情况下,TEXT完全可以视作一个无限大小的VARCHAR。但也有一点点区别,如下:
以下区别对BLOB同样适用
1、必备的前缀索引
TEXT必须指定索引前缀长度
2、不允许有默认值
DEFAULT选型对TEXT不适用。
3、排序最大长度
MySQL排序时最多使用TEXT列的 max_sort_length
字节,max_sort_length
的默认值是 1024,可以被修改
SET max_sort_length = 2000;
4、对临时表的影响
MEMORY并不支持TEXT,因此在查询产生临时表,并且需要将TEXT值存储在临时表时,MySQL不得不将临时表存储在磁盘中,这会导致性能的严重下降。
5、底层存储方式
每个 BLOB 或 TEXT 值在内部都由一个单独分配的对象表示
二进制类型
下面三个属于二进制类型的字符串,他们的长度单位为字节(区分CHAR的字符)。
有了_bin排序,为啥还需要二进制
我们知道,MySQL是支持_bin
的Collation的,为啥还要搞一系列的BINARY出来?
1、排序规则不同
BINARY,基于字节的数字值进行比较;
CHAR的 _bin,一个字符对应多个字节。CHAR的 _bin针对的还是字符,只是用字符对应的字节数值来比较。
2、无法隐式转化
BINARY在MySQL不存在任何隐式转化,作为对比,CHAR以不同字符集插入字段时,也许是可以成功的。
3、无法理解大小写
BINARY无法使用LOWER/UPPER等大小写转化函数。
mysql> SET NAMES binary;
mysql> SELECT LOWER('aA'), LOWER(CONVERT('aA' USING utf8mb4));
+-------------+------------------------------------+
| LOWER('aA') | LOWER(CONVERT('aA' USING utf8mb4)) |
+-------------+------------------------------------+
| aA | aa |
+-------------+------------------------------------+
4、重视所有字节
插入数据,BINARY字节数不足时,填充的是0x00,且在查询时不会删除尾部字节,并且比较时所有字节参与比较
mysql> SET NAMES binary;
mysql> SELECT 'a ' = 'a';
+------------+
| 'a ' = 'a' |
+------------+
| 0 |
+------------+
mysql> CREATE TABLE t1 (
a CHAR(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin,
b BINARY(10)
);
mysql> INSERT INTO t1 VALUES ('x','x');
mysql> INSERT INTO t1 VALUES ('x ','x ');
mysql> SELECT a, b, HEX(a), HEX(b) FROM t1;
+------+------------------------+--------+----------------------+
| a | b | HEX(a) | HEX(b) |
+------+------------------------+--------+----------------------+
| x | 0x78000000000000000000 | 78 | 78000000000000000000 |
| x | 0x78200000000000000000 | 78 | 78200000000000000000 |
+------+------------------------+--------+----------------------+
详细请参考官网:The binary Collation Compared to _bin Collations
BINARY与VARBINARY
BINARY与VARBINARY,分别对应了CHAR和VARCHAR,它们非常像。
只不过,BINARY基于字节存储,collation排序比较也是基于字节的数值。
另外,BINARY不足时,填充的是0x00,且在查询时不会删除尾部字节。
0x00和空格是不同的,0x00 排序在空格之前。
BLOB
BLOB有四种:TINYBLOB
, BLOB
, MEDIUMBLOB
, LONGBLOB
,范围和四种TEXT一样。
BLOB可以用来存储图片,声音和视频等二进制文件。
BLOB具有的特性和TEXT几乎完全一样。
ENUM:枚举
ENUM('value1','value2')
ENUM在行记录中会被编码为数字,从而节省大量磁盘空间,当然查询时MySQL会自动转化为对应的字符串。
对于空字符串和NULL,枚举同样支持。
最多支持65535种不同的枚举值。
SET:枚举集合
SET('value1','value2')
SET是一个字符串的集合,但是它的字符串被限定在若干枚举值中,最多支持64种不同的枚举值。
字符串常用函数
字符串拼接:concat
concat(str1,str2)
大小写转换:lower/upper
upper(str)
子串:substring
substring(str,index,N) # 字符串str 从index开始 长度为N
反转:reverse
reverse(str)
重复:repeat
repeat(str,count) # 重复count次
最后介绍一个在性能测试时可能需要用到的函数:
返回一个长度为N的随机字符串
delimiter $$
# 入参N 代表随机字符串的长度
create function rand_string(n int) returns varchar(255)
begin
#声明你希望出现在字符串中的所有字符
declare chars_str varchar(100) default 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
# 返回值
declare return_str varchar(255) default '';
#声明变量i默认0
declare i int default 0;
# N次,每次随机选择一个字符插入
while i < n do
set return_str = concat(return_str,substring(chars_str,floor(1+rand()*52),1));
set i=i+1;
end while;
return return_str;
end $$
怎么对字符串加索引
下面给出几种方案:
1、创建完整索引
如果字符串长度较小,可以考虑直接创建完整索引
缺点是:占用空间大,所以字符串长度较小的情况这就不是问题
优点是:如果能够索引覆盖从而避免回表,性能还是不错的
2、创建前缀索引
传统的前缀索引,如果前缀的区分度足够高,还是相当好的。对性能没有太大负担,并且节约空间。
3、后缀索引
可以通过reverse函数转化为后缀
缺点是不支持范围查询,只支持等值查询
4、HASH索引
通过crc32在HASH值上建索引
缺点是不支持范围查询,只支持等值查询
参考文档
《MySQL实战45讲》
《MySQL实战宝典》