MySQL 字符串类型有两种变体:一种没有字符集标签,另一种有字符集标签。
一个固定长度的字符串,最后用空格填充,是 CHAR(n)。没有字符集标签的匹配类型是 BINARY(n)。将字符串“hello”存储在CHAR(255) CHARSET utf8 中将占用 765 个字节(用空格填充到全长的字符串,存储为 utf8,最坏的情况是 3 个字节/字符的空间使用分配 3*255 个字节)。
具有一个或两个长度字节且没有填充的可变长度字符串是 VARCHAR((n)。没有字符集标签的匹配类型是 VARBINARY(n)。将字符串“hello”存储在 VARCHAR(255) CHARSET utf8 中将占用 6 个字节(1 个长度字节加上 5 个字节用于实际文本)。以相同类型存储字符串 クリス 将占用 10 个字节(1 个长度字节加上 3 个字符,每个字符用 3 个字节来表示它们)。
mysql> select hex('クリス'), length(hex('クリス'))/2 as bytes;
+--------------------+--------+
| hex('クリス') | bytes |
+--------------------+--------+
| E382AFE383AAE382B9 | 9.0000 |
+--------------------+--------+
1 row in set (0.02 sec)
具有 1、2、3 或 4 个长度字节的可变长度字符串是 TINYTEXT、TEXT、MEDIUMTEXT 和 LARGETEXT。没有字符集标签的匹配类型有 TINYBLOB、BLOB、MEDIUMBLOB 和 LARGEBLOB。
TEXT/BLOB-like 类型与 VARCHAR/VARBINARY-like 类型的不同之处在于数据的存储方式和位置,请参阅http://www.mysqlperformanceblog.com/2010/02/09/blob-storage-in-innodb/ 了解有关 TEXT/BLOB-like 类型如何根据版本和ROW_FORMAT 设置。出于性能原因,您需要最新版本的 InnoDB 和“梭子鱼”格式表。
MySQL 无法处理任何大小超过 max_allowed_packet(默认值:1M)的数据,除非您在服务器端构建复杂且内存密集的解决方法。这进一步限制了 TEXT/BLOB 类类型可以做什么,并且通常使 LARGETEXT/LARGEBLOB 类型在默认配置中无用。
对于没有字符集标签的类型(BINARY、VARBINARY 和 %BLOB%),MySQL 将接受接收到的数据并将其写入磁盘。对于具有字符集标签的类型,MySQL 将查看您向服务器宣布的客户端字符集SET NAMES,以及定义的字符集标签是什么列。然后它将从连接字符集转换为列字符集并写入转换后的数据。您可以使用 HEX() 函数进行检查,例如SELECT HEX(str) FROM t WHERE id = ....
在检索时,使用SET NAMES 声明的连接字符集可能与写入时不同。 MySQL 将再次根据为此连接宣布的字符集检查列字符集标签,如有必要,将转换为连接字符集。
无论如何,与此类数据发生的磁盘 I/O 所花费的时间相比,这种转换的性能损失可以忽略不计,就性能而言,您选择哪种类型几乎无关紧要。相反,规则是:如果您正在处理文本数据,请选择带有字符集标签的类型,如果您不是,请选择不带字符集标签的类型。
一个经常被问到的相关问题:我应该选择 CHAR 还是 VARCHAR(分别是 BINARY 或 VARBINARY)?
对于 InnoDB,答案始终是:选择可变长度数据类型。 InnoDB 中的固定长度数据类型没有性能优势,但是如果您选择固定长度数据类型然后没有使用其中的所有空间,则会有巨大的大小损失。另外,固定长度的 SQL 字符串类型在结尾处使用空格进行填充和修剪的规则非常奇怪,您可能不会费心去学习这些规则。对于 MySQL,情况可能会有所不同,但几乎不会。
另一个相关问题:我应该为我的字符串选择 VARCHAR 还是 TEXT(分别为 VARBINARY 或 BLOB)?
答案是使用最新版本的 InnoDB、梭子鱼格式表,然后是 TEXT/BLOB。其原因在http://www.mysqlperformanceblog.com/2011/04/07/innodb-row-size-limitation/ 中有详细解释。这样做的结果是:使用前 Barracuda 格式的 VARCHAR 或 TEXT/BLOB,如果单行中有太多 InnoDB 行大小限制,则存在溢出 InnoDB 行大小限制的风险。
最后:我应该在数据库中存储文件/图像/其他大型 blob 或文本数据吗?
答案是:通常不会。与从文件系统提供文件相比,从数据库 (http://mysqldump.azundris.com/archives/36-Serving-Images-From-A-Database.html) 提供文件是一项昂贵的操作。如果可能的话,你会想要这样做。有一种方法可以解决这个问题,http://www.blobstreaming.org/,但这是一种先进的技术,需要您完全控制自己的执行环境,而在托管环境中永远不会出现这种情况。
四舍五入:MEMORY 引擎表中没有可变长度数据类型。因此,如果您在 EXPLAIN 输出中看到“使用临时”,这意味着
- VARCHAR 在该临时表中转换为 CHAR
- VARBINARY 转换为 BINARY
如果此进程的临时表变得大于 tmp_table_size 或 max_heap_table_size,它会即时转换为 MyISAM 格式并进入磁盘。
示例:您正在定义一个 Ruby Active Record 类 User,其中包含标记为 :string 的十个字段。这些中的每一个最终都是VARCHAR(255) CHARSET utf8 在您的Users 表中。
在您的代码库的其他地方,Users 的使用方式涉及using temporary 计划。您在负载下的磁盘操作中会立即死亡,因为Users 表的每一行现在至少在 MEMORY 中使用 7650 个字节,其中大部分是用作填充的空格。这会强制将临时表转换为 MyISAM 并写入磁盘。
- 任何 %TEXT% 或 %BLOB% 类型都不能在 MEMORY 中表示,因此临时表会作为 MyISAM 进入磁盘,即使根据上述限制它本来足够小以保存在内存中。
这意味着任何具有 TEXT 或 BLOB 类型的查询以及具有“使用临时”的计划都需要重写,以避免临时表撞到磁盘。