VARCHAR 和 CHAR 类型(MySQL)

读《高性能MySQL》第三版 笔记。
官方文档 https://dev.mysql.com/doc/refman/5.7/en/char.html

VARCHAR 和 CHAR 是两种最主要得字符串类型。
不幸的是,很难精确地解释这些值是怎么存储在磁盘和内存中的,因为这跟存储引擎的具体实现有关。下面的描述假设使用的存储引擎是 InnoDB 和 / 或者 MyISAM。如果使用的不是这两种存储引擎,请参考所使用的存储引擎的文档。

先看看 VARCHAR 和 CHAR 值通常在磁盘上怎么存储。请注意,存储引擎存储 CHAR 或者 VARCHAR 值的方式在内存中和在磁盘上可能不一样,所以 MySQL 服务器从存储引擎读出的值可能需要转换为另一种存储格式。下面是关于两种类型的一些比较。


VARCHAR

VARCHAR 类型用于存储可变长字符串,是最常见的字符串数据类型。
它比定长类型更节省空间,因为它仅使用必要的空间(例如,越短的字符串使用越少的空间)。有一种情况例外,如果 MySQL 表使用 ROW_FORMAT=FIXED 创建的话,每一行都会使用定长存储,这会很浪费空间。


VARCHAR 需要使用 1 或 2 个额外字节记录字符串的长度:
如果列的最大长度小于或等于 255 字节,则只使用 1 个字节表示,否则使用 2 个字节。
假设采用 latin1 字符集,一个 VARCHAR(10) 的列需要 11 个字节的存储空间。VARCHAR(1000) 的列则需要 1002 个字节,因为需要 2 个字节存储长度信息。


VARCHAR 节省了存储空间,所以对性能也有帮助。
但是,由于行是变长的,在 UPDATE 时可能使行变得比原来更长,这就导致需要做额外的工作。如果一个行占用的空间增长,并且在页内没有更多的空间可以存储,在这种情况下,不同的存储引擎的处理方式是不一样的。
例如,MyISAM 会将行拆成不同的片段存储,InnoDB 则需要分裂页来使行可以放进页内。其他一些存储引擎也许从不在原数据位置更新数据。


下面这些情况下使用 VARCHAR 是合适的:

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

在 5.0 或者更高版本,MySQL 在存储和检索时会保留末尾空格。但在 4.1 或者老的版本,MySQL 会剔除末尾空格。

InnoDB 引擎会更灵活,它可以把过长的 VARCHAR 存储为 BLOB。


CHAR

CHAR 类型是定长的:MySQL 总是根据定义的字符串长度分配足够的空间。
当存储 CHAR 值时,MySQL 会删除所有的末尾空格(在 MySQL 4.1 和更老版本中 VARCHAR 也是这样实现的——也就是说这些版本中 CHAR 和 VARCHAR 在逻辑上是一样的,区别只是在存储格式上)。CHAR 值会根据需要采用空格进行填充以方便比较。


CHAR 适合存储很短的字符串,或者所有值都接近同一个长度。
例如,CHAR 非常适合存储密码的 MD5 的值,因为这是一个定长的值。

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


栗子

下面通过一个具体的栗子来说明。

首先,我们创建一张只有一个 CHAR(10) 字段的表并且往里面插入一些值:

mysql> CREATE TABLE char_test(char_col CHAR(10));
Query OK, 0 rows affected (0.05 sec)

mysql> INSERT INTO char_test(char_col) VALUES
    -> ('string1'),(' string2'),('string3 ');
Query OK, 3 rows affected (0.01 sec)
Records: 3  Duplicates: 0  Warnings: 0

当检索这些值的时候,会发现 string3 末尾的空格被截断了。

mysql> SELECT CONCAT("'",char_col,"'") FROM char_test;
+--------------------------+
| CONCAT("'",char_col,"'") |
+--------------------------+
| 'string1'                |
| ' string2'               |
| 'string3'                |
+--------------------------+
3 rows in set (0.00 sec)

如果用 VARCHAR(10) 字段存储相同的值,

mysql> CREATE TABLE varchar_test(varchar_col VARCHAR(10));
Query OK, 0 rows affected (0.04 sec)

mysql> INSERT INTO varchar_test(varchar_col) VALUES
    -> ('string1'),(' string2'),('string3 ');
Query OK, 3 rows affected (0.01 sec)
Records: 3  Duplicates: 0  Warnings: 0

可以得到如下结果(string3 尾部的空格还在)。

mysql> SELECT CONCAT("'",varchar_col,"'") FROM varchar_test;
+-----------------------------+
| CONCAT("'",varchar_col,"'") |
+-----------------------------+
| 'string1'                   |
| ' string2'                  |
| 'string3 '                  |
+-----------------------------+
3 rows in set (0.00 sec)

补充

数据如何存储取决于存储引擎,并非所有的存储引擎都会按照相同的方式处理定长和变长的字符串。
Memory 引擎只支持定长的行,即使有变长字段也会更加最大长度分配最大空间。不过,填充和截取空间的行为在存储引擎都是一样的,因为这是在 MySQL 服务器层进行处理的。


与 CHAR 和 VARCHAR 类似的类型还有 BINARY 和 VARBINARY,它们存储的是二进制字符串。
二进制字符串跟常规字符串非常相似,但是二进制字符串存储的是字节码而不是字符。
填充也不一样:MySQL 填空 BINARY 采用的是 \0(零字节)而不是空格,在检索时也不会去掉填充值(如果需要在检索时保持值不变,则需要特别小心 BINARY 类型,MySQL 会用 \0 将其填充到需要的长度)。

当需要存储二进制数据,并且希望 MySQL 使用字节码而不是字符串进行比较时,这些类型是非常有用的。二进制比较的优势并不仅仅体现在大小写敏感上。MySQL 比较 BINARY 字符串时,每次按一个字节,并且根据该字节的数值进行比较。因此,二进制比较字符串比较简单很多,所以也就更快。


慷慨是不明智的
使用 VARCHAR(5) 和 VARCHAR(200) 存储 ’ hello ’ 的空间开销是一样的。那么使用更短的列有什么优势吗?
事实证明有很大的优势。更长的列会消耗更多的内存,因为 MySQL 通常会分配固定大小的内存块来保存内部值。尤其是使用内存临时表进行排序或操作时会特别糟糕。在利用磁盘临时表进行排序时也同样糟糕。
所以最好的策略是只分配真正需要的空间。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值