在固定大小的字段上使用 CHAR 与 VARCHAR 对性能有何影响?

Jas*_*ker 66 mysql innodb performance varchar

我有一个存储 MD5 哈希的索引列。因此,该列将始终存储 32 个字符的值。无论出于何种原因,它都是作为 varchar 而不是 char 创建的。迁移数据库以将其转换为字符是否值得?这是在带有 InnoDB 的 MySQL 5.0 中。

Rol*_*DBA 63

之前有人问过类似的问题

MySQL VARCHAR 大小的性能影响

这是我的回答的摘录

您必须意识到使用 CHAR 与 VARCHAR 的权衡

使用 CHAR 字段,您分配的内容正是您获得的内容。例如,CHAR(15) 分配和存储 15 个字节,无论您在字段中放置多少字符。字符串操作简单明了,因为数据字段的大小是完全可以预测的。

使用 VARCHAR 字段,您会得到一个完全不同的故事。例如 VARCHAR(15) 实际上动态分配最多 16 个字节,最多 15 个用于数据,至少还有 1 个额外的字节来存储数据的长度。如果要存储字符串 'hello',它将占用 6 个字节,而不是 5 个字节。在所有情况下,字符串操作必须始终执行某种形式的长度检查。

当你做两件事时,权衡更明显:1. 存储数百万或数十亿行 2. 索引 CHAR 或 VARCHAR 列

TRADEOFF #1 显然,VARCHAR 具有优势,因为可变长度数据会产生更小的行,从而产生更小的物理文件。

TRADEOFF #2 由于 CHAR 字段需要较少的字符串操作,因为字段宽度固定,因此针对 CHAR 字段的索引查找平均比 VARCHAR 字段快 20%。这不是我的任何猜测。《MySQL Database Design and Tuning》一书在 MyISAM 表上做了一些奇妙的事情来证明这一点。书中的例子做了如下的事情:

ALTER TABLE tblname ROW_FORMAT=FIXED;
Run Code Online (Sandbox Code Playgroud)

该指令强制所有 VARCHAR 都表现为 CHAR。我在 2007 年的上一份工作中做到了这一点,并使用了一个 300GB 的表并将索引查找速度提高了 20%,而没有更改任何其他内容。它按发布的方式工作。然而,它确实产生了一张几乎两倍大小的表,但这只是回到权衡#1。

您可以分析存储的数据以查看 MySQL 推荐的列定义。只需对任何表运行以下命令:

SELECT * FROM tblname PROCEDURE ANALYSE();
Run Code Online (Sandbox Code Playgroud)

这将遍历整个表并根据它包含的数据、最小字段值、最大字段值等为每一列推荐列定义。有时,您只需要使用常识来规划 CHAR 与 VARCHAR。这是一个很好的例子:

如果要存储 IP 地址,则此类列的掩码最多为 15 个字符 (xxx.xxx.xxx.xxx)。我会立刻跳起来,CHAR(15)因为 IP 地址的长度不会有太大变化,而且字符串操作的复杂性由一个额外的字节控制。你仍然可以PROCEDURE ANALYSE()对这样的专栏做一个。它甚至可能会推荐 VARCHAR。在这种情况下,我的钱仍然会放在 CHAR 上而不是 VARCHAR 上。

CHAR 与 VARCHAR 问题只能通过适当的计划来解决。强大的力量伴随着巨大的责任(陈词滥调,但确实如此)。

更新

对于MD5,strlen在切换整个行格式时,内部的计算应该被取消。无需更改字段定义。

如果 MD5 键是唯一存在的 VARCHAR,我会选择它并将表行格式转换为 fixed。如果存在大量其他 VARCHAR 字段,它们也会受益。作为交换,桌子会扩大到原来的两倍。但是查询速度应该会提高大约 20%,而无需额外调整。


Jac*_*las 20

它看起来像你会节省每值1个字节或约3%的转换为char。如果您无论如何都以十六进制存储MD5,则可能不值得- 您可以通过使用 abinary来节省 50% 。

感谢 Ovais(见评论)指出如果您使用多字节字符集,它char(32)可以使用超过 32 个字节。

感谢 Rick James 指出您应该使用该unhex函数将十六进制字符串转换为二进制:

create table foo(bar varbinary(100));
insert into foo(bar) values(md5('a')); 
insert into foo(bar) values(unhex(md5('a'))); 
Run Code Online (Sandbox Code Playgroud)
select length(bar) from foo;
Run Code Online (Sandbox Code Playgroud)
| 长度(条)|
| ----------: |
| 32 |
| 16 |

db<>在这里摆弄

  • 对于字符集为 utf-8 的 char(32) 列,每个值都需要 32x3 字节来存储。为什么需要将 MD5 哈希值设置为 utf-8。转换为 binary(32) 每个值需要 32 个字节。 (3认同)
  • 除非您还使用了“UNHEX()”,否则更改为“BINARY”的作用很小。也就是说,您可以将 `UNHEX(MD5(x))` 存储到 16 字节的 `BINARY(16)` 中,以节省将 `MD5(x)` 存储到 `CHAR(32) CHARACTER SET ascii` 中的大量空间。 (2认同)

RTh*_*mas 15

在我看来,这不值得改变。如果您查看此处的文档,它应该说明两者之间的区别。在您的使用场景中,除非您真的担心与行大小相关的额外开销,否则一个并没有真正提供任何显着的好处。

http://dev.mysql.com/doc/refman/5.0/en/char.html

还要注意我上面链接到的文档的第一条评论...“如果整个记录是固定大小,CHAR 只会加快您的访问速度。也就是说,如果您使用任何可变大小的对象,您不妨将它们全部可变大小。在还包含 VARCHAR 的表中使用 CHAR 不会提高速度”