MySQL的char与varchar的区别

本文采用的存储引擎是InnoDB和/或者MyISAM

一、版本支持

varchar在不同的版本中的存储长度规则

版本存储范围(字节)例子:varchar(20)
5.0.3 以下版本和字节无关指的是20字节,如果存放汉字时,只能存6个
5.0.3 及以上版本65533(或65532)指的是20字符,无论存放的是数字、字母还是汉字,都可以存放20个

在MySQL5.0.3及以上的版本中,varchar数据类型的长度支持到了65535,也就是说可以存放65532个字节的数据,起始位和结束位占去了3个字节,也就是说,在5.0.3以下版本中需要使用固定的TEXT或BLOB格式存放的数据可以在高版本中使用可变长的varchar来存放,这样就能有效的减少数据库文件的大小。

二、不同字符集下的存储限制

varchar在不同字符集情况下的存储限制字符(按照实际存储大于256字节计算,字段 默认为NULL):

字符集字符占用字节数存储长度限制/字符【varchar(M)】
latin1165532
gbk1或265532或32766
utf-8165532
232766
321844
416383

GBK 占用字节的规则:

  • 一个utf8数字占1个字节
  • 一个utf8英文字母占1个字节
  • 一个中文占2个字节

UTF-8 占用字节的规则:

  • 占2个字节的:带有附加符号的拉丁文、希腊文、西里尔字母、亚美尼亚语、希伯来文、阿拉伯文、叙利亚文及它拿字母则需要二个字节编码
  • 占3个字节的:基本等同于GBK,含21000多个汉字
  • 占4个字节的:中日韩超大字符集里面的汉字,有5万多个
  • 一个utf8数字占1个字节
  • 一个utf8英文字母占1个字节
  • 少数是汉字每个占用3个字节,多数占用4个字节。

三、SQL_MODE存储模式

mysql支持的sql_mode模式:ANSI、TRADITIONAL、STRICT_ALL_TABLES和STRICT_TRANS_TABLES。

  • ANSI模式:宽松模式,对插入数据进行校验,如果不符合定义类型或长度,对数据类型调整或截断保存,报warning警告。

  • TRADITIONAL模式:严格模式,当向mysql数据库插入数据时,进行数据的严格校验,保证错误数据不能插入,报error错误。用于事物时,会进行事物的回滚。

  • STRICT_TRANS_TABLES模式:严格模式,进行数据的严格校验,错误数据不能插入,报error错误。只对支持事务的表有效。

  • STRICT_ALL_TABLES模式:严格模式,进行数据的严格校验,错误数据不能插入,报error错误。对所有表都有效。

四、CHAR和VARCHAR的详解

4.1 char和varchar的区别

  • CHAR是定长字符串类型,在声明时指定其长度m,当存储CHAR值时,MySQL会删除所有的末尾空格。如果写入的字符串未达到指定长度m,那么会在右侧使用空格补齐到长度m,当存储CHAR值时,MySQL会删除所有的末尾空格所以,声明为m长度的CHAR类型,不论其存放的字符串长短,统一都会占用m字节。

  • VARCHAR是变长字符串类型,在声明时虽然指定其长度m,仅指其能存放字符串的最长长度,在存储时,如果存放的字符串长度不超过255,VARCHAR会额外申请一个字节用于存放实际字符串的长度,如果超过255,会额外申请两个字节的空间用于存放实际字符串的长度,所以根据VARCHAR实际存放的字符长度不同,其所占空间也会不同。有一种情况例外,如果MySQL表使用ROW_FORMAT=FIXED创建的话,每一行都会使用定长存储,这会很浪费空间。

VARCHAR需要使用1或2个额外字节记录字符串的长度:如果列的最大长度小于或等于255字节,则只使用1个字节表示,否则使用2个字节。假设采用latin1字符集,一个VARCHAR(10)的列需要11个字节的存储空间。VARCHAR(1000)的列则需要1002个字节,因为需要2个字节存储长度信息。
VARCHAR节省了存储空间,所以对性能也有帮助。但是,由于行是变长的,在UPDATE时可能使行变得比原来更长,这就导致需要做额外的工作。如果一个行占用的空间增长,并且在页内没有更多的空间可以存储,在这种情况下,不同的存储引擎的处理方式是不一样的。例如,MyISAM会将行拆成不同的片段存储,InnoDB则需要分裂页来使行可以放进页内。其他一些存储引擎也许从不在原数据位置更新数据。

下面的表显示了将各种字符串值保存到CHAR(4)和VARCHAR(4)列后的结果,说明了CHAR和VARCHAR之间的差别(存储模式SQL_MODE=ANSI):

CHAR(4)存储需求VARCHAR(4)存储需求
‘’’ ’4个字节‘’1个字节
‘ab’'ab ’4个字节'ab ’3个字节
‘abcd’‘abcd’4个字节‘abcd’5个字节
‘abcdefgh’‘abcd’4个字节‘abcd’5个字节

从CHAR(4)和VARCHAR(4)列检索的值并不总是相同,因为检索时从CHAR列删除了尾部的空格。
通过以下执行结果进行说明:

mysql> CREATE TABLE test (col1 VARCHAR(4), col2 CHAR(4));
Query OK, 0 rows affected (0.02 sec)

mysql> INSERT INTO test VALUES ('123 ', '123 ');
Query OK, 1 row affected (0.00 sec)

mysql> SELECT CONCAT(col1, '='), CONCAT(col2, '=') FROM test;
+----------------+----------------+
| CONCAT(col1, '=') | CONCAT(col2, '=') |
+----------------+----------------+
| 123 = | 123= |
+----------------+----------------+
1 row in set (0.00 sec)

可以从上面的执行结果看到,使用concat对输出结果进行了调整,可以看到CHAR在存储时,自动裁剪了右侧的空格串。

CHAR、VARCHAR(以及TEXT)类型在进行比较时,会自动裁剪后侧的空格串,然后进行比较,对于此规则,如果在该类型的列上有唯一索引,那么插入裁剪右侧空格串后相同的字符串会报错。如下图所示:

请注意所有MySQL校对规则属于PADSPACE类。这说明在MySQL中的所有CHAR和VARCHAR值比较时不需要考虑任何尾部空格。

mysql> SELECT col1 = '123 ', col2 = '123 ' FROM names;
+-----------------------------+-------------------------------+
| col1 = '123 ' | col2 = '123 ' |
+-----------------------------+-------------------------------+
| 1 | 1 |
+-----------------------------+-------------------------------+
1 row in set (0.00 sec)

如果需要将右侧的空格字符串参与比较,使用like即可,如下执行结果所示:

mysql> CREATE TABLE test (col1 VARCHAR(4), col2 CHAR(4));
Query OK, 0 rows affected (0.02 sec)

mysql> INSERT INTO test VALUES ('123 ', '123');
Query OK, 1 row affected (0.00 sec)
mysql> SELECT col1 LIKE '123 ', col2 LIKE = '123 ' FROM names;
+-----------------------------+-------------------------------+
| col1 = '123 ' | col2 = '123 ' |
+-----------------------------+-------------------------------+
| 1 | 0 |
+-----------------------------+-------------------------------+
1 row in set (0.00 sec)

4.2、varchar存储限制的计算方法

4.2.1 存储受限因素

varchar存储受限于以下几个因素:

4.2.1.1、存储限制
  • varchar最多能存储65535个字节的数据。varchar 的最大长度受限于最大行长度(max row size,65535bytes)。65535并不是一个很精确的上限,可以继续缩小这个上限。65535个字节包括所有字段的长度,变长字段的长度标识(每个变长字段额外使用1或者2个字节记录实际数据长度)、NULL标识位的累计。

  • NULL标识位,如果varchar字段定义中带有default null允许列空,则需要需要1bit来标识,每8个bits的标识组成一个字段。一张表中存在N个varchar字段,那么需要(N+7)/8 (取整)bytes存储所有的NULL标识位。

如果数据表只有一个varchar字段且该字段DEFAULT NULL,那么该varchar字段的最大长度为65532个字节,即65535-2-1=65532 byte。
测试案例如下:

mysql> create table t1 ( name varchar(65532) default null)charset=latin1;
Query OK, 0 rows affected (0.09 sec)

mysql> 
mysql> create table t2 ( name varchar(65533) default null)charset=latin1;  
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
mysql> 

可以看见当设置长度为65533时,已经超过行最大长度,我们可以计算一下,行最大长度是65535字节。上面t2表name字段使用 varchar(65533),字符集是latin1,占用1个字节。还有默认为空,那么还有null标识位,( 1 + 7 ) / 8 =1,所以null标识位占用1个字节。
现在我们来看看,65533 + 1 + 2=65536字节,已经大于行最大长度。这里2字节怎么来的???因为varchar类型存储变长字段的字符类型,与char类型不同的是,其存储时需要在前缀长度列表加上实际存储的字符,当存储的字符串长度小于255字节时,其需要1字节的空间,当大于255字节时,需要2字节的空间。

如果数据表只有一个varchar字段且该字段NOT NULL,那么该varchar字段的最大长度为65533个字节,即65535-2=65533byte

mysql> create table t2 ( name varchar(65533) not null) charset=latin1;   
Query OK, 0 rows affected (0.03 sec)

mysql> 
mysql> create table t3 ( name varchar(65534) not null) charset=latin1;  
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
mysql> 
4.2.1.2、编码长度限制

字符类型若为gbk,每个字符最多占2个字节,最大长度不能超过32766;

字符类型若为utf8,每个字符最多占3个字节,最大长度不能超过21845。

若定义的时候超过上述限制,则varchar字段会被强行转为text类型,并产生warning。

4.2.1.3、行长度限制

导致实际应用中varchar长度限制的是一个行定义的长度。 MySQL要求一个行的定义长度不能超过65535。若定义的表长度超过这个值,则提示

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。
4.2.1.4、存储受限计算举例

举两个例说明一下实际长度的计算。

a) 若一个表只有一个varchar类型,如定义为

create table t4(c varchar(N)) charset=gbk;

则此处N的最大值为(65535-1-2)/2= 32766。

  • 减1的原因是实际行存储从第二个字节开始;

  • 减2的原因是varchar头部的2个字节表示长度;

除2的原因是字符编码是gbk。

b) 若一个表定义为

create table t4(c int, c2 char(30), c3 varchar(N)) charset=utf8;

则此处N的最大值为 (65535-1-2-4-30*3)/3=21812

  • 减1和减2与上例相同;
  • 减4的原因是int类型的c占4个字节;
  • 减30*3的原因是char(30)占用90个字节,编码是utf8。

如果被varchar超过上述的b规则,被强转成text类型,则每个字段占用定义长度为11字节,当然这已经不是varchar了。

则此处N的最大值为 (65535-1-2-4-30*3)/3=21812,例子如下:

mysql> create table t4(c int, c2 char(30), c3 varchar(21812)) charset=utf8; 
Query OK, 0 rows affected (0.05 sec)

mysql> 
mysql> create table t5(c int, c2 char(30), c3 varchar(21813)) charset=utf8;  
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
mysql> 

最后让我们来看一个例子

CREATE TABLE t6 (
id int,
a VARCHAR(100) DEFAULT NULL,
b VARCHAR(100) DEFAULT NULL,
c VARCHAR(100) DEFAULT NULL,
d VARCHAR(100) DEFAULT NULL,
e VARCHAR(100) DEFAULT NULL,
f VARCHAR(100) DEFAULT NULL,
g VARCHAR(100) DEFAULT NULL,
h VARCHAR(100) DEFAULT NULL,
i VARCHAR(N) DEFAULT NULL
) CHARSET=utf8; 

那么上面这条语句中的varchar(N)的最大值是多少呢?

让我们来计算一下,每个NULL字段用1bit标识,10个字段都是default null,那么需要用(10+7)/8bit = 2 bytes存储NULL标识位。int占用4个 byte。

(65535 - 1 - 28 -4 - 1003*8 -2)/3=21037

mysql> CREATE TABLE t6 ( id int, a VARCHAR(100) DEFAULT NULL, b VARCHAR(100) DEFAULT NULL, c VARCHAR(100) DEFAULT NULL, d VARCHAR(100) DEFAULT NULL, e VARCHAR(100) DEFAULT NULL, f VARCHAR(100) DEFAULT NULL, g VARCHAR(100) DEFAULT NULL, h VARCHAR(100) DEFAULT NULL, i VARCHAR(21037) DEFAULT NULL ) CHARSET=utf8;
Query OK, 0 rows affected (0.01 sec)

mysql> 
mysql> CREATE TABLE t7 ( id int, a VARCHAR(100) DEFAULT NULL, b VARCHAR(100) DEFAULT NULL, c VARCHAR(100) DEFAULT NULL, d VARCHAR(100) DEFAULT NULL, e VARCHAR(100) DEFAULT NULL, f VARCHAR(100) DEFAULT NULL, g VARCHAR(100) DEFAULT NULL, h VARCHAR(100) DEFAULT NULL, i VARCHAR(21038) DEFAULT NULL ) CHARSET=utf8;  
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
mysql> 

可以看见多一个字符都报错了。

varchar到底能存多少个字符?这与使用的字符集相关,latin1、gbk、utf8编码存放一个字符分别需要占1、2、3个字节。

五、使用建议

varchar适用的场景:

  • 字符串列的最大长度比平均长度大很多
  • 列的更新很少,所以碎片不是问题
  • 使用了像UTF-8这样复杂的字符集,每个字符都使用不同的字节数进行存储

char的适用场景:

  • 适合存储很短的字符串,或者所有值都接近同一个长度。
  • 对于经常变更的数据,CHAR也比VARCHAR更好。定长的CHAR类型不容易产生碎片。
  • CHAR比VARCHAR在存储空间上也更有效率。例如用CHAR(1)来存储只有Y和N的值,如果采用单字节字符集只需要一个字节,但是VARCHAR(1)却需要两个字节,因为还有一个记录长度的额外字节。

参考:
https://www.cnblogs.com/duanxz/p/3770181.html
https://blog.csdn.net/weixin_42570248/article/details/89786882
https://www.imooc.com/article/41502
https://blog.csdn.net/bluetjs/article/details/52936943
https://baijiahao.baidu.com/s?id=1656246979336777711&wfr=spider&for=pc

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值