Answers:
这是一个非常常见的“考试/面试题”。我将尽我所能回答:
在InnoDB和MyISAM的标准行格式(动态/紧凑)中,a VARCHAR(50)
和a VARCHAR(255)
将以相同的方式存储字符串文本-长度为1个字节,实际字符串为每个字符1至4个字节(取决于编码和存储的实际字符)。
实际上,如果我没记错的话,我记得有人用十六进制编辑器修改了数据字典,以便将a VARCHAR(50)
变为a VARCHAR(100)
,因此可以动态完成(通常需要重建表)。这是可能的,因为实际数据不受该更改的影响。
对于而言,这是不正确的VARCHAR(256)
,因为这样一来,长度总是至少需要2个字节。
因此,这意味着我们应该始终这样做VARCHAR(255)
,不是吗?不,有几个原因。
尽管InnoDB可能以动态方式存储varchar,但其他引擎却并非如此。MyISAM具有固定的行大小格式,并且MEMORY表的大小始终是固定的。我们应该关心其他引擎吗?是的,我们应该这样做,因为即使不直接使用它们,MEMORY表也很常用于中间结果(内存中的临时表),并且由于事先不知道结果,因此必须以最大大小创建表可能- VARCHAR(255)
如果那是我们的类型。如果您可以考虑浪费的空间,那么如果我们使用MySQL的'utf8' charset
编码,则MEMORY将为长度保留2个字节+每行3 * 255个字节(对于在InnoDB上可能只占用几个字节的值)。在100万张表上,这几乎是1GB-仅用于VARCHAR。这不仅会导致不必要的内存压力,还可能激起要在磁盘上执行的操作,从而有可能使速度降低数千倍。所有这些都是由于对其定义的数据类型(与内容无关)的选择不佳。
它也对InnoDB产生一些后果。索引大小限制为3072字节,单列索引限制为767字节*。因此,很可能无法完全索引一个VARCHAR(255)
字段(假设您使用utf8或任何其他可变长度编码)。
此外,InnoDB的最大内联行大小为半页(大约8000个字节),并且如果长度不可变的字段(例如BLOB或varchar)不适合放在半页上,则可以在页外存储。这会对性能产生一些影响(有时视情况而定,有时是好的,有时是不好的),这是不容忽视的。这在COMPACT和DYNAMIC格式之间造成了一些怪异。例如,请参见:错误1118:行大小太大。utf8 innodb
最后但并非最不重要的一点是,正如@ypercube提醒我的那样,即使使用VARCHAR(255)
,也可能需要超过1个字节的长度,因为定义是字符形式的,而长度则存储字节。例如REPEAT('ñ', 255)
,utf8中的字节数超过2 ^ 255,因此存储其长度将需要1个字节以上:
mysql> SELECT LENGTH(REPEAT('ñ', 255));
+---------------------------+
| LENGTH(REPEAT('ñ', 255)) |
+---------------------------+
| 510 |
+---------------------------+
1 row in set (0.02 sec)
mysql> SELECT CHAR_LENGTH(REPEAT('ñ', 255));
+--------------------------------+
| CHAR_LENGTH(REPEAT('ñ', 255)) |
+--------------------------------+
| 255 |
+--------------------------------+
1 row in set (0.00 sec)
因此,一般建议是使用尽可能小的类型,因为否则可能会导致性能或管理问题。即使您不知道确切的长度,A VARCHAR(100)
还是比VARCHAR(255)
(尽管a VARCHAR(20)
会更好)更好。尽量保持保守,因为除非表太大,否则以后总是可以更改定义。
更新:由于可变长度字符串的爆炸式增长,例如随着表情符号的使用,Oracle一直在努力提高这些情况的性能。在最新的MySQL版本(5.6、5.7)中,InnoDB已被设置为固有表和显式临时表的默认引擎,这意味着可变长度字段现在是一等公民。这意味着可能没有太多理由限制字符长度(但是仍然存在)。
(*)第二次更新:默认情况下,最新的MySQL版本(8.0)默认启用large_prefix_index,但对于较早版本或使用lagacy innodb文件/行格式(动态或压缩格式除外)的情况仍然如此。默认情况下,单列索引最多可以包含3072个字节。