避免大 ID 值的原因

rum*_*cho 17 sql-server auto-increment

我们正在开发一个 Web 应用程序,用户尚无法访问。我的老板注意到新创建的记录的 ID 超过 10 000,即使表中只有不到 100 条记录。她假设 Web 界面出于某种原因创建了比实际记录多 100 倍的临时记录(并删除了它们),这可能导致我们在发布后的几个月内超出范围。

我不认为她关于 ID 膨胀的原因是正确的(可以回答这个问题的同事正在休假,所以我们不确定),但让我们假设她是。她说她讨厌使用 bigint 列,她希望我们停止自动递增 ID 列并编写服务器端代码,选择第一个“未使用”的整数并将其用作 ID。

我是一名计算机科学研究生,几乎没有实践经验,担任初级开发人员的角色。她在管理我们组织的所有数据库和设计其中大部分数据库方面拥有多年经验。我认为在这种情况下她是不正确的,bigint ID 没有什么可害怕的,并且模仿 DBMS 功能的反模式气味。但我还不相信我的判断。

支持和反对每个立场的论据是什么?如果我们使用 bigint 会发生什么坏事,重新发明轮子自动递增功能有什么危险?有没有比任何一种都更好的第三种解决方案?她想要避免身份证面值膨胀的原因是什么?我也有兴趣了解实际原因 - 也许 bigint ID 理论上可行,但在实践中会引起头痛?

该应用程序预计不会处理大量数据。我怀疑它会在未来几年内达到 10 000 条实际记录。

如果它有任何区别,我们正在使用 Microsoft SQL 服务器。该应用程序是用 C# 编写的,并使用 Linq to SQL。

更新

谢谢,我发现现有的答案和评论很有趣。但恐怕你误解了我的问题,所以它们包含了我想知道的。

我并不真正关心高 ID 的真正原因。如果我们自己找不到它,我可以问一个不同的问题。我感兴趣的是了解这种情况下的决策过程。为此,请假设应用程序将每天写入 1000 条记录,然后删除其中的 9999 条。我几乎可以肯定事实并非如此,但这就是我的老板提出要求时所相信的。那么,在这些假设情况下,使用 bigint 或编写我们自己的代码来分配 ID(以重用已删除记录的 ID 的方式,以确保没有间隙)的优缺点是什么?

至于实际原因,我强烈怀疑这是因为我们曾经写过代码从另一个数据库中导入数据,作为概念证明,以后可以在一定程度上进行迁移。我认为我的同事在导入过程中实际上创建了数千条记录,后来又删除了它们。我必须确认是否确实如此,但如果是,则甚至不需要采取行动。

Han*_*non 24

如果没有看到代码,很难确切地说出正在发生的事情。虽然,很可能该IDENTITY值被缓存,导致 SQL Server 重新启动后该值出现间隙。请参阅/sf/ask/1231096611/以获得一些好的答案和相关信息。

一个简单的INT字段最多可以保存 2,147,483,647 个值。您实际上可以从 -2,147,483,648 开始标识值,提供​​完整的 32 位值。40 亿个不同的值。我非常怀疑你会用完值来使用。假设你的应用程序耗时增加每个实际行1000倍的值,你需要要创建每天每天近12000行ID的6个月内假设你开始冒了出来IDENTITY,在0值,并使用INT。如果您使用的是 BIGINT,如果您每天写入 12,000 行,每行消耗 1,000 个“值”,那么您将不得不等待 2100 万个世纪才能用完值。

话虽如此,如果您想BIGINT用作身份字段数据类型,那当然没有错。这将为您提供所有意图和目的,无限的价值供应。INT 和 BIGINT 之间的性能差异在现代 64 位硬件上几乎不存在,并且比用于NEWID()生成 GUID的示例更为可取。

如果您想管理自己的 ID 列值,您可以创建一个键表,并使用以下问题的答案中显示的方法之一提供一种非常可靠的方法: Handling concurrent access to a key table without SQL Server 中的死锁

假设您使用的是 SQL Server 2012+,另一个选项是使用SEQUENCE对象来获取列的 ID 值。但是,您需要将序列配置为不缓存值。例如:

CREATE SEQUENCE dbo.MySequence AS INT START WITH -2147483648 INCREMENT BY 1 NO CACHE;
Run Code Online (Sandbox Code Playgroud)

针对你老板对“高”数字的负面看法,我想说这有什么不同?假设您使用一个带有 的INT字段,IDENTITY您实际上可以开始IDENTITYat2147483647并将值“增加” -1。这对内存消耗、性能或使用的磁盘空间绝对没有影响,因为 32 位数字是 4 个字节,无论是0还是2147483647. 0二进制是00000000000000000000000000000000存储在 32 位有符号INT字段中时。 214748364701111111111111111111111111111111- 两个数字在内存和磁盘上占用的空间量完全相同,并且都需要完全相同的 CPU 操作量来处理。正确设计应用程序代码比关注存储在关键字段中的实际数字重要得多。

您询问了 (a) 使用更大容量的 ID 列,例如 a BIGINT,或 (b) 滚动您自己的解决方案以防止 ID 间隙的优缺点。回答这些问题:

  1. BIGINT而不是INT作为相关列的数据类型。使用 aBIGINT需要两倍的存储量,包括列本身的磁盘和内存。如果该列是所涉及的表的主键索引,则附加到该表的每个非聚集索引也将存储BIGINT值,大小是 的两倍INT,同样在内存中和磁盘上。SQL Server 将数据以 8KB 页的形式存储在磁盘上,其中每“页”的“行”数取决于每行的“宽度”。因此,例如,如果您有一个包含 10 列的表,每列一个INT,那么每页大约可以存储 160 行。如果那些列在哪里BIGINT列,每页只能存储 80 行。对于具有大量行的表,这显然意味着在此示例中,对于任何给定的行数,读取和写入表所需的 I/O 将翻倍。诚然,这是一个非常极端的例子 - 如果您有一行由单个INTorBIGINT列和一个NCHAR(4000)列组成,那么您将(简单地)每页获得一行,无论您使用 anINT还是 a BIGINT。在这种情况下,它不会产生太大的显着差异。

  2. 滚动您自己的场景以防止 ID 列中出现间隙。您需要以这样一种方式编写代码,即确定要使用的“下一个”ID 值不会与表中发生的其他操作发生冲突。SELECT TOP(1) [ID] FROM [schema].[table]天真地想到了一些东西。如果有多个参与者试图同时向表中写入新行怎么办?两个 actor 很容易获得相同的值,从而导致写入冲突。解决这个问题需要序列化对表的访问,从而降低性能。关于这个问题已经有很多文章了;我将留给读者对该主题进行搜索。

这里的结论是:您需要了解您的需求并正确估计行数和行宽,以及应用程序的并发需求。像往常一样,它取决于™。

  • +1 但我不会放弃 BIGINT 的空间要求。与其说是磁盘空间,不如说是浪费在内存中的 I/O 和空间。您可以使用数据压缩来抵消其中的很多,因此在超过 20 亿之前,您不会真正感受到 BIGINT 类型的冲击。理想情况下,他们只会解决问题(我犹豫是否将其称为错误)——虽然人们不应该关心差距,虽然人们不应该每天重新启动服务器 15 次,但我们有这两种情况相当普遍,而且经常同时出现。 (4认同)
  • 像往常一样,非常有效的观点,亚伦。无论如何,我倾向于使用 INT,因为 BIGINT 几乎完全是矫枉过正,除非他们期待大量的行。 (3认同)
  • 虽然如果你*确实*有一个合理的期望,你可能需要`bigint`,你可能会感谢自己提前决定而不是需要将它添加到具有数十亿行的表中。 (3认同)
  • @ user2338816 这就是重点 - 如果表变大,内存中会有很多。而且由于标识列通常是集群键,因此每个索引中的每一行也额外增加 4 个字节。在每种情况下都重要吗?不。它应该被忽略吗?绝对不。似乎没有人对可扩展性发表意见,直到为时已晚。 (2认同)

小智 6

要做的主要任务是找出当前值如此高的根本原因。

对 SQL2012 之前的 SQL Server 版本的最合理解释(假设您在谈论测试数据库)是先进行负载测试,然后进行清理。

从 SQL2012 开始,最可能的原因是 SQL 引擎多次重启(如 Max 提供的第一个链接中所述)。

如果差距是由测试场景引起的,从我的角度来看,没有理由担心。但为了安全起见,我会在应用程序的正常使用期间以及引擎重新启动之前和之后检查身份值。

MS 声明这两种选择(跟踪标志 272 或新的 SEQUENCE 对象)都可能影响性能,这很“有趣”。

使用 BIGINT 而不是 INT 可能是最好的解决方案,只是为了安全起见来涵盖 MS 的下一个“改进”......