INT 和 VARCHAR 主键之间是否存在真正的性能差异?
-
22-07-2019 - |
题
使用 INT 与使用 INT 之间是否存在可测量的性能差异?VARCHAR 作为 MySQL 中的主键?我想使用 VARCHAR 作为参考列表的主键(想想美国各州、国家/地区代码),并且同事不会在 INT AUTO_INCRMENT 作为所有表的主键上让步。
我的论点,详细 这里, 的一点是,INT 和 VARCHAR 之间的性能差异可以忽略不计,因为每个 INT 外键引用都需要 JOIN 来理解引用,而 VARCHAR 键将直接呈现信息。
那么,是否有人对这个特定用例以及与之相关的性能问题有经验?
解决方案
您说的很对,你可以使用什么叫做的避免加入了查询的一些数自然键 代替 代理键 一>。只有你可以评估,如果这样做的好处是在应用程序中显著。
这是,你可以测量你的应用程序,它是最重要的是快速的查询,因为他们有大量数据的工作,或者他们执行得非常频繁。如果这些查询从消除加盟,并且不使用VARCHAR主键遭受获益,然后再去做。
不要使用两种策略数据库中的所有表。这可能是因为在某些情况下,一个自然的关键是更好的,但是在其他情况下,代理键更好。
其他人说的很对,它是罕见的在实践中自然键永远不会改变或有重复,所以代理键通常是值得的。
其他提示
这不是性能。这是关于如何成为一名优秀的主键。独特的和不变的一段时间。你可能会认为一个实体,如国家代码永远不会随时间变化而将是一个主键一个很好的候选人。但痛定思痛的是,很少如此。
INT AUTO_INCREMENT满足“的独特的和不变的一段时间”的条件。因此,偏好。
取决于长度。如果将varchar将是20个字符,int是4,那么如果您使用int,你的指数将有每索引空间页五倍之多节点上盘...这意味着遍历索引将需要五分之一尽可能多的物理和/或逻辑读取..
所以,如果性能是一个问题,有机会,总是用你的表的组成无意义的密钥(称为代理),并为这些表中引用的行外键...
同时,以保证数据的一致性,每个表,其中它的事项应的 也 强>有一个有意义的非数字备用键, (或唯一索引),以确保重复的行不能插入(重复基于有意义的表属性)。
有关你在谈论(如状态查询),它其实并不重要,因为表的大小是如此之小的具体使用。一般情况下也用不到几对性能没有影响,从上表中的索引1000行...
绝对不是。
我已经做INT之间的若干... ...几个性能检查,VARCHAR和CHAR。
千万记录表具有主键(独特和群集)有相同的速度和性能(和子树成本)无论哪个三个予使用。
话虽这么说...使用什么是最适合你的应用程序。不要担心性能。
我对网上缺乏基准测试感到有点恼火,所以我自己进行了测试。
请注意,虽然我不会定期这样做,所以请检查我的设置和步骤是否有任何可能无意中影响结果的因素,并在评论中发表您的疑虑。
设置如下:
- 英特尔® 酷睿™ i7-7500U CPU @ 2.70GHz × 4
- 15.6 GiB RAM,我在测试期间确保其中大约 8 GB 是可用的。
- 148.6 GB SSD 驱动器,具有充足的可用空间。
- Ubuntu 16.04 64 位
- MySQL 版本 14.14 Distrib 5.7.20,适用于 Linux (x86_64)
表格:
create table jan_int (data1 varchar(255), data2 int(10), myindex tinyint(4)) ENGINE=InnoDB;
create table jan_int_index (data1 varchar(255), data2 int(10), myindex tinyint(4), INDEX (myindex)) ENGINE=InnoDB;
create table jan_char (data1 varchar(255), data2 int(10), myindex char(6)) ENGINE=InnoDB;
create table jan_char_index (data1 varchar(255), data2 int(10), myindex char(6), INDEX (myindex)) ENGINE=InnoDB;
create table jan_varchar (data1 varchar(255), data2 int(10), myindex varchar(63)) ENGINE=InnoDB;
create table jan_varchar_index (data1 varchar(255), data2 int(10), myindex varchar(63), INDEX (myindex)) ENGINE=InnoDB;
然后,我用一个PHP脚本填充了每个表中的1000万行,其本质是这样的:
$pdo = get_pdo();
$keys = [ 'alabam', 'massac', 'newyor', 'newham', 'delawa', 'califo', 'nevada', 'texas_', 'florid', 'ohio__' ];
for ($k = 0; $k < 10; $k++) {
for ($j = 0; $j < 1000; $j++) {
$val = '';
for ($i = 0; $i < 1000; $i++) {
$val .= '("' . generate_random_string() . '", ' . rand (0, 10000) . ', "' . ($keys[rand(0, 9)]) . '"),';
}
$val = rtrim($val, ',');
$pdo->query('INSERT INTO jan_char VALUES ' . $val);
}
echo "\n" . ($k + 1) . ' millon(s) rows inserted.';
}
为了 int
表格,一点 ($keys[rand(0, 9)])
被替换为刚刚 rand(0, 9)
, ,并且对于 varchar
表中,我使用了完整的美国州名,没有将其剪切或扩展至 6 个字符。 generate_random_string()
生成一个 10 个字符的随机字符串。
然后我在MySQL中运行:
SET SESSION query_cache_type=0;
- 为了
jan_int
桌子:SELECT count(*) FROM jan_int WHERE myindex = 5;
SELECT BENCHMARK(1000000000, (SELECT count(*) FROM jan_int WHERE myindex = 5));
- 对于其他表,同上,用
myindex = 'califo'
为了char
表和myindex = 'california'
为了varchar
表。
时代的 BENCHMARK
对每个表进行查询:
- 一月int:21.30 秒
- jan_int_index:18.79秒
- 一月字符:21.70 秒
- jan_char_index:18.85秒
- 简_varchar:21.76 秒
- jan_varchar_index:18.86秒
关于表和索引大小,这是输出 show table status from janperformancetest;
(有几列未显示):
|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| Name | Engine | Version | Row_format | Rows | Avg_row_length | Data_length | Max_data_length | Index_length | Data_free | Auto_increment | Collation |
|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| jan_int | InnoDB | 10 | Dynamic | 9739094 | 43 | 422510592 | 0 | 0 | 4194304 | NULL | utf8mb4_unicode_520_ci |
| jan_int_index | InnoDB | 10 | Dynamic | 9740329 | 43 | 420413440 | 0 | 132857856 | 7340032 | NULL | utf8mb4_unicode_520_ci |
| jan_char | InnoDB | 10 | Dynamic | 9726613 | 51 | 500170752 | 0 | 0 | 5242880 | NULL | utf8mb4_unicode_520_ci |
| jan_char_index | InnoDB | 10 | Dynamic | 9719059 | 52 | 513802240 | 0 | 202342400 | 5242880 | NULL | utf8mb4_unicode_520_ci |
| jan_varchar | InnoDB | 10 | Dynamic | 9722049 | 53 | 521142272 | 0 | 0 | 7340032 | NULL | utf8mb4_unicode_520_ci |
| jan_varchar_index | InnoDB | 10 | Dynamic | 9738381 | 49 | 486539264 | 0 | 202375168 | 7340032 | NULL | utf8mb4_unicode_520_ci |
|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
我的结论是,这个特定用例没有性能差异。
对于短代码,可能没有区别。尤其如此,因为保存这些代码的表可能非常小(最多几千行)并且不经常更改(我们最后一次添加新的美国州是什么时候)。
对于键之间变化较大的较大表,这可能很危险。例如,考虑使用用户表中的电子邮件地址/用户名。当您有几百万用户并且其中一些用户的名称或电子邮件地址很长时,会发生什么情况?现在,任何时候您需要使用该键加入该表时,它都会变得更加昂贵。
至于主键,无论物理使得应当被确定作为主键唯一的行。
有关的参考作为外键,使用自动递增的整数作为替代为两个主要的原因是个好主意。结果 - 首先,有一个在加入通常发生更少的开销结果。 - 第二,如果你需要更新包含独特的VARCHAR那么更新有向下级联到所有的子表,并更新所有的人,以及指标,而用INT替代,它只有更新表主表和它的索引。
在drawaback使用替代的是,你可能允许的替代的含义改变:
ex.
id value
1 A
2 B
3 C
Update 3 to D
id value
1 A
2 B
3 D
Update 2 to C
id value
1 A
2 C
3 D
Update 3 to B
id value
1 A
2 C
3 B
这一切都取决于你真正需要担心在你的结构和用什么方法最。
代理人的常见情况 AUTO_INCREMENT
痛:
常见的模式模式是 多对多映射:
CREATE TABLE map (
id ... AUTO_INCREMENT,
foo_id ...,
bar_id ...,
PRIMARY KEY(id),
UNIQUE(foo_id, bar_id),
INDEX(bar_id) );
这种模式的性能要好得多,特别是在使用 InnoDB 时:
CREATE TABLE map (
# No surrogate
foo_id ...,
bar_id ...,
PRIMARY KEY(foo_id, bar_id),
INDEX (bar_id, foo_id) );
为什么?
- InnoDB辅助键需要额外的查找;通过将这一对移入 PK,就可以避免一个方向的情况。
- 二级索引是“覆盖”的,因此不需要额外的查找。
- 由于去掉了这个表,所以变得更小
id
和一个索引。
另一个案例(国家):
country_id INT ...
-- versus
country_code CHAR(2) CHARACTER SET ascii
新手常常将 Country_code 标准化为 4 字节 INT
而不是使用“自然”的 2 字节、几乎不变的 2 字节字符串。更快、更小、更少的 JOIN、更具可读性。
在HauteLook,我们改变了我们的许多表的使用自然键。我们没有遇到性能真实世界的增长。至于你提到的,我们的很多疑问现在使用较少的加入使查询更好的性能。我们甚至会用复合主键,如果这是有道理的。话虽这么说,一些表只是更容易,如果他们有一个代理键工作。
此外,如果你让人们写信给你的数据库接口,一个代理键会有所帮助。第三方可以依靠的事实,代理键会在极少数情况下才会改变。
现在的问题是关于MySQL,所以我说是有显著差异。如果是关于Oracle(用于存储数字作为字符串 - 是的,我不能在第一次相信它),那么没有太大的区别
存储在表中没有,但更新和参照索引是问题。涉及查找基于其主键的记录查询频繁 - 你想,因为他们经常会发生他们尽可能快地发生
。的事情是用4个字节和8个字节的整数一个CPU处理自然,在硅。这真是快它来比较两个整数 - 它发生在一个或两个时钟周期。
现在看一个字符串 - 它是由很多个字符(每个字符多个字节这些天)的。对于优先级比较两个字符串不能在一个或两个周期来完成。相反,直到发现差别时字符串字符必须被重复。我敢肯定,有技巧,使其在一些数据库快,而是因为一个int比较自然地完成,快如闪电的硅由CPU这就是这里无关紧要。
我的一般原则 - 每一个主键应该特别是在二OO的自增INT应用使用的是一个ORM(休眠,DataNucleus将什么的),还有很多对象之间的关系 - 他们通常会总是被实现为一个简单的FK和快速解决这些能力为DB是您的应用程序”重要 小号的响应性。
我面临同样的困境。我有3事实表,道路交通事故,车辆的事故和伤亡的事故中,有一个DW(星座模式)。数据包括记录在英国1979年至2012年的所有事故,60个维度表。总之,约20万条记录。
事实表的关系:
+----------+ +---------+
| Accident |>--------<| Vehicle |
+-----v----+ 1 * +----v----+
1| |1
| +----------+ |
+---<| Casualty |>---+
* +----------+ *
RDMS:MySQL的5.6
本身的事故指数是一个varchar(数字和字母)中,用15位数字。我试图不具有代理键,一旦发生事故指标永远不会改变。 在i7处理器(8个核)的计算机,在DW成为视后的尺寸的1200万条记录负载的查询速度太慢。 大量的返工和增加BIGINT代理键后,我得到了平均20%的速度性能提升。 然而,低性能增益,但有效尝试。林在MySQL调谐和聚类工作。
不知道对性能的影响,但它似乎是一个可能的妥协,在开发过程中,至少将包括自动递增整数“代孕”键,以及你的意图,独一无二的,“自然”键。这将使你有机会评估性能,以及其他可能的问题,包括自然键的可变性。
和往常一样,没有毯答案。 '这取决于!'我不是在开玩笑。我原来的问题的理解是对小表的键 - 就像国家(整数ID或CHAR / VARCHAR代码)是一个外键,如地址/联系表一个潜在的巨大的表
这里有两种情况,当你想将数据从DB回来。首先是一个列表/搜索类型的查询要列出所有与国家和国家代码或名称(IDS不会帮助,因此将需要一个查找)接触。另一种是在主键的GET场景,其示出了状态的名称,国家需要被示出的单个联系人记录。
对于后者GET,它可能并不重要的FK是基于因为我们正在把表一起为单个记录或几个记录,并在关键的读取。前者(搜索或列表)的情况可以通过我们的选择会受到影响。既然是必须出示国家(至少一个识别码,甚至搜索本身包括国家代码),不必通过代理键加入另一个表有可能(我只是在这儿谨慎,因为我没有实际测试这一点,但似乎极有可能),提高性能;尽管事实上它肯定与搜索帮助。
作为代码在大小是小 - 通常不超过3个字符为国家和状态时,它可以是好使用自然键作为外键在这种情况下
。在其它情形,其中键是依赖于较长VARCHAR值和或许在较大的表;代理键可能具有的优点。
请允许我说是的肯定是有区别的,同时考虑到性能范围(开箱即用的定义):
1使用替代int是在应用速度更快,因为你并不需要在你的代码或查询中使用ToUpper的(),ToLower将(),ToUpperInvarient(),或ToLowerInvarient(),并且这些4个功能具有不同的业绩基准。看到这个微软性能规则。 (应用程序的性能)
2-使用替代INT保证不随时间变化的键。即使国家代码可能会改变,见维基百科ISO代码是如何随时间变化。这将需要大量的时间来更改子树的主键。 (数据维护性能)
3-似乎存在与ORM解决方案,如NHibernate的问题,当PK / FK是不是int。 (显影剂的性能)