【问题标题】:Asking for opinions : One sequence for all tables征求意见:所有表格的一个序列
【发布时间】:2010-12-04 22:07:31
【问题描述】:

这是我最近一直在考虑的另一个问题。 我们在前面的讨论中得出结论:“自然主键不好,人工主键好。” 之前使用 Hibernate 我已经看到 Hibernate 默认为所有表创建一个序列。起初我对此感到困惑,你为什么要这样做。但后来我看到了它的好处,它使连接父母和孩子变得万无一失。由于没有表具有相同的主键值,因此意外将父表与非子表链接不会产生任何结果。

有没有人认为这种方法有任何缺点。我只看到一个:您的数据库中的记录不能超过 999999999999999999999999999。

【问题讨论】:

  • “我们已经在之前的讨论中得出结论”——内部讨论还是关于 SO 的?
  • 关于 SO 的几次讨论。而“我们”的意思是“我”;-)
  • 自然键并不“坏”。从数据完整性的角度来看,它们是必不可少的。它们允许用户识别数据库中正在识别的真实世界事物。原则上,序列也可以是自然键。实际上,我更喜欢“业务密钥”一词,但它与“自然密钥”的含义相同 - 即用于识别数据库外部信息的密钥(例如由业务用户)。
  • @dportas - 自然键很有用,尽管在应用程序中使用自然键作为主键/@@Id 字段可能很糟糕,当您的应用程序是这些值的记录系统时这些值可能需要稍后更改。多年前,当我将 username 设置为 @@Id 列时,我很难学到这一点(好吧,当时没有注释)。这是一个清晰的自然键——具有商业意义的唯一记录标识符。唯一的问题是,如果您需要更改它并且有 10 个其他表使用它作为外键怎么办?

标签: oracle database-design


【解决方案1】:

所有代码从单个序列中获取值可能存在性能问题 - 请参阅 this Ask Tom thread

【讨论】:

  • 在我们的例子中,当我们显着增加序列的缓存时,单序列和多序列的性能是相似的。
  • 啊,您可能还想向下滚动该线程并查看 Mark Bobaks 先生的回复,这反映了我的经历
  • @Edwards 感谢您指出这一点,确实非常有趣。
  • “Oracle 序列的最大值实际上是 10^27”——理论上你可以创建几个序列并适当地错开起始值。请务必设置一个脚本,以便在您的应用运行非常非常长的时间时通知管理员。
【解决方案2】:

取决于序列在数据库中的实现方式,总是命中相同的序列可能更好或更糟。当只有少数或只有一个线程请求新值时,不会出现锁定问题。但糟糕的实现可能会导致拥塞。

另一个问题是回滚事务:序列不会回滚(因为其他人可能已经请求了更高的值),因此您可能会有很大的间隙,这会比您预期的更快地占用您的数字空间。 OTOH,吃掉 2 或 40 亿个 ID 需要一些时间(如果您“仅”使用 32 位(有符号)整数),因此在实践中这很少成为问题。

最后,如果需要的话,你不能轻易地重置序列。但是,如果您需要重新启动序列(例如,从午夜开始的记录数),您可以告诉 Hibernate 创建/使用第二个序列。

一个主要优点是您可以仅通过 ID 唯一地标识数据库中任何位置的对象。这意味着你可以严重削减你在生产系统中写入的日志信息,如果你只有 ID,仍然可以找到一些东西。

【讨论】:

  • Ref: "2 或 40 亿个 ID(如果使用 32 位(有符号)整数)",Oracle 序列的最大值实际上是 10^27,Oracle NUMBER 类型将存储整数高达 38 位精度。应该让我们坚持一段时间。
  • 我的意思是“即使你只使用 32 位整数”作为 ID。今天的大多数人将使用 64 位(LONG 或 BIGNUM)或更多甚至 UUID。
  • 确实如此。我的评论实际上是要指出 Oracle 数字数据类型与其他系统有很大不同。此处参考文档:download.oracle.com/docs/cd/B19306_01/server.102/b14200/…
  • 如果您使用 NUMBER,数字用完应该不是问题。以每秒一万亿笔交易的速度运行,您可以使用 3160 万年。
【解决方案3】:

我更喜欢每张桌子有一个序列。这来自一个普遍的观察:一些表(“主表”)的行数相对较少,必须“永远”保存。例如,ERP 中的客户表。

在其他表(“事务表”)中,许多行是永久生成的,但一段时间后,这些行可以被归档(或简单地删除)。最极端的例子是用于调试目的的跟踪表;它可能以每秒数百行的速度增长,但每一行在几天后就会过时。

主表中的小 ID 使直接在数据库上工作时更容易,例如用于调试目的。

select * from orders where customerid=415

select * from orders where customerid=89461836571

但这只是一个小问题。更大的问题是骑自行车。如果对所有表使用一个序列,则根本无法让它重新启动。每个表一个序列,当您归档或删除旧数据时,您可以重新启动事务表的序列。主表几乎没有这个问题,因为它们的增长速度要慢得多。

我认为所有表只有一个序列没有什么价值。到目前为止所说的论点并不能说服我。

【讨论】:

    【解决方案4】:

    使用单个序列有几个缺点:-

    • 减少并发。分发下一个序列值涉及同步。在实践中,我认为这可能不是一个大问题
    • Oracle 在维护 btree 索引时有特殊的代码来检测单调递增的值并适当地平衡树
    • 如果填写了大多数值,CBO 可能会更好地估计索引上的范围查询(如果您曾经这样做过)

    一个优点可能是您可以确定不同表之间的插入顺序。

    【讨论】:

      【解决方案5】:

      当然,一个序列与每个表一个序列的方法各有利弊。就我个人而言,我发现能够为一行分配一个真正唯一的标识符,使每个 id 列成为一个 uuid,足以胜过任何缺点。正如 Aaron D. 简洁地写道:

      您可以仅通过 ID 唯一标识数据库中任何位置的对象

      而且,对于大多数应用程序而言,由于 Hibernate3 批处理 IMPORT 语句的方式,这不会成为性能瓶颈,除非大量记录争夺相同的数据库资源(SELECT hibernate_sequence.nextval FROM dual)。

      此外,最新版本 (1.2) 的 Grails 不支持此序列映射。尽管 Grails 1.1 (!) 支持它。它现在需要子类化 Hibernate 方言类之一作为解决方法。

      对于使用 Grails/GORM 的用户,请查看以下 JIRA 条目:

      Oracle Sequence mappings ignored

      【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2010-12-16
      • 1970-01-01
      • 2013-02-25
      • 2010-10-30
      • 1970-01-01
      • 2018-11-01
      • 1970-01-01
      相关资源
      最近更新 更多