字符串类型
MySQL 中的字符串类型比较多也比较的复杂,各个字符串类型的差别不仅仅在存储时候的空间占用,对存取时候字段某位的strip 和padding 还有差异。对于类型CHAR/VARCHAR/TEXT 是跟本地字符集相关的,这会影响到实际占用空间的字节数、字符比较等。
类型
长度
CHAR(M)
Mxw bytes
BINARY(M)
M bytes
VARCHAR(M), VARBINARY(M)
L+1/L+2 bytes
TINYBLOB, TINYTEXT
L+1 bytes
LOB, TEXT
L+2 bytes
MEDIUMBLOB, MEDIUMTEXT
L+3 bytes
LONGBLOB, LONGTEXT
L+4 bytes
根据官方手册,CHAR/BINARY 及其衍生的类型的数据是存储在表的行内部(inline) 的,而对于BLOB 和TEXT 类型,每一个字段只占用该行9-12(1~4+8) 个字节( 用于数据的地址和长度) ,实际的数据是存储在Row Buffer 之外位置的。所以对于经常访问的字符串类型,而长度又不是特别的大,还是建议用 VARCHAR(M)
的数据类型,性能会比TEXT 快不少。
CHAR(M)/VARCHAR(M)
长度限制参数M 表示的是本地字符集的字符个数而不是bytes 数目,比如对于UTF8 编码,每个本地字符其实际占用的byte 长度可能是3 或4 倍的本地字符长度。比如VARCHAR(255) ,如果每个本地字符占用两个字节,那么其需要的存储空间最大为255x2+2 。CHAR 的M 被限制在最大255 ,而VARCHAR 的M 理论上受限于Row Size 的长度(65,535bytes) ,且实际存储时候会附加1~2 字节的前缀表示数据实际长度。如果strict SQL 模式没有被打开,那么当插入数据超过声明长度限制的时候,数据将会被截断并产生警告信息,在strict SQL 模式下将会出错。
CHAR 类型在存储的时候,会在右端padding SPACE 到指定的M 长度,当取该字段的时候,所有末尾的SPACE 都将会被strip 掉然后返回;VARCHAR 不会对进行SPACE 进行padding 以及strip 操作,存储什么样的数据就会返回什么样的数据。对于CHAR/VARCHAR/TEXT 类型,在进行字符串比较的时候,(SQL 语句参数中的字符串) 结尾的空格都是不参与比较的,但是对于LIKE 语句,检索的时候结尾的空格是考虑在内的。
BINARY(M)/VARBINARY(M)
BINARY/VARBINARY 在操作的时候,参考的是byte streaming 而不是charaset streaming ,所以其长度限制参数M 表示的是byte 数目,在比较的时候也是直接的数字大小比较( 而非本地字符集方式比较) 。
BINARY 在插入的时候,会使用0x00( 而非SPACE)padding 到长度M ,取值的时候不会进行strip 尾部空字符的操作( 意味着取出来的长度一定是M) ;VARBINARY 则是保证原样存取的。
BLOB/TEXT
分别有TINY/MEDIUM/LONG 类型的衍生长度,BLOB 是bytes streaming 类型的,而TEXT 是基于character streaming 本地字符集类型的,两者在存取的时候都不会进行padding 和strip 操作。BLOB/TEXT 的关系和之前的VARBINARY/VARCHAR 是比较相似的,除了:BLOB/TEXT 不能够有DEFAULT 值;BLOB/TEXT 在创建索引的时候必须要有prefix length ,而前者是可选的;给予TEXT 索引需要有前缀长度,而且建立索引会自动padding SPACE 到索引长度,所以如果插入的字符前面一样,只是尾部空字符长度不同,也是会产生相同的索引值。
字符编码
先说一下utf8 的标准,早期是用1 ~ 6 个byte 来表示一个字符。所以最早的MySQL 实现,一个Char 是用6 个Bytes 去实现的。这是正确的做法。但是MySQL 为了性能,希望用户使用等长度的字符列。也就是说,一个字符如果用不到6 个byte ,存储里就会被填充空白符号。学过计算机的人都会明白等长字符,用数组的索引值去找到数据会非常快。
MySQL 计划在4.1 版本支持utf8 。4.1 的早期开发版本用最多6 个byte 表示一个utf8 字符,这是对的。但是MySQL 不知道脑子里抽了哪根筋,在2002 年9 月27 日,for no particular reason,搞出这么一个commit ,强制让utf8 编码只能处理最多3 个byte 的序列。
在Unicode 中,3 个Byte 可以支持所有的BMP (basic multi-lingual plane)的字符;但是无法支持SMP (supplementary multi-lingual plane) ,包括emoji (这是重灾区) ,一些生僻的CJK 字符,一部分生僻的符号等。对于主要的文字(英文、欧洲各种语种、中文、日文……) ,3 个byte 的utf8 也算是够用。但是,多年之后,也许是苹果强力推emoji ,大家才发现MySQL 的utf8 其实并不那么utf8 。直到2010 年,MySQL 的5.5.3 版本的时候,才引入了utf8mb4 (从此刻开始,utf8 是“utf8mb3“的alias ) 。
JSON
MySQL 为我们提供了原生的JSON 类型的支持,其使用方式类似于普通的字符串类型:
mysql> CREATE TABLE user( id INT PRIMARY KEY, name VARCHAR( 20) , lastlogininfo JSON) ;
Query OK, 0 rows affected ( 0.27 sec)
mysql> INSERT INTO user VALUES( 1 ,"lucy" ,'{"time":"2015-01-01 13:00:00","ip":"
192.168.1.1","result":"fail"}' ) ;
Query OK, 1 row affected ( 0.05 sec)
mysql> INSERT INTO user VALUES( 2 ,"bobo" ,'{"time":"2015-10-07 06:44:00","ip":"
192.168.1.0","result":"success"}' ) ;
Query OK, 1 row affected ( 0.04 sec)
也可以使用JSON_OBJECT() 函数:
mysql> INSERT INTO user VALUES( 1 ,"lucy" ,JSON_OBJECT( "time" ,NOW() ,"ip" ,"
192.168.1.1" ,"result" ,"fail" )) ;
Query OK, 1 row affected ( 0.00 sec)
操作符
JSON 结构允许我们传入操作符,如果传入的不是一个有效的键,则返回Empty set 。该表达式可以用于SELECT 查询列表,WHERE/HAVING, ORDER/GROUP BY 中,但它不能用于设置值。
mysql> SELECT * FROM user WHERE lastlogininfo ->'$.time' > '2015-10-02' ;
mysql> SELECT * FROM user WHERE JSON_EXTRACT( lastlogininfo,'$.time' ) > '2015-10-02' ;
比较JSON 值采用两个级别。第一级是基于JSON 类型的比较。如果类型不同,则取决于哪种类型具有更高的优先级。如果是相同的JSON 类型,则是第二级,使用该类型的规则来比较。下面的列表显示了JSON 类型的比较规则, 从最高优先级到最低优先级。显示在一行的类型则是具有相同的优先级:
BLOB
BIT
OPAQUE
DATETIME
TIME
DATE
BOOLEAN
ARRAY
OBJECT
STRING
INTEGER, DOUBLE
NULL
使用JSON_TYPE() 函数返回指定属性对应的类型名称:
$ mysql> SELECT JSON_TYPE( lastlogininfo->'$.ip' ) FROM user;
虚拟列
值得一提的是,可以通过虚拟列对JSON 类型的指定属性进行快速查询。创建虚拟列:
mysql> ALTER TABLE user ADD lastloginresult VARCHAR( 15)
-> GENERATED ALWAYS AS ( lastlogininfo->'$.result' ) VIRTUAL;
Query OK, 0 rows affected ( 0.08 sec)
Records: 0 Duplicates: 0 Warnings: 0
使用时和普通类型的列查询是一样的:
mysql> SELECT lastloginresult FROM user WHERE name = 'lucy' ;