主键还是唯一索引?

151
在工作中,我们有一个大型的数据库,使用唯一索引而不是主键,一切都运作正常。
我正在为一个新项目设计新的数据库,并且我面临一个两难境地:
在数据库理论中,主键是基础元素,这没问题,但在实际项目中,两者各自有什么优缺点?
你在项目中使用哪种方式?
编辑:在 MS SQL 服务器上进行复制时,主键又有什么影响?

2
这里讨论了一些额外的注意事项(尽管附加了覆盖索引的上下文)-http://dba.stackexchange.com/questions/21554/implications-of-using-a-unique-nonclustered-index-with-covering-columns-instead - StuartLC
注意:SQLite与常见标准不同,允许主键为空,这是由于遗留问题所致。https://www.sqlite.org/lang_createtable.html - bitinn
15个回答

189

什么是唯一索引?

在一个列上创建的唯一索引除了创建该列的索引外,还强制要求此列在不同行中不能有两个相等的值。例如:

CREATE TABLE table1 (foo int, bar int);
CREATE UNIQUE INDEX ux_table1_foo ON table1(foo);  -- 在foo列上创建唯一索引。
INSERT INTO table1 (foo, bar) VALUES (1, 2); -- 正确 INSERT INTO table1 (foo, bar) VALUES (2, 2); -- 正确 INSERT INTO table1 (foo, bar) VALUES (3, 1); -- 正确 INSERT INTO table1 (foo, bar) VALUES (1, 4); -- 失败!
Duplicate entry '1' for key 'ux_table1_foo'

最后一个插入操作失败,因为它违反了对列 foo 上的唯一索引,当试图第二次将值1插入此列时会产生冲突。

在MySQL中,唯一约束允许多个NULL值。

可以在多个列上创建唯一索引。

主键与唯一索引的区别

相同之处:

  • 主键意味着唯一索引。

不同之处:

  • 主键还意味着NOT NULL,但唯一索引可以为可空。
  • 只能有一个主键,但可以有多个唯一索引。
  • 如果没有定义聚集索引,则主键将成为聚集索引。

4
请注意,“唯一索引是对某一列的索引”这句话并不完全准确,因为一个唯一索引或主键可以包含多个列。 - Alex Jasmin
2
@Alexandre Jasmin:已修复,谢谢。有关多列的部分稍后会提到。 - Mark Byers
关于空值,ANSI标准允许在具有唯一约束条件的数据集中存在多个空值,这也是Oracle和PostgreSQL的实现方式。我认为SQL Server只允许一个空值。 - David Aldridge
4
我还是不明白,什么情况下应该使用主键(primary key),什么情况下应该使用唯一索引(unique index)?或者在某些情况下二者都可以使用?请为我解答。 - Amit

38

你可以这样理解:

主键是唯一的

唯一值不一定是元素的表示形式

什么意思?嗯,主键用于标识元素。如果你有一个“人”,你会想要有一个个人身份证号码(社会安全号码或类似的),它是你的“人”身份的主键。

另一方面,这个人可能有一个电子邮件地址是唯一的,但并不能确定这个人的身份。

我总是在关系表中使用主键(中间表/连接表),为什么呢?因为我喜欢在编码时遵循标准,如果“人”有一个标识符,汽车也有一个标识符,那么“人”->“汽车”也应该有一个标识符!


3
最好的是主键(person_id, car_id)。但我通常会创建一个新列,虽然它会增加一些开销,但我认为这样很好。你永远不知道以后是否需要关联到特定的关系。 - Filip Ekberg
1
代理主键对于复合/联接表的另一件事情是简化手动任务的维护。 - Robert C. Barth
2
只有当你打算有子表时才需要一个主键。如果该值不出现,也不用于任何目的,为什么要添加列和序列呢?这只是为了防止Access请求PK而进行的无谓工作。如果需要将记录标识为子对象,则创建主键;否则就是浪费。 - Mark Brady
3
如果与关系无关,它又与什么有关呢?你指着一个领域说,“那是主键”。然后呢?接下来会发生什么?如果没有自然的主键,我会添加一个列、序列和触发器,所有这些都是因为什么?有些东西只需要是主键。我避免没有原因的规则。 - Mark Brady
在将SSN作为主键之前,请考虑那些没有SSN的人。 - joym8
显示剩余2条评论

10

外键与唯一约束和主键一样相互配合。引用自Books Online:

一个FOREIGN KEY约束不必仅与另一张表的PRIMARY KEY约束相连;它也可以被定义为引用另一张表中的UNIQUE约束的列。

对于事务复制,您需要主键。引用自Books Online:

为事务复制发布的表必须有一个主键。如果一张表在事务复制出版物中,则不能禁用与主键列相关联的任何索引。这些索引是由复制所需的。要禁用索引,必须首先从出版物中删除该表。

两个答案都适用于SQL Server 2005。


“THAT scares the hell out of me (first quote). Why? I have a person table with an arbitrary ID that's my PK but I decide to add a UK to Phone, Email, & SSN... so now 4 different tables join to person on 4 different columns? I think I'd forgo any flexibility you might get for consistency.” - Mark Brady

7

何时使用代理主键而不是自然键的选择很棘手。类似“始终”或“从不”的答案很少有用。我发现这取决于情况。

举个例子,下面是我的一些表:

CREATE TABLE toll_booths (
    id            INTEGER       NOT NULL PRIMARY KEY,
    name          VARCHAR(255)  NOT NULL,
    ...
    UNIQUE(name)
)

CREATE TABLE cars (
    vin           VARCHAR(17)   NOT NULL PRIMARY KEY,
    license_plate VARCHAR(10)   NOT NULL,
    ...
    UNIQUE(license_plate)
)

CREATE TABLE drive_through (
    id            INTEGER       NOT NULL PRIMARY KEY,
    toll_booth_id INTEGER       NOT NULL REFERENCES toll_booths(id),
    vin           VARCHAR(17)   NOT NULL REFERENCES cars(vin),
    at            TIMESTAMP     DEFAULT CURRENT_TIMESTAMP NOT NULL,
    amount        NUMERIC(10,4) NOT NULL,
    ...
    UNIQUE(toll_booth_id, vin)
)

我们有两个实体表(toll_booths和cars)和一个事务表(drive_through)。toll_booth表使用替代键,因为它没有保证不会更改的自然属性(名称可以很容易地更改)。cars表使用自然主键,因为它具有非更改唯一标识符(vin)。drive_through事务表使用替代键进行简单识别,但还在插入记录时具有保证唯一性的属性上设置了唯一约束。此链接http://database-programmer.blogspot.com包含一些关于这个特定主题的很好的文章。

4
没有主键的缺点。
补充一下@MrWiggles和@Peter Parker的回答,如果表没有主键,则有些应用程序无法编辑数据(它们最终会说无法在没有主键的情况下编辑/删除数据)。Postgresql允许在UNIQUE列中有多个NULL值,PRIMARY KEY不允许NULL。此外,一些生成代码的ORM可能会遇到没有主键的表的一些问题。
更新:
据我所知,在MSSQL中不可能复制没有主键的表,至少不能没有问题(详情请参见详细信息)。

当插入新行或更新该列时,会产生开销。 - Mark Brady

3
如果某个东西是主键,根据你的数据库引擎,整个表格将按照主键排序。这意味着在主键上进行查找比其他任何类型的索引都要快得多,因为它不需要进行任何解引用操作。除此之外,这只是理论。

3
表格将会按照聚簇索引进行排序,不一定是按照主键排序。 - Ray Booysen
1
恰巧大多数人将主键设置为聚集索引。 - Ray Booysen
我们知道这通常是一个非常糟糕的想法,除非当然我们喜欢在我们的表中出现热点和不平衡的索引树... - Mike Woodhouse
1
这并不总是一个非常糟糕的想法。了解您的数据,了解您的关系型数据库管理系统,知道选择的含义。很少有选择总是好或坏。如果总是只有一种选择,那么数据库就会强制执行或禁止它。他们给你选择是因为“这取决于情况”。 - Mark Brady

2

相对于唯一索引,聚集索引存在一些缺点。

如前所述,聚集索引会在表中物理排序数据。

这意味着当你在一个包含聚集索引的表上进行大量插入或删除时,每次(好吧,几乎每次,取决于你的填充因子)更改数据时,物理表都需要更新以保持排序。

在相对较小的表中,这很好,但是当涉及到具有GB级别数据且插入/删除影响排序的表时,你将遇到问题。


那么有什么优势呢?排序查询更快吗?这对于大部分数据只写入一次(或很少写入)但经常查询的用例来说更好吗? - Buffalo

2
除了其他答案提到的内容,一些数据库和系统可能需要存在主键。我想到一个情况; 在使用Informix企业复制时,表必须有一个主键才能参与复制。

2
只要不允许值为空,它们应该被处理相同,但是在数据库中,空值NULL的处理方式不同(据我所知,MS-SQL不允许多个NULL值,而mySQL和Oracle允许这样做,如果一个列是唯一的)。因此,您必须将此列定义为NOT NULL UNIQUE INDEX。

1
MS-SQL允许在具有唯一索引的列中存在多个NULL值,这也应该是每个关系型数据库管理系统的标准。可以这样理解:NULL不是一个值,因此当您插入第二个NULL时,它永远不会匹配现有的NULL。表达式(NULL == NULL)不会评估为真或假,而是评估为NULL。 - gregmac
谢谢Gregmac,我不确定微软是否遵循这个。我记得一些微软的怪癖,不过那是几年前(2000年之前),可能是一个旧的Access数据库cough - Peter Parker

2
在关系数据理论中,不存在所谓的主键,因此你的问题必须从实际层面回答。
唯一索引不是SQL标准的一部分。DBMS的特定实现将决定声明唯一索引的后果。
在Oracle中,声明主键将导致系统自动创建一个唯一索引,所以这个问题几乎没有意义。我无法告诉你其他DBMS产品的情况。
我倾向于声明一个主键。这会禁止在关键列中使用NULL并禁止重复值。我还倾向于声明REFERENCES约束以强制执行实体完整性。在许多情况下,对于外键列声明索引将加速连接。这种类型的索引通常不应该是唯一的。

在MS SQL Server中,主键始终是唯一且非空的 - 例如,它实际上只是一个唯一索引,但具有不能为NULL的额外限制。 - marc_s
Oracle可以使用非唯一索引来强制执行唯一约束。如果MSSS不能这样做,我会感到惊讶。说“它实际上只是一个唯一索引”是不公平的。 - Mark Brady
在许多情况下,在外键的列上声明索引将加速连接。但在数据仓库环境中,如果可以使用哈希连接,则几乎总是不正确的。 - JAC2703
OP没有提到仓库。我不确定哈希腰在SQL Server上如何工作。有多少工作可以在仓库更新时完成。 - Walter Mitty

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接