我们应该为主键使用序列还是身份?

Should we use sequences or identities for our primary keys?

我们正在创建一个包含 20 多个表的新数据库,我们的数据库支持:

所以,问题是:我们应该使用序列还是恒等式?哪一个更好?团队似乎在这个问题上存在分歧,所以我想听听 proscons,以帮助做出决定。

正在添加数据库详细信息:

我是序列的粉丝。我喜欢所有的 ID 都是相同的类型,并且所有的 ID 都来自相同的序列。这不是必需的,只是让您梳理出事情发生的顺序的东西……这通常不是技术要求,而是调试辅助工具。我倾向于将 bigint 作为我的密钥类型,所以我几乎可以保证永远不会 运行 出 ID。如果您使用的是 int 键(或更小的键),则您希望每个 table.

使用一个序列

话虽如此,序列还是有一些需要注意的问题。例如,可以 "burn" 序列而不实际将它们放入数据中。同样,这可能是也可能不是问题。一般我没怎么关心过。

序列通常通过对 table 的 ID 列进行默认约束来实现。这意味着需要注意几件事。有可能该列的值实际上是在插入中提供的...这不是 'bump' 您的序列,并且可能与不提供值的未来插入冲突。对我来说,这是最重要的问题。如果默认提供所有 ID,则这是 non-issue.

程序(和远程客户端)可以保留或 "burn" 序列。这非常方便……让您的过程代码提前知道 ID 是什么,而不必将它们提交给数据。您可以随时执行以下操作:

insert someTempTable( Id, Name )
select
  next value for dbo.MySequence,
  Name
from
  dbo.SomeTable

...这会烧毁序列值,但好的是,当我将工作中的行 table someTempTable 插入 real table,ID不会冲突的,放心吧。这比使用 identity column-based ID 更简单。我可以在临时中构建一系列相关数据,然后将它们全部移动到持久存储中 set-wise。这通常会更有效率。

最好的答案是让你回到你的情况。

首先,许多人更喜欢序列,因为它们易于生成并提供单一数据类型来导航您的联接。此外,许多商店需要单列主键来帮助进一步降低代码复杂性。

让我们谈谈缺点:

序列: 当使用 b-tree 索引时,序列通常按升序插入,这会导致 "unbalanced tree" 并随着时间的推移导致性能不尽如人意(在 b-tree 索引上)。有时,人们反而会生成散列或 GUID 以生成更平衡的树。

使用 "lookup tables" 时,序列可能会产生 "hard to read" 代码,尤其是当值在数据库中进行硬编码时。示例:"where status_seq=1" 比 "where status_id='ACTIVE'".

更难阅读

使用 ID 的缺点: 混合数据类型会导致混淆。有时它们是数字,有时它们是 varchar 或 char。许多 ORM 可能会混淆这些并遗漏前导零,从而导致结果错误。 IE 01234 != 1234,但您的 ORM 可能 return 1234 而不是 01234.

许多人以人类可读的形式存储 ID,例如 "VALID" 或州缩写。这可能会导致长 运行 令人头疼,因此即使您在 table 上使用 ID,您也可能希望避免直接向客户显示这些 ID。

ID 字段在将来 "need to change" 比序列更有可能。示例:假设您有一个国家/地区代码 table,一场革命发生了,国家/地区代码发生​​了变化。您真的要遍历主 table 和引用它的所有外键,输入新的国家代码——还是使用旧的国家代码,因为这是您的选择。如果在这种情况下使用序列,则只需更新基础 table 中的其他 non-key 列就可以了。

好处:

序列的好处: 序列本质上是自动生成的。 ID并不总是。添加记录时,您真的希望程序员或用户命名一个不能轻易更改的 ID 吗?当您使用序列时,很少需要重新编号,如果出错,可以轻松更改基础 human-readable 数据。

如上所述,它们始终是数字数据类型,如果使用得当,可以帮助 "navigating" 您的应用程序(IE,通常只需要 "pass around" 一个数字来导航您的 table 结构)

在数据库和您的编程语言之间使用通信时,您可以指望能够将整数转换为整数,而不会出现任何奇怪的数据转换问题。

ID: 主要好处是代码更易于阅读,我们已经在上面解释过了。

总而言之,我认为这是个案,具体取决于 table 和列的使用情况。如果您打算使用 ID,请避免向用户显示值的诱惑。如果 table 不会更改并且只是保存标志或 "enum" 类型数据,那么 ID 肯定有助于提高代码的可读性。否则,序列通常是数据可维护性的更好选择。

有些人选择 GUID 或 ID 来帮助提高索引性能,但就个人而言,如果代码可读性有任何损失或代码变得更复杂,我会在编写更复杂的代码之前花一些钱购买更好的硬件代码--因为好处微乎其微。

资料来源:Oracle 认证的 DBA(关于这个确切主题的培训),以及 20 多年与开发人员和企业数据库打交道的经验。

您的问题是关于使用序列与身份(大概是 "generated always as identity" 列)。在 Postgres 中,这些将被声明为 serial。这些总是单列中的某种数字。

从数据库性能的角度来看,两者相差不大。一个重要的区别是一些数据库将缓存标识列,这可以加快插入速度但会导致间隙。缓存序列的规则可能不同。在高事务环境中,缓存不足可能成为性能瓶颈。在多个 table 之间共享一个序列会使这个问题变得更糟。

从数据管理的角度来看,差异更大。一个序列需要管理两个对象(table 和序列)。 identityserial 列内置于 table.

对于单个 table,我只考虑过在不支持 built-in auto-increment/serial/identity 列的数据库中使用序列(咳咳,"Oracle")。否则,我会使用设计用于 tables.

的机制

我确实想指出使用 auto-incremented 代理键还有其他好处。如果数据库中存在这样的概念,这也应该是用于聚类数据的键。新的插入总是在 "end" 处(尽管如果您要删除数据,那么页面可能只被部分使用)。主键也应该是用于外键引用的唯一键,即使其他列 - 单独或一起 - 是唯一的和候选主键。

我没有使用过序列,但我可以讨论身份字段。

首先,在我过去 18 年使用 SQL Server 的每个案例中,它们都能很好地工作。这在其他数据库上很可能是正确的,而且这对于使用它们的数据库来说是一个关键特性。我们在使用身份方面从未遇到过任何问题。如果您希望拥有一个非常大的数据库,您可能希望在设置时将标识定义为 big int。

如果您在创建 table 时没有设置身份,稍后在 SQl 服务器中设置它会很麻烦,请检查您的数据库以获取详细信息。但是,如果您将自动生成的密钥专门用作 PK,则可以在创建 table 时执行此操作。

使用身份(或序列或 GUID)时的一个关键问题是,除了自动生成的值外,您还需要为 [=37= 中的自然键创建一个唯一索引] 如果你有的话。这将防止数据完整性问题。

如果您在回滚时遇到数字被跳过的问题,则可能会出现其他问题。由于这些是占位符,它们不应该有任何意义,所以这可能不是问题,但我看到过人们出于业务原因而不是技术原因需要此功能的情况。使用回滚测试两者,看看是否有间隙,如果你需要它们没有间隙。如果两者都有差距,那么您将需要推出自己的系统,注意竞争条件。

既然你说你在 DB2 中创建一个数据库以迁移到 Postgres,我会用几个 tables 与 db2 中的身份和几个 tables 设置一个测试与序列。向其中插入大量虚假数据。然后我会测试将它们移植到 Postgres 数据库并开始添加记录有多困难。这可能是一个关键数据,在您的特定情况下,哪种方法更好。

您还可以考虑通过将大量记录插入两个测试 table 来进行有关性能的测试,这两个测试 table 是相似的,只是它们分配 Id 的方式不同。可能是性能是acceptable 两种方式,可能是一种比另一种更快。以下 link 适用于 SQL 服务器,但测试方法可能对您做出决定很有用。 http://dba-presents.com/index.php/sql-server/25-identity-vs-sequence-performance-test

如果这是一个关键问题,那么您自己决定性能等事情是至关重要的,因为结果可能会受到您自己的特定 set-up 的影响。

如果您想要一个基于某些文本值和递增数字(例如 CA1、CA2、CA3、TX1、TX2、TX3)的有意义的 ID,那么身份将不起作用,但我认为可以使用序列(请参阅本文:PostgreSQL sequence based on another column)。所以序列可以给你更多的灵活性,但如果你不需要它,那又何必呢?

可能我还会认为,有时使用一个有时使用另一个对于维护(以及您的情况下的转换)来说是最令人困惑的。做事方式的一致性可能是关键。如果您遇到一种情况,其中序列为您提供了您必须拥有的灵活性,而身份却没有。我会在整个过程中使用序列,只是为了避免在进行转换时知道哪个 table 使用了什么的不必要的复杂性。

Db2 IDENTITY 列由序列支持(支持缓存和乱序生成以获得更高的性能)——区别纯粹是语法上的。带有标识列:

create table t1 (
  id integer not null generated always as identity cache 100,
  foobar varchar(111)
)

提供该列的值,它是自动生成和插入的:

insert into blah (foobar) values ('something')

如果列未定义为 IDENTITY 您必须明确创建序列并在插入行时生成值

create table t2 (
  id integer not null,
  foobar varchar(111)
)

create sequence myseq cache 100

insert into t2 values (next value for myseq, 'something else')

同样,当您需要重新定义身份属性时,您可以通过 ALTER TABLE 语句进行;对于使用 ALTER SEQUENCE.

的序列

一个table中只有一列可以定义为IDENTITY;如果您需要多个这样的列,则必须为它们使用序列。

当使用 LOADIMPORT 实用程序将 mass-loading 数据放入具有标识列的 table 时,需要特殊处理——您将需要覆盖或忽略可能存在的标识值。