保持AUTO_INCREMENT字段是不合适的设计?

时间:2012-12-09 14:36:56

标签: sql normalization rdbms database

在数据库中,我看到许多表Primary-Key(PK)AUTO_INCREMENT类型。

假设我创建了一个表Children,如下所示:

CREATE TABLE Children(
  childNo INTEGER AUTO_INCREMENT NOT NULL PRIMARY KEY,
  name    VARCHAR(25),
  age     INTEGER,
  address VARCHAR(100) 
)
  • ChildNoAUTO_INCREMENT,但是一旦我插入了一行,如何知道为某个孩子(名称)分配了哪个值? PK的错误选择。

  • 如果我搜索孩子的名字就会效率低下(并且不保证是唯一的)。出于这个原因,我认为将AUTO_INCREMENT作为主键表示弱模式设计?

  • 假设我有另一个表Parents,我需要将ChidNo保留为Foreign Key (FK)。那会很复杂。

  • 如果存在递归关联,那么保持PK为AUTO_INCREMENT将会非常糟糕。

保持关系中的自动增量字段表示规范化不合适?

在某些表中,为了引入额外的AUTO_INCREMENT字段,我希望将所有列保留为PK。我错了吗?

因为我的想法反对使用AUTO_INCREMENT,请建议我保留AUTO_INCREMENT字段的可用性?

2 个答案:

答案 0 :(得分:3)

自动增量PK列可以称为surrogate key

在某些情况下,使用代理键可能是一种有用的优化:

  • 如果表中没有其他列可以可靠地处理候选键。对于您的示例,可能无法保证(名称,年龄,地址)的组合可以保证在所有情况下唯一标识行。似乎不太可能会有两个同名,年龄相同,住在同一地址的人。但是,它仍然不是无效。在这种情况下,使用代理键可以使所有其他列不唯一。
  • 可能希望PK不变。例如,一个人可以改变他们的名字,但他们仍然是同一个人。当然,SQL允许更改PK值,但随后所有其他通过值引用PK的数据也必须更改。如果您的RDBMS支持使用ON UPDATE CASCADE的外键,则可以自动执行此操作。但是,如果您没有ON UPDATE CASCADE(例如Oracle),或者您没有外键(例如较旧的MySQL或SQLite),或者您有数据存储在RDBMS之外,该怎么办?使用代理键意味着任何“自然”数据列都可以自由更改值而不更改行的标识。代理键值是任意的,与自然数据无关,因此键永远不需要更改。
  • 即使存在可用作候选键的列,也可能需要使用大的列子集作为复合主键。密钥的存储变得笨重,当然比单个整数更庞大。因此,使用代理键在存储效率方面具有优势。
  • 操作多列PK还可以为开发人员提供更多编码工作,因为他们需要在JOIN和WHERE子句中编写更长的条件。此外,如果需求发生变化(名称,年龄,地址)不再是足够的PK,则需要向PK添加第四列,现在您必须更改所有应用程序中的所有SQL代码。

因此,代理键有合法的好处。

也就是说,代理键经常被过度使用。许多应用程序框架(例如Ruby on Rails)使用默认值,即每个表都有一个名为ID的整数代理键,无论它是否合适。您可以逐个表地指定PK列,但许多程序员将默认值作为规则,这导致他们有一些无意义的表设计。我见过的最糟糕的例子是,每个多对多表都有一个多余的ID列。

对于它的价值,使用代理键与规范化无关。也就是说,规范化规则既不鼓励也不鼓励使用代理键。

每个支持代理键的数据库还提供一个函数,该函数返回当前会话中最近生成的id值。正如@JStead所提到的,在SQL Server中它是@@IDENTITYSCOPE_IDENTITY()。在MySQL中,它是LAST_INSERT_ID()。等等。

这些函数只返回一个值,因此如果在单个INSERT语句中插入多行,则无法获取所有生成的id值。这是一个限制。

答案 1 :(得分:2)

我同意它已被过度使用,但确实在RDBMS世界中占有一席之地。你没有指定数据库,这更像是一个哲学辩论然后任何实现,所以我将在下面的例子中使用sql server。

auto_increment或identity的最佳情况是,在宽或多列自然键的很多情况下,它比自然键更有效,更容易使用。

让我们看下表。

CREATE TABLE TABLE_OBJECT (
 Table_ID int identity(1,1),
 Server_NME varchar(128),
 Database_NME varchar(128),
 Table_NME varchar(128)
)

CREATE TABLE COLUMN_OBJECT (
 Column_ID int identity(1,1),
 Table_ID int not null,
 Server_NME varchar(128),
 Database_NME varchar(128),
 Table_NME varchar(128),
 Column_NME varchar(128)
)

现在让我们说在这种情况下,我们想要将两个表连接在一起,我们没有身份。

select * from TABLE_OBJECT to
inner join COLUMN_OBJECT co on co.Server_NME = to.Server_NME
                          and co.Database_NME = to.Database_NME
                          and co.Table_NME = to.Table_NME

除此之外,写入它的效率也非常低,我必须读取6 *(128)字节来比较一行。

现在将其与以下简单性进行比较。

select * from TABLE_OBJECT to
inner join COLUMN_OBJECT co on co.Table_id = to.Table_ID

在上面的例子中,我只需要读取2 *(4)个字节来比较一行。当你有很多行时,这是一个巨大的差异。

然后也有普通存储方面。

CREATE TABLE COLUMN_OBJECT (
     Server_NME varchar(128),
     Database_NME varchar(128),
     Table_NME varchar(128),
     Column_NME varchar(128)
    )

CREATE TABLE COLUMN_OBJECT (
     Column_ID int identity(1,1),
     Table_ID int not null,
     Column_NME varchar(128)
    )

此处的存储是身份版本2 *(4)+ 128字节,而自然密钥版本为4 * 128字节。

还可以通过table_id和column_nme上的唯一约束来保证唯一性。然后在父表中对table_nme,database_nme,server_nme进行唯一约束。老实说虽然我本来可能已经创建了一个数据库表,但table对database_id和table_nme只有一个唯一的约束,但是你明白了。如果为唯一索引选择了正确的列,则唯一性永远不应成为问题。

在大多数语言中获取以前插入的标识或auto_incremenet的值也是微不足道的。

select @@IDENTITY
or
select LAST_INSERT_ID()

每种语言都有办法获得最后一种语言。