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

评论

警告此问题及其答案是在InnoDB和utf8为默认值之前编写的。

#1 楼



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数据库设计和调优》这本书在MyISAM表上做了出色的工作来证明这一点。书中的示例执行了以下操作:

ALTER TABLE tblname ROW_FORMAT=FIXED;


此伪指令强制所有VARCHAR充当CHAR。我在2007年的上一份工作中做到了这一点,并使用了300GB的表,并将索引查找速度提高了20%,而没有进行任何其他更改。它像出版一样工作。但是,它的确产生了几乎两倍大的表,但这只是权衡了第一点。

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

SELECT * FROM tblname PROCEDURE ANALYSE();


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

如果要存储IP地址,则该列的掩码最多为15个字符(xxx.xxx.xxx.xxx)。我会心跳地跳到CHAR(15),因为IP地址的长度变化不会太大,而且字符串操作的复杂性由一个额外的字节控制。您仍然可以对这样的列执行PROCEDURE ANALYSE()。它甚至可能建议使用VARCHAR。在这种情况下,我的钱仍将放在CHAR上,而不是VARCHAR上。

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

UPDATE

当涉及到MD5时,在切换整个行格式时应在内部消除strlen的计算。无需更改字段定义。

如果MD5键是唯一存在的VARCHAR,我将使用它并将表行格式转换为固定格式。如果存在大量其他VARCHAR字段,它们也将受益。作为交换,桌子将扩大到其大小的两倍左右。但是,如果不进行其他调整,查询的速度应提高20%左右。

评论


我想我会使用char(4)或类似无符号整数的IP地址

–杰克·道格拉斯(Jack Douglas)
2011年5月10日21:28

@JackPDouglas在这一点上你是正确的。

– RolandoMySQLDBA
2011年5月10日21:36

索引不是以固定长度存储吗?我不知道如何将存储格式更改为固定长度的改进的索引查找。您是说它改善了表扫描吗?

–马库斯·亚当斯(Marcus Adams)
2012年4月15日在22:24

@JackDouglas,为什么不位和二进制?

–起搏器
2014年12月10日,下午3:46

@Pacerier会更好,我同意:)

–杰克·道格拉斯(Jack Douglas)
2014年12月10日下午5:32

#2 楼

看起来您可以通过转换为char来为每个值节省1个字节或大约3%。如果仍然以十六进制存储MD5,则可能不值得-而是使用binary可以节省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'))); 






select length(bar) from foo;


| length(bar) |
| ----------: |
|          32 |
|          16 |



db <>在这里拨弄

评论


改用二进制文件的好方法。

–RThomas
2011年5月10日在21:17

我打算将其转换为二进制文件。现在,我考虑了一下,因为我们的编码是utf-8,所以大小不应仅基于我使用的是字节还是字符而有所不同。还是我错了?

–杰森·贝克(Jason Baker)
2011年5月17日17:36

@Jason-编码不适用于二进制-还是我误解了?

–杰克·道格拉斯(Jack Douglas)
2011年5月17日在18:53

对于字符集为utf-8的char(32)列,每个值都需要32x3字节进行存储。为什么需要将MD5哈希值设置为utf-8。转换为binary(32)将需要每个值32个字节。

–ovais.tariq
11年5月25日在12:05

除非您也使用UNHEX(),否则更改为BINARY几乎没有作用。也就是说,您可以将UNHEX(MD5(x))存储到16字节的BINARY(16)中,以节省将MD5(x)存储到CHAR(32)CHARACTER SET ascii中的空间。

–里克·詹姆斯(Rick James)
18年8月16日在5:11

#3 楼

我认为这不值得改变。如果您浏览此处的文档,则应说明两者之间的区别。在您的使用场景中,除非您真的担心与行大小相关的额外开销,否则一个并不会真正提供任何明显的好处。
/doc/refman/5.0/zh-CN/char.html

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

评论


该“加速”适用于MyISAM,而不适用于InnoDB。

–里克·詹姆斯(Rick James)
16年7月2日在5:17