【问题标题】:Choice of primary key type主键类型的选择
【发布时间】:2010-11-23 04:17:16
【问题描述】:

我有一个每秒可能有大量插入的表,我正在尝试选择我想使用的主键类型。出于说明目的,假设它是用户表。我试图在使用 GUID 和 BIGINT 作为主键和最终作为整个应用程序的 UserID 之间进行选择。如果我使用 GUID,我会保存一次到数据库以生成新 ID,但 GUID 不是“用户友好的”,并且无法按此 ID 对表进行分区(我打算这样做)。使用 BIGINT 更方便,但生成它是一个问题——我不能使用 IDENTITY(这是有原因的),所以我唯一的选择是有一些包含上次使用的 ID 的帮助表,然后我称之为存储过程:

create proc GetNewID @ID BIGINT OUTPUT
as
begin
update HelperIDTable set @ID=id, id = id + 1 
end

获取新的ID。但是这个帮助表是一个明显的瓶颈,我担心它每秒可以更新多少次。

我真的很喜欢使用 BIGINT 作为 pk 的想法,但瓶颈问题与我有关 - 有没有办法粗略估计它每秒可以产生多少个 id?我意识到它高度依赖于硬件,但是有任何物理限制吗?我们在看什么程度? 100 秒/秒? 1000 秒/秒?

非常感谢任何有关如何解决该问题的想法!这个问题现在让我睡不着觉!

谢谢! 安德烈

【问题讨论】:

  • 您能否具体说明一下为什么不能使用 IDENTITY?
  • 另外,BIGINT 是一个非常大的 int,INT 对你来说还不够吗?如果是用户表,您拥有超过 2,147,483,647 个用户的可能性有多大?
  • 如果你在一个事务中使用其他 CRUD 操作调用该过程,你将锁定并导致等待
  • 实际上是 (2,147,483,647 * 2),因为你可以在 -2,147,483,647 获得种子

标签: sql-server primary-key scalability


【解决方案1】:

GUID 似乎是一个自然的选择 - 如果你真的必须,你可能会争辩将它用作表的 PRIMARY KEY - 唯一标识数据库中行的单个值。

我强烈建议不要使用 GUID 列作为群集键,SQL Server 默认会这样做,除非您明确告诉它不要这样做。

正如Kimberly Tripp - 索引女王 - 和其他人已经多次声明的那样 - 作为集群键的 GUID 并不是最优的,因为由于它的随机性,它会导致大量页面和索引碎片,并一般表现不佳。

是的,我知道 - 在 SQL Server 2005 及更高版本中有 newsequentialid() - 但即使这样也不是真正和完全顺序的,因此也会遇到与 GUID 相同的问题 - 只是不太突出。

还有一个需要考虑的问题:表上的聚簇键也将添加到表上每个非聚簇索引的每个条目中 - 因此您确实希望确保它尽可能小.通常,具有 2 亿以上行的 INT 对于绝大多数表来说应该足够了 - 与作为集群键的 GUID 相比,您可以在磁盘和服务器内存中节省数百兆字节的存储空间。

所以总结一下:除非你有一个很好的理由,否则我总是推荐一个 INT IDENTITY 字段作为你表上的主键/聚簇键。

马克

【讨论】:

  • 当你说 newsequentialid() “不是真正和完全连续的”时你是什么意思?
  • +1 Kim Tripp 关于 GUID 的帖子。了解物理实现是件好事……
  • @kyralessa:有一段时间是连续的 - 然后序列中有跳跃,然后它可能又会连续一段时间。我原以为如果我插入 10'000 行,它们将具有真正连续的新 GUID,但事实并非如此。
【解决方案2】:

我尝试对除小型查找表之外的所有表使用 GUID PK。 GUID 概念确保可以在内存中安全地创建对象的标识,而无需往返数据库并在以后保存而不更改标识。

当您需要“人类可读”的 id 时,您可以在保存时使用自动增量 int。对于分区,您还可以稍后通过数据库计划一次性为多个用户创建 BIGINT。

【讨论】:

  • 实际上,如果您使用 GUID 并且表是集群的,那么您的性能将会很糟糕,因为记录将插入 CL IX 的随机点并导致大量页面拆分。我建议不要这样做。
  • 你的表现怎么样? GUID 作为主要的(因此默认情况下是集群键)对性能来说很糟糕。查看 Kim Tripp 关于该主题的优秀博文:sqlskills.com/BLOGS/KIMBERLY/category/Indexes.aspx
  • 是的,但是您可以创建 PK nmon clustered 并在 BIGINT 列上设置一个聚集索引。
【解决方案3】:

您是出于业务原因需要主键,还是出于存储问题需要集群键? 请参阅stackoverflow.com/questions/1151625/int-vs-unique-identifier-for-id-field-in-database 以获取有关 PK 与集群密钥主题的更详细的帖子。

您确实必须详细说明为什么不能使用 IDENTITY。手动生成 ID,特别是在服务器上进行额外的往返和更新,以生成每个 ID 以用于插入它不会扩展。你很幸运能达到每秒 100 秒。问题不仅在于往返和更新时间,还主要来自 ID 生成更新与插入批处理的交互:插入批处理事务将序列化 ID 生成。解决方法是在单独的会话上分离 ID 生成,以便它可以自动提交,但是插入批处理是没有意义的,因为 ID 生成没有批处理:它必须在每个 ID 生成后等待日志刷新命令提交。与此相比,uuid 将围绕您的手动 ID 生成运行。但是由于碎片化,uuid 是集群密钥的可怕选择。

【讨论】:

    【解决方案4】:

    尝试使用脚本访问您的数据库,可能使用 jmeter 来模拟并发命中。也许您可以然后衡量自己可以处理多少负载。您的数据库也可能导致瓶颈。哪一个?我希望 PostgreSQL 负载重,就像 yahoo 和 skype 一样

    【讨论】:

      【解决方案5】:

      一个需要认真测试的想法:尝试批量创建(插入)新行——比如一次 1000(10,000?1M?)。您可能有一个主(又名瓶颈)表列出下一个要使用的表,或者您可能有一个查询,类似于

       select min(id) where (name = '')
      

      在早上、每小时或当您的空闲行数达到一定数量时生成一批新的空行。这仅解决了生成新 ID 的问题,但如果这是主要瓶颈,它可能会有所帮助。

      表分区选项:假设一个 bigint ID 列,您如何定义分区?如果您每天允许 1G 行,您可以在晚上设置新分区(第 1 天 = 1,000,000,000 到 1,999,999,999,第 2 天 = 2,000,000,000 到 2,999,999,999 等),然后在准备好时将其换入。当然,您的分区限制为 1000 个,因此使用 bigint 时,您会在 ID 用完之前用完分区。

      【讨论】:

      • 这里主要是一个思考练习,可能没有太大帮助。
      猜你喜欢
      • 1970-01-01
      • 2018-09-08
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-05-23
      • 2012-07-28
      • 2011-05-16
      相关资源
      最近更新 更多