3

在一个数据库中,我看到了许多Primary-Key(PK)isAUTO_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,我需要保留ChidNoForeign Key (FK). 那么它会很复杂。

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

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

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

因为我的想法是反对使用AUTO_INCREMENT,请建议我也可以使用保持AUTO_INCREMENT字段?

4

2 回答 2

3

自增 PK 列可以称为代理键

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

  • 如果表中没有其他列集可以可靠地被视为候选键。对于您的示例,可能无法保证 (name, age, address) 的组合在所有情况下都能唯一标识行。似乎不太可能有两个同名、同龄、住在同一个地址的人。但这仍然不是无效的。在这种情况下,使用代理键可以使所有其他列不唯一。
  • 可能希望 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 中它是@@IDENTITYor SCOPE_IDENTITY()。在 MySQL 中,它是LAST_INSERT_ID(). 等等。

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

于 2012-12-09T18:47:00.800 回答
2

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

auto_increment 或标识的最佳情况是,在很多情况下,它比自然键更有效且更易于使用,其中自然键是宽列或多列自然键。

让我们看下表。

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 进行唯一约束。老实说,虽然我也很可能创建了一个数据库表,并且表对 database_id 和 table_nme 有唯一的约束,但你明白了。如果为唯一索引选择了正确的列,则唯一性永远不会成为问题。

在大多数语言中,获取先前插入的身份或 auto_incremenet 的值也很简单。

select @@IDENTITY
or
select LAST_INSERT_ID()

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

于 2012-12-09T15:29:40.873 回答