这是一个反模式:下一个主键存储在表中

时间:2014-03-17 15:59:34

标签: sql design-patterns documentation

我正在寻找一些关于为什么这是(或不是)糟糕设计的文档。

第三方vedor的代码使用单独的表来存储所有表的主键。

__KeyTable____
NextKey  TableName
45       Table1
1003     Table2
90       Table3

__Table1____
id  Column1  Column2
45  ...      ...

CREATE TABLE [dbo].[Table1](
    [id] [int] NOT NULL,        /* <---- IDENTITY(1,1) is not present */
    [Column1] [nvarchar](1) NULL,
    [Column2] [nvarchar](1) NULL
 CONSTRAINT [PK_WFDATASET] PRIMARY KEY CLUSTERED 
(
    [ID] ASC
)

因此,当在Table1中插入新记录时,将查询KeyTable,找到数字45,向其添加1(在不同服务器上的C#中)。该数字用于Table1的下一个insert语句,然后KeyTable的Table1记录更新为46。

这也阻止了使用级联删除的能力。相反,每个表都有大约3个触发器,这些触发器复制了级联删除的效果。这是一个丑陋的设计,我正在寻找一些我可以指出的官方文章来解释为什么这是一个糟糕的设计。

2 个答案:

答案 0 :(得分:2)

嗯,这是一个糟糕的设计,因为你需要在针对Table1的事务持续时间内在KeyTable中锁定一行。也就是说,在您运行交易时:

  1. 从KeyTable
  2. 中获取值
  3. 将增加的值存储在KeyTable
  4. 在Table1中插入新行
  5. 没有并发会话可以做同样的事情。这对可伸缩性来说是一个很大的损失,因为插入到Table1的所有会话都被强制排队并连续访问KeyTable。

    尽管任何良好实现的身份功能应该只需要非常简单地锁定身份分配器,从而提高吞吐量。这发生在记忆中。 IDENTITY缓存一组接下来的10个标识值,以及插入从此集合绘制的行的会话。

    使用IDENTITY也可以减少存储引擎级别锁定的争用,这在将KeyTable中的更改写回磁盘时是必需的。

答案 1 :(得分:1)

使用IDENTITY及其类似工作的副作用是可能会发生值序列中的间隙。这不是一个bug;它是设计的。

但有些应用要求您考虑您使用的每个ID号。考虑一下财务管理系统,您可能需要考虑从开始到结束的每个发票号或每个支票号。使用密钥表(如示例中的密钥表)是保证值序列中没有间隙的一种方法。

另一种可能性是,您的应用程序是从较旧的,更简单的系统开始的,该系统不是基于SQL dbms。 (所以没有像IDENTITY这样的东西。)

我能想到你的应用程序的唯一直接原因是允许删除行(这可能会引入间隙)而不使用ON DELETE CASCADE将是你的表没有外键。使用密钥表和使用ON DELETE CASCADE声明的外键之间没有技术冲突。 (试试吧。)

数据库设计者的一个经验法则是“不要用程序代码替换DDL”,这意味着当你只能将外键声明为ON DELETE CASCADE时,通常不会在每个表上写三个触发器。有罕见的例外;这可能不是其中之一。