在 MySQL 中,char 和 varchar 可能是我们最常使用字符串类型。那么到底 varchar 和 varchar 有什么不同?我们什么时候使用 char,什么时候使用 varchar 呢?

区别

char

char 是定长的,插入数据不足规定长度的,右边补空格,当然查询出来的数据也会有空格,插入数据超过规定长度,会返回错误[22001][1406] Data truncation: Data too long for column 'name' at row 1MySQL并不会自动截短字符串。因为 char 是定长的,所以查询的效率比 varchar 高(后面会将为什么效率高),但在列容量不能充分利用的情况下会造成一定的空间浪费。

varchar

varchar 是不定长的,varchar 类型的列是不定长的,在 5.0 版本以后的最大长度是 65536 字节(2^16), 但是这个长度只是 “系统长度”,这并不意味着你真的可以完全利用 65536 字节来存储数据,因为 varchar 是不定长的,所以需要前两个字节标记字段的实际长度,结尾还要用一个字节表示结束。

需要注意的是 65535 只是字节个数,而且是理论字节个数,在减去头尾的 “系统” 占用字节后,只剩下 65533 可用字节。那么我们建表的时候,能不能直接写 varchar(65533) 呢?当然是不可以的,因为 4.0 之后,varchar 后面的小括号里就不再是字节长度了,而是字符长度。 字节和字符个数之间的换算关系是根据编码决定的:

编码长度
utf865533/3=21844(汉字占 3 个字符)
utf8mb465533/4=16383(汉字占 4 个字符,包含了生僻汉字和文字表情)

我们只列出了常用的编码格式。

那么这是否意味着,在 utf8mb4 编码下我们可以用 varchar(16383) 来定义一个列呢?

答案是要看情况,MySQL 规定了一个row所有的字段加起来总长度不能超过 65535 字节,所以如果一个表只有一个列,那完全可以用 varchar(16383) 来定义这个列,如果这个表还有其他列,无论其他列多么短,都是会占用字节数的,所以,使用 varchar(16383) 来定义的时候,MySQL会返回错误提示:ERROR 1118 (42000): Row size too large. The maximum row size for the used table type, not counting BLOBs, is 65535. You have to change some columns to TEXT or BLOBs, 意思是 row 的容量太大,超出了 row 的最大容量 65535,如果不改变列的长度的话,推荐使用TEXT or BLOBs类型。

所以,如果我们要创建一个只包含两个字段的表(编码是 utf8mb4),一列是主键,一列是字符串,字符串的最大长度是多少呢?你可以先自己算一下,再往下看。

长度
idint(11)
articlevarchar((65535-4)/4=16382)

为什么 65535 要减去 4 呢?因为 int(11) 占 4 个字节,那么在 utf8 编码情况下,还是同样的数据结构,article 的最大长度有事多少呢?

长度
idint(11)
articlevarchar((65535-4)/3=21843)

相信这次你一定算对了。

为什么 char 类型查询效率高

这是由他们在磁盘上存放的不同形式决定的,我们先来看一个图:

char 和 varchar 类型数据存储示意图. jpg

我们可以看到 char 类型在存放数据的时候,中间是没有间隔的,数据本身是有空格的,但是数据段之间没有间隔,因为我们在创建列的时候已经告诉MySQL列的长度了,MySQL在查询数据的时候,只需要按部就班寻找就行了,不需要在中途计算这个数据段的长度

但是 varchar 类型的存放就不同了,在每个数据段开头,都要有一段空间(1~2 个字节)存放数据段的长度,在数据段的结尾还有一段空间(1 个字节)标记此字段的节数。MySQL在读取一个数据段的时候,首先要读开头,比如读到了 3,说明数据段的长度是 3,之后就不多不少,只读 3 个字节。所以MySQL在遍历数据的时候,磁针要比 char 类型的列多读很多次磁盘来获取字段的真实长度,这就是为什么 varchar 比 char 查询效率低的原因了。

应用

我们可以用 varchar 存放不定长的数据,比如人的名字,或者一篇博客的文章。可以用 char 存放定长的数据,比如身份证号和手机号,我们把一个列定义为mobile varchar(11),中国大陆的手机号最长,达到 11 位,香港是 8 位,瑞士是 10 位,所以定义成 11 位完全够用,可以存放各国的手机号了。

附加

除了 char 和 varchar 类型,最常用的就是数值类型了,为了方便建表的时候计算列的最大长度,把数值类型占用的字节和值的范围放在这里:

MySQL 数值类型的取值范围和占用字节数. png