mysql int vs varchar作为主键(InnoDB存储引擎?


13

我正在构建一个Web应用程序(项目管理系统),并且在性能方面一直想知道这一点。

我有一个Issues表,里面有12个外键链接到其他各种表。在其中的8个中,我需要加入才能从其他表中获取title字段,以便使记录在Web应用程序中有意义,但是,这意味着进行8个加入似乎非常繁琐,尤其是因为我只是在拉入这些联接中的每个联接都有1个字段。

现在,出于永久性的原因,我还被告知要使用自动递增的主键(除非考虑到分片,在这种情况下我应该使用GUID),但是在性能上使用varchar(最大长度为32)有多糟糕?我的意思是,这些表中的大多数可能不会有很多记录(其中大多数应该在20以下)。另外,如果我使用标题作为主键,则不必在95%的时间内进行联接,因此对于95%的sql,我什至会发生任何性能下降(我认为)。我唯一能想到的缺点是我将拥有更高的磁盘空间使用率(但是一天下来确实是一件大事)。

我将查找表用于很多此类而不是枚举的原因是因为我需要最终用户可以通过应用程序本身配置所有这些值。

将varchar用作不包含很多记录的表的主键有什么弊端?

更新-一些测试

因此,我决定对此做一些基本测试。我有100000条记录,这些是基本查询:

基本VARCHAR FK查询

SELECT i.id, i.key, i.title, i.reporterUserUsername, i.assignedUserUsername, i.projectTitle, 
i.ProjectComponentTitle, i.affectedProjectVersionTitle, i.originalFixedProjectVersionTitle, 
i.fixedProjectVersionTitle, i.durationEstimate, i.storyPoints, i.dueDate, 
i.issueSecurityLevelId, i.creatorUserUsername, i.createdTimestamp, 
i.updatedTimestamp, i.issueTypeId, i.issueStatusId
FROM ProjectManagement.Issues i

基本INT FK查询

SELECT i.id, i.key, i.title, ru.username as reporterUserUsername, 
au.username as assignedUserUsername, p.title as projectTitle, 
pc.title as ProjectComponentTitle, pva.title as affectedProjectVersionTitle, 
pvo.title as originalFixedProjectVersionTitle, pvf.title as fixedProjectVersionTitle, 
i.durationEstimate, i.storyPoints, i.dueDate, isl.title as issueSecurityLevelId, 
cu.username as creatorUserUsername, i.createdTimestamp, i.updatedTimestamp, 
it.title as issueTypeId, is.title as issueStatusId
FROM ProjectManagement2.Issues i
INNER JOIN ProjectManagement2.IssueTypes `it` ON it.id = i.issueTypeId
INNER JOIN ProjectManagement2.IssueStatuses `is` ON is.id = i.issueStatusId
INNER JOIN ProjectManagement2.Users `ru` ON ru.id = i.reporterUserId
INNER JOIN ProjectManagement2.Users `au` ON au.id = i.assignedUserId
INNER JOIN ProjectManagement2.Users `cu` ON cu.id = i.creatorUserId
INNER JOIN ProjectManagement2.Projects `p` ON p.id = i.projectId
INNER JOIN ProjectManagement2.`ProjectComponents` `pc` ON pc.id = i.projectComponentId
INNER JOIN ProjectManagement2.ProjectVersions `pva` ON pva.id = i.affectedProjectVersionId
INNER JOIN ProjectManagement2.ProjectVersions `pvo` ON pvo.id = i.originalFixedProjectVersionId
INNER JOIN ProjectManagement2.ProjectVersions `pvf` ON pvf.id = i.fixedProjectVersionId
INNER JOIN ProjectManagement2.IssueSecurityLevels isl ON isl.id = i.issueSecurityLevelId

我还使用以下功能运行了这些查询:

  • 选择特定项目(其中i.key = 43298)
  • 按i.id分组
  • 排序方式(用于int FK的it.title,用于varchar FK的i.issueTypeId)
  • 限制(50000,100)
  • 分组和限制在一起
  • 一起分组,订购和限制

这些结果包括:

查询类型:VARCHAR FK TIME / INT FK TIME


基本查询:〜4ms /〜52ms

选择特定项目:〜140ms /〜250ms

按i.id分组:〜4ms /〜2.8sec

排序:〜231ms /〜2sec

时限:〜67ms /〜343ms

分组和限制在一起:〜504ms /〜2sec

一起分组,排序和限制:〜504ms /~2.3sec

现在我不知道我可以做些什么配置来使一个或另一个(或两者都)更快,但是似乎VARCHAR FK在查询数据时看到更快(有时快很多)。

我想我必须选择提高速度是否值得额外的数据/索引大小。


您的测试表明有问题。我还将测试各种InnoDB设置(缓冲池等),因为默认MySQL设置并未真正针对InnoDB优化。
ypercubeᵀᴹ

您还应该测试插入/更新/删除性能,因为这也会受到索引大小的影响。每个InnoDB表的一个集群键通常是PK,此(PK)列也包含在其他所有索引中。这可能是InnoDB中大型PK和表上许多索引的一大缺点(但是32字节是中等大小,不是很大,因此这可能不是问题)。
ypercubeᵀᴹ

如果您希望表增长到100K以上(不是很大),则还应该使用更大的表(例如10-100M行或更大的表)进行测试。
ypercubeᵀᴹ

@ypercube因此,我将数据增加到200万,并且在varchar外键保持稳定的情况下,用于int FK的select语句呈指数增长。有人认为,对于选择查询来说,varchar值得磁盘/内存需求中的价格(这对于此特定表和其他几个表将至关重要)。
ryanzec'4

在得出结论之前,只需检查您的数据库(尤其是InnoDB)设置。有了小的参考表,我不希望指数增长
ypercubeᵀᴹ

Answers:


9

我对主键遵循以下规则:

a)不应有任何业务意义-它们应该完全独立于您正在开发的应用程序,因此我选择自动生成的数字整数。但是,如果您需要其他列是唯一的,则可以创建唯一索引以支持该列

b)应该在联接中执行-随着主键长度的增加,联接到varchars与整数的速度要慢2到3倍,因此您希望将键作为整数。由于所有计算机系统都是二进制的,因此我怀疑它的字符串将字符串更改为二进制,然后与其他计算机相比比较慢

c)尽可能使用最小的数据类型-如果您希望表中只有很少的列(例如52个美国州),则使用可能的最小类型(例如CHAR(2)来表示2位数字),但是我还是会选择使用tinyint (128)对于列vs一个大型int可以达到20亿

此外,例如,如果项目名称发生更改(这很常见),那么将您的更改从主键级联到其他表也将面临挑战。

为您的主键选择顺序自动递增的整数,并获得数据库系统提供的内置效率,以支持将来的更改


1
字符串不更改为二进制;它们从一开始就以二进制形式存储。它们还将如何存储?也许您在考虑允许不区分大小写的比较的操作?
所有行业的乔恩(Jon of All Trades)2012年

6

在您的测试中,您不是在比较varchar和int键的性能差异,而是比较多个联接的成本。毫不奇怪,查询1个表比连接多个表要快。
正如atxdba指出的那样,varchar主键的缺点之一是增加了索引大小。即使您的查找表除PK之外没有其他索引(这不太可能,但有可能),每个引用查找的表在此列上也会有一个索引。
关于自然主键的另一个坏处是,它们的值可能会更改,从而导致许多级联更新。并非所有的RDMS,例如Oracle,甚至都可以让您拥有on update cascade。一般而言,更改主键值是一种非常糟糕的做法。我不想说自然主键总是邪恶的。如果查找值很小并且永不更改,我认为可以接受。

您可能要考虑的一种选择是实现实例化视图。Mysql不直接支持它,但是您可以使用基础表上的触发器来实现所需的功能。因此,您将有一个表,其中包含您需要显示的所有内容。此外,如果性能可以接受,请不要遇到当前不存在的问题。


3

最大的缺点是PK的重复。您指出了磁盘空间使用量的增加,但是要明确的是,增加索引大小是您更大的担忧。由于innodb是聚集索引,每个二级索引在内部存储PK的副本,该副本最终用于查找匹配记录。

您说表应该是“小”的(20行的确很小)。如果您有足够的RAM来将innodb_buffer_pool_size设置为等于

select sum(data_length+index_length) from information_schema.tables where engine='innodb';

然后这样做,您可能会坐得很漂亮。作为一般规则,尽管您希望将系统总内存的至少30%-40%留给其他mysql开销和Dis缓存。并假设它是专用的数据库服务器。如果您在系统上运行其他内容,则还需要考虑它们的要求。


1

除了@atxdba答案-这还向您解释了为什么使用数字会更好地占用磁盘空间,我想补充两点:

  1. 如果您的Issues表是基于VARCHAR FK的,并且假设您有20个小型VARCHAR(32)FK,则记录的长度可以达到20x32bytes,而正如您提到的,其他表是查找表,因此INT FK可以是TINYINT FK,这使得对于20个字段,一个20字节的记录。我知道几百条记录不会有太大变化,但是当您达到几百万条记录时,我想您会节省空间的

  2. 对于速度问题,我会考虑使用覆盖索引,因为对于此查询,您似乎没有从查找表中检索到大量数据,因此我将用于覆盖索引并再次测试您提供的VARCHAR FK / W / COVERING索引和常规INT FK。

希望能有所帮助,

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.