天天看点

MySQL字符数据类型char与varchar的区别

数据类型差不多是接触mysql一开始就了解的内容,最近遇到几个现象如varchar自动转mediumtext,blob存储性能的问题,不得不回头明确一下关于mysql常用数据类型的选择。

<code>char</code>类型是使用固定长度空间进行存储,范围0-255。比如<code>char(30)</code>能放30个字节,存放abcd时,尾部会以空格补齐,实际占用空间 30bytes 。检索它的时候尾部空格会被去除。

<code>varchar</code>类型保存可变长度字符串,范围0-65535(但受到单行最大64kb的限制)。比如用<code>varchar(30)</code>去存放abcd,实际使用5个字节,因为还需要使用额外1个字节来标识字串长度(0-255使用1个字节,超过255需要2个字节)。

varchar善于存储值的长短不一的列,也是用的最多的一种类型,节省磁盘空间。update时varchar列时,如果新数据比原数据大,数据库需要重新开辟空间,这一点会有性能略有损耗,但innodb引擎下查询效率比char高一点。这也是innodb官方推荐的类型。

如果存储时真实长度超过了char或者varchar定义的最大长度呢?

在sql严格模式下,无论char还是varchar,如果尾部要被截断的是非空格,会提示错误,即插入失败

在sql非严格模式下,无论char还是varchar,如果尾部要被截断的是非空格,会提示warning,但可以成功

如果尾部要被截断的是空格,无论sql所处模式,varchar都可以插入成功但提示warning;char也可以插入成功,并且无任何提示

这里特意提到sql的严格模式,是因为在工作中也遇到过一些坑,参考[mysql的sql_mode严格模式注意点]()。

贴上官方的一个表格:

value

char(4)

storage required

varchar(4)

''

' '

4 bytes

1 byte

'ab'

'ab '

3 bytes

'abcd'

5 bytes

'abcdefgh'

另外,mysql字段值比较时默认是不区分大小写的,这是由于他们的校对规则(一般是 utf8_general_ci)决定的,按字符比较,所以查询时 值尾部 的空格也是被忽略的,除非建表时对列指定 <code>binary</code> (校对字符集变成utf8_bin)或者<code>select * from vc where binary v='ab ';</code>,就会按字节比较,即比较时区分大小写和尾部空格。

需要注意的是,使用varchar不能因为长度可变就随意分大空间,比如90个字节能放够的列定义成varchar(200),因为开辟内存时是以200字节进行的,遇到需要filesort或tmp table作业可能会带来不利影响。

最后研究一下字符集对存储长度影响,以 <code>create table tc_utf8(c1 int primary key auto_increment, c2 char(30), c3 varchar(n)) charset=utf8;</code> 为例:

字符集为utf8,于是中文每个字符占3个字节,英文还是1个字节,所以n最大为 (65535-1-2-4-303)/3 = 21812,即最多能存放21812个英文、数字、汉字。其中65535是单行最大限制,减1是null标识位,减2的是头部的2个字节标识长度,减303的原因是char(30)占用90个字节,最后除以3还是因为utf8最长用3个字节表示一个字符。

但有人会说,utf8的英文字符只需要1个字节表示,并不占用3个字节,在存ascii字符的情况下n是不是可以更大呢。答案是否定的,因为定义表的时候mysql事先并不知道c3要存的是英文还在中文,只能以最大来计。mysql也是以这种方式来确保行最大65535bytes限制:数据行只要出现一个ascii字符(如英文字母、数字),就永远达不到65535,数据行全中文则刚好满。

还有一种特殊情况:

即在非严格模式下,因为n=21813 &gt; 21812,所以报 row size too large 错误。但n=21846 &gt; (65535/3)时,只是出现warnings,varchar自动变成了mediumtext 类型。

细心的朋友可能注意到上面开始我看了一下字符集 <code>show variabels like "char%";</code>,因为接下来要说明另外一个问题:客户端字符集与database不一样的情况。

我们回到 n&lt;=21812 的正常情况:

插入一些数据:

上面的en_30代表insert的时候存入30个英文字符。可以看到30个a占用30个字节,30个汉字占用90个字节,大于30的会被截断,证实了文章一开头的说法。

上面的实验显示,db table是utf8,但客户端连接时使用latin1,在非严格模式下 varchar(30) 只能存10个汉字,多余的尾部被截断了

我们来看一下占用字节的情况:(2,3行的乱码是意料之中的)

在严格模式下就没这么复杂了,所以尽量使用 strict_trans_tables ,避免意外的情况带入生产环境。早期设计的时候就要保持客户端与数据库字符集一致。