哪个更快:char(1)还是tinyint(1)?为什么?

34

我的平台:

PHP和MySQL

我的情况:

我遇到了这样一种情况:需要在表的某一列中存储用户选择的值。现在我的选项是:

  1. 将该列声明为char(1),并将值存储为'y'或'n'
  2. 将该列声明为tinyint(1),并将值存储为1或0
  3. 还可以对此列进行索引以供应用程序使用。

我的问题:

我想知道,上述两个类型中:

  1. 在访问该列时,哪种类型会导致更快的查询速度(为了简单起见,请排除其他查询或访问其他列)?
  2. 哪种方式是最有效的存储和访问数据的方法,并说明其原因?
  3. 如果对列建立索引和不建立索引,访问速度会有何变化?

我的理解是,由于char(1)和tinyint(1)只占据1字节的空间,因此在这种情况下,存储空间不是问题。那么剩下的就是访问速度了。据我所知,数值型索引比其他任何东西都要快和高效。但是,在这种情况下,我认为这是一个很难做出决定的问题,因此我想听听您的经验。

提前感谢您。


5
请提供文件并告知结果。 - erenon
2
一个错误的二分法,还有 enum('1','0')(例如)。 - David Thomas
这个问题与PHP无关,所以我删除了PHP标签。 - streetparade
将一个字段索引为只有两个可能值的情况是毫无意义的。 - recursive
@recursive 列的类型对其适用于索引的影响很小。如果您将该列放入“WHERE”子句中,并且没有索引,它将不得不执行完整的表扫描,而不管其类型如何。 - Schwern
哇!没想到这个问题会在这么短的时间内被浏览和回复这么多次。@erenon:请原谅我的无知,我该怎么做?phpmyadmin有任何工具可以做到这一点吗(我可能还不知道)?@ricebowl很好,但正如Langdon所提到的那样,其中有一个陷阱。请参考他的评论。@streetparade:没关系,但看起来它帮助了其他用户,比如Matchu和Zombat,在他们的评论中指出了一个有效的观点。@recursive我完全同意Schwern的观点。在一个有一百万行的表上,无索引扫描将是过度杀伤力的。因此,使用它是很好的选择。 - Devner
8个回答

44
                       Rate insert tinyint(1) insert char(1) insert enum('y', 'n')
insert tinyint(1)     207/s                --            -1%                  -20%
insert char(1)        210/s                1%             --                  -19%
insert enum('y', 'n') 259/s               25%            23%                    --
                       Rate insert char(1) insert tinyint(1) insert enum('y', 'n')
insert char(1)        221/s             --               -1%                  -13%
insert tinyint(1)     222/s             1%                --                  -13%
insert enum('y', 'n') 254/s            15%               14%                    --
                       Rate insert tinyint(1) insert char(1) insert enum('y', 'n')
insert tinyint(1)     234/s                --            -3%                   -5%
insert char(1)        242/s                3%             --                   -2%
insert enum('y', 'n') 248/s                6%             2%                    --
                       Rate insert enum('y', 'n') insert tinyint(1) insert char(1)
insert enum('y', 'n') 189/s                    --               -6%           -19%
insert tinyint(1)     201/s                    7%                --           -14%
insert char(1)        234/s                   24%               16%             --
                       Rate insert char(1) insert enum('y', 'n') insert tinyint(1)
insert char(1)        204/s             --                   -4%               -8%
insert enum('y', 'n') 213/s             4%                    --               -4%
insert tinyint(1)     222/s             9%                    4%                --

看起来,大部分情况下,enum('y', 'n') 的插入速度更快。

                       Rate select char(1) select tinyint(1) select enum('y', 'n')
select char(1)        188/s             --               -7%                   -8%
select tinyint(1)     203/s             8%                --                   -1%
select enum('y', 'n') 204/s             9%                1%                    --
                       Rate select char(1) select tinyint(1) select enum('y', 'n')
select char(1)        178/s             --              -25%                  -27%
select tinyint(1)     236/s            33%                --                   -3%
select enum('y', 'n') 244/s            37%                3%                    --
                       Rate select char(1) select tinyint(1) select enum('y', 'n')
select char(1)        183/s             --              -16%                  -21%
select tinyint(1)     219/s            20%                --                   -6%
select enum('y', 'n') 233/s            27%                6%                    --
                       Rate select tinyint(1) select char(1) select enum('y', 'n')
select tinyint(1)     217/s                --            -1%                   -4%
select char(1)        221/s                1%             --                   -2%
select enum('y', 'n') 226/s                4%             2%                    --
                       Rate select char(1) select tinyint(1) select enum('y', 'n')
select char(1)        179/s             --              -14%                  -20%
select tinyint(1)     208/s            17%                --                   -7%
select enum('y', 'n') 224/s            25%                7%                    --

选择似乎也是 enum。 代码可以在此处找到


+1 @gms8994非常感谢您提供的统计数据。这让我们更深入地了解了速度问题。请问您是否知道其他工具可以产生与上述相同的结果?再次感谢。 - Devner
@Devner 我不知道有其他的。我专门为了回答这个问题而编写了这个,但你可以在响应中链接的 GitHub 页面上查看它。 - Glen Solsberry
你使用的 MySQL 版本是什么? - Davi Menezes
根据发布时间,@DaviMenezes可能是基于5.1或5.5 - 我不会期望在新版本中有显著的百分比变化,尽管这完全有可能。 - Glen Solsberry
好奇使用枚举类型(enum)中的 'y' 和 null 代替 enum('y', 'n') 能否提高性能 - Joel Karunungan
@JoelKarunungan,代码仍然可以在提供的链接处找到。你应该能够下载它,进行任何必要的修改并运行它。 - Glen Solsberry

37

我认为你应该创建一个具有 ENUM('n','y') 的列。Mysql以最优方式存储此类型。它还将帮助您仅在字段中存储允许的值。

您还可以使其更加人性化,ENUM('no','yes') 而不会影响性能。因为字符串'no''yes'仅在每个ENUM定义中存储一次。 Mysql仅存储每行值的索引。

还要注意按ENUM列排序的问题:

ENUM值根据枚举成员在列规范中列出的顺序进行排序。(换句话说,ENUM值根据其索引号进行排序。)例如,对于ENUM('a','b'),'a'在'b'之前排序,但对于ENUM('b','a'),'b'在'a'之前排序。


10
很久以前,我和提问者一样有同样的问题,我进行了基准测试,发现枚举(enum)是三个选项中最快和最有效的。只需确保不要像我一样使用enum('0','1')--否则您将想知道为什么 UPDATE X SET Y = 0; 不起作用(需要单引号)。 - Langdon
+1 给 Langdon。你提到的那一点非常独特。直到现在我都不知道。这意味着如果我们使用 enum('0', '1'),我们的查询必须是 UPDATE X SET Y = '0';对吗? @Ivan 如果我没错的话,ENUM('n','y') 占用的空间与 ENUM('no','yes') 相同。我对吗? - Devner
@Devner 是的,空间使用是相同的,因为你不能添加除了''、'no'和'yes'之外的任何值。Mysql仅存储每行的值的索引,而不是字符串。'no'和'yes'字符串仅在表定义中存储一次。 - Ivan Nevostruev
1
@Devner:所有枚举值都有数字索引,从1开始(0是一个特殊值,表示空字符串)。您可以使用这些索引来查询和设置值,但正如手册所说:“出于这些原因,不建议定义具有看起来像数字的枚举值的ENUM列,因为这很容易变得混淆。”[http://dev.mysql.com/doc/refman/5.1/en/enum.html](不要将这些数字索引与实际列索引混淆,只是没有更好的词来区分它们) - Jan Fabry
1
枚举类型是魔鬼的产物! - Vérace
为什么不使用NULL和yes。NULL表示没有,它不会占用额外的空间,完全为空。它仍然可以被索引。 - Joel Karunungan

11

使用tinyint更符合标准实践,并且可以更轻松地检查字段的值。

// Using tinyint 0 and 1, you can do this:
if($row['admin']) {
    // user is admin
}

// Using char y and n, you will have to do this:
if($row['admin'] == 'y') {
    // user is admin
}

我不是MySQL内部工作的专家,但直觉上感觉检索和排序整数字段比字符字段更快(我只是有一种感觉,即'a'>'z'比0>1更费力),并且从计算机的角度来看,0和1是标准的开/关标志,更容易理解。因此,整数的存储似乎更好,更舒适,并且在代码逻辑中使用更容易。对我来说,0/1是明显的胜利者。
您还可以注意到,在某种程度上,这也是MySQL的官方立场,从他们的文档中可以看出:
BOOL,BOOLEAN:这些类型是TINYINT(1)的同义词。零被认为是假的。非零值被认为是真的。
如果MySQL把TINYINT(1)等同于BOOLEAN,那么这似乎是正确的做法。

也许这种检查是一件好事?让我解释一下IDE......require_once("./Permissions.php"); ... if( $row['permissions'] === Permissions::ADMIN ) { // user is admin }不仅对于代码的可读性有好处,使用静态属性引用值还可以在编译时检查拼写错误,并且当使用预测性IDE时,它将帮助您快速编码。此示例为您提供了多级权限,但我认为可读性和可维护性是开发大型项目的关键,因此我完全支持这一点。 - Gary Paluk
@Gary 感谢您的评论,但我无法确定您是主张使用0和1还是不使用它。我只是觉得您的编程实践与我的不同,所以请您耐心等待,因为我可能需要更多时间来理解您的意思。 - Devner

4
要确定的话,您应该进行基准测试。或知道在整个项目的更大视图中,它可能并不那么重要。
Char列具有编码和排序规则,对它们进行比较可能涉及不必要的编码切换,因此我猜测使用int会更快。出于同样的原因,我认为更新int列上的索引也更快。但是,仍然不会有太大影响。
CHAR根据所选字符集和表选项可能占用多个字节。有些字符可能需要三个字节来编码,因此MySQL有时会保留该空间,即使您只使用y和n。

但是再说一遍,“这并不重要”。我也是这么想的。区别可能微乎其微。 - Justin Johnson
@Jan 你说的话对我来说很有道理。所以,如果我使用enum('n', 'y'),在编码和比较之间切换是否仍然会出现延迟?当使用INNODB VS MyISAM时会有什么不同? - Devner
@Devner:是的,由于枚举列是通过编码和排序规则来定义的,我认为这可能会对性能产生影响。我不知道InnoDB和MyISAM之间的区别,只是提供了一个描述InnoDB选项的注释,该选项可以影响字符存储[http://dev.mysql.com/doc/refman/5.1/en/data-size.html]。 - Jan Fabry

3

它们都非常接近,所以没有关系。如果你在SO上感到必须问这个问题,那么你正在过度优化。使用最合乎逻辑的那个。


1

如果在创建MySQL表时将类型BOOLBOOLEAN指定为列类型,则会将列类型创建为TINYINT(1)。这可能是两者中更快的一种。

文档

另外:

我们打算在未来的MySQL版本中实现完整的布尔类型处理,以符合标准SQL。


1

虽然我猜测在TINYINT上建立索引比在CHAR(1)上建立索引更快,因为没有字符串处理开销(排序、空格等),但我没有任何事实来支持这一点。我的猜测是,性能差异不大,不值得担心。

然而,因为你正在使用PHP,将其存储为TINYINT更有意义。使用1/0值相当于使用truefalse,即使它们作为字符串返回到PHP,也可以像处理布尔值一样处理。你可以简单地使用if ($record['field'])进行布尔检查,而不是一直在'y'和'n'之间转换。


+1 @Zombat 那很有道理。我认为在应用程序中使用数字会真正简化PHP代码的处理。 - Devner

1
 TINYINT    1 Byte
CHAR(M)     M Bytes, 0 <= M <= 255

有什么不同吗?


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接