在MySQL中使用INT与VARCHAR作为主键之间是否存在可测量的性能差异?我想将VARCHAR用作参考列表的主键(想想美国的州和国家/地区代码),并且同事不会花INT AUTO_INCREMENT作为所有表的主键。
我的论点,详见这里,是INT和VARCHAR之间的性能差异可以忽略不计,因为每个INT外键引用将需要JOIN的参考意义,一个VARCHAR键则直接呈现的信息。
那么,是否有人对这个特定用例以及与此相关的性能问题有经验?
在MySQL中使用INT与VARCHAR作为主键之间是否存在可测量的性能差异?我想将VARCHAR用作参考列表的主键(想想美国的州和国家/地区代码),并且同事不会花INT AUTO_INCREMENT作为所有表的主键。
我的论点,详见这里,是INT和VARCHAR之间的性能差异可以忽略不计,因为每个INT外键引用将需要JOIN的参考意义,一个VARCHAR键则直接呈现的信息。
那么,是否有人对这个特定用例以及与此相关的性能问题有经验?
Answers:
您提出了一个很好的观点,即可以通过使用自然键而不是代理键来避免一些联接查询。只有您可以评估这样做的好处在您的应用程序中是否很重要。
也就是说,您可以测量应用程序中最重要的查询,因为它们要处理大量数据或执行得非常频繁,因此它们对于提高查询速度至关重要。如果这些查询从消除联接中受益,并且不因使用varchar主键而受苦,则可以这样做。
不要对数据库中的所有表都使用这两种策略。在某些情况下,自然键可能会更好,但在其他情况下,替代键会更好。
其他人指出,在实践中,很少有自然密钥永远不会更改或重复,因此代理密钥通常是值得的。
这与性能无关。这是什么使一个好的主键。随着时间的流逝,保持不变。您可能会认为诸如国家/地区代码之类的实体不会随时间变化,并且将是主键的理想选择。但是痛苦的经历很少如此。
INT AUTO_INCREMENT满足“唯一且不随时间变化”的条件。因此,偏好。
网上缺乏基准测试使我有些恼火,所以我自己进行了测试。
请注意,尽管我不会定期进行此操作,所以请检查我的设置和步骤以了解可能无意影响结果的任何因素,并在评论中发表您的疑虑。
设置如下:
表格:
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)])
替换为just 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
每个表的查询时间:
关于表和索引的大小,以下是输出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 |
|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
我的结论是,此特定用例没有性能差异。
INDEX
代替的公平点PRIMARY KEY
。我不记得我的推理-我可能认为PRIMARY KEY
这只是INDEX
具有唯一性约束的。但是,在federico-razzoli.com/primary-key-in-innodb中阅读有关如何在InnoDB中存储内容的部分,我认为我的结果仍然适用于主键,并回答了有关值查找性能差异的问题。另外,您的评论还建议您查看排序算法的性能,这不适用于我研究的用例,而是在一个集合中查找值。
取决于长度。如果varchar为20个字符,而int为4,则如果您使用int,则索引在磁盘上每页索引空间的节点数是其的五倍...这意味着遍历该索引将需要五分之一的物理和/或逻辑读取。
因此,如果性能是一个问题,只要有机会,请始终对表以及引用这些表中的行的外键使用完整的无意义键(称为代理键)。
同时,为了保证数据的一致性,每张桌子真正重要的事情应该还具有有意义的非数字备用键(或唯一索引),以确保不能插入重复的行(基于有意义的表属性进行重复)。
对于您正在谈论的特定用途(如状态查找),这实际上并不重要,因为表的大小非常小。.通常,少于几千行的表的索引不会对性能产生影响。 ..
绝对不。
我已经做了几个...几个...在INT,VARCHAR和CHAR之间进行性能检查。
无论我使用哪三个,带有主键(唯一键和群集键)的1000万条记录表的速度和性能(以及子树成本)都完全相同。
话虽这么说...使用最适合您的应用程序的东西。不用担心性能。
对于短代码,可能没有什么区别。尤其是因为包含这些代码的表可能很小(最多两千行)并且不经常更改(这是我们上一次添加新美国州的时间)。
对于键之间变化较大的较大表,这可能很危险。例如,考虑使用“用户”表中的电子邮件地址/用户名。当您拥有数百万个用户并且其中一些用户具有长名或电子邮件地址时,会发生什么。现在,任何时候您需要使用该键加入该表时,它的成本就会大大提高。
至于主键,无论物理上使行唯一的都应确定为主键。
对于作为外键的引用,使用自动递增整数作为代理是一个不错的主意,主要有两个原因。
-首先,通常在连接中产生的开销较少。
-其次,如果您需要更新包含唯一varchar的表,则更新必须级联到所有子表并更新所有子表以及索引,而使用int代理,则只需更新主表及其索引。
使用代理的缺点是您可以允许更改代理的含义:
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) );
为什么?
id
一个索引,因此该表较小。另一个案例(国家):
country_id INT ...
-- versus
country_code CHAR(2) CHARACTER SET ascii
新手经常将country_code标准化为4字节,INT
而不是使用“自然的” 2字节,几乎不变的2字节字符串。更快,更小,更少的JOIN,更易读。
我面临着同样的困境。我用3个事实表(道路事故,事故中的车辆和事故中的人员伤亡)制作了DW(星座模式)。数据包括1979年至2012年在英国记录的所有事故,以及60个维度表。总共约有2000万条记录。
+----------+ +---------+
| Accident |>--------<| Vehicle |
+-----v----+ 1 * +----v----+
1| |1
| +----------+ |
+---<| Casualty |>---+
* +----------+ *
RDMS:MySQL 5.6
本地的“事故索引”是一个带有15位数字的varchar(数字和字母)。一旦事故索引永远不会改变,我就尝试没有代理键。在i7(8核)计算机上,DW在记录了1200万条负载记录(取决于尺寸)后变得太慢而无法查询。经过大量的重新工作并添加了bigint代理键,我的速度性能平均提高了20%。尚未获得较低的性能增益,但可以尝试。我正在从事MySQL调整和群集工作。
问题是关于MySQL的,所以我说有很大的不同。如果是关于Oracle(将数字存储为字符串-是的,我起初不敢相信),那么差别就不大了。
表中的存储不是问题,但更新和引用索引是问题。涉及基于记录的主键查找记录的查询很常见-您希望它们发生得尽可能快,因为它们经常发生。
问题是CPU在硅片中自然处理4字节和8字节整数。比较两个整数真的非常快-它发生在一个或两个时钟周期内。
现在来看一个字符串-它由许多字符组成(如今,每个字符超过一个字节)。比较两个字符串的优先级不能在一两个周期内完成。相反,必须迭代字符串的字符,直到找到差异为止。我敢肯定,有一些技巧可以使某些数据库更快,但是这无关紧要,因为int比较是自然完成的,而CPU在硅片上的闪电速度很快。
我的一般规则-每个主键都应该是一个自动递增的INT,尤其是在使用ORM(休眠,Datanucleus等)的OO应用中,对象之间存在很多关系-通常,它们通常将实现为简单的FK,并且快速解决这些问题的数据库对于应用程序的响应能力很重要。
像往常一样,没有一揽子答案。'这取决于!' 我也没戏 我对原始问题的理解是针对小型表上的键-例如Country(整数ID或char / varchar代码)是潜在大型表(如地址/联系表)的外键。
您需要从数据库返回数据的两种情况。首先是列表/搜索类型的查询,您要在其中列出所有带有州和国家/地区代码或姓名的联系人(ID将无济于事,因此需要查找)。另一个是主键上的get场景,它显示了一个联系人记录,其中需要显示州名,国家/地区。
对于后者,FK的基础可能无关紧要,因为我们将针对单个记录或一些记录以及键读取的表放在一起。前一种(搜索或列表)方案可能会受到我们选择的影响。由于要求显示国家(至少是可识别的代码,甚至搜索本身都包括国家代码),因此不必通过代理键加入另一个表(我在这里要谨慎,因为我尚未实际测试过)这样,但似乎很有可能)提高性能;尽管它确实有助于搜索。
由于代码的大小很小-国家和州的代码通常不超过3个字符,因此在这种情况下可以使用自然键作为外键。
另一种情况是,键依赖于较长的varchar值,并且可能依赖于较大的表;代理密钥可能具有优势。
考虑到性能范围(开箱即用的定义),请允许我说肯定有区别:
1-在应用程序中使用替代int更快,因为您无需在代码或查询中使用ToUpper(),ToLower(),ToUpperInvarient()或ToLowerInvarient(),并且这4个函数具有不同的性能基准。请参阅Microsoft性能规则。(申请的执行)
2-使用替代int保证不会随着时间的推移更改密钥。甚至国家/地区代码也可能更改,请参阅Wikipedia ISO代码随时间的变化。更改子树的主键将花费大量时间。(数据维护性能)
3-似乎PK / FK不是int时,ORM解决方案存在问题,例如NHibernate。(开发人员表现)