主键还是唯一索引?


127

在工作中,我们有一个具有唯一索引而不是主键的大型数据库,并且一切正常。

我正在为一个新项目设计新的数据库,但有一个难题:

在数据库理论中,主键是基本元素,没关系,但是在REAL项目中,两者的优点和缺点是什么?

您在项目中使用什么?

编辑: ...以及MS SQL服务器上的主键和复制呢?


2
这里有讨论的一些额外的考虑(虽然具有覆盖索引的附加上下文) - dba.stackexchange.com/questions/21554/...
StuartLC

注意:SQLite的不同之处在于,由于遗留问题,它们确实允许将主键设置为空(与通用标准相对)。sqlite.org/lang_createtable.html
bitinn '18 October

Answers:


168

什么是唯一索引?

列上的唯一索引是该列上的索引,该索引还强制执行以下约束:在该列中不能在两个不同的行中具有两个相等的值。例:

创建表table1(foo int,bar int);
创建唯一索引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); -失败!

键“ ux_table1_foo”的条目“ 1”重复

最后一次插入失败,因为foo在第二次尝试将值1插入此列时,它违反了列上的唯一索引。

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

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

主键与唯一索引

相同的东西:

  • 主键意味着唯一索引。

不同之处:

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

4
请注意,唯一索引是一列的索引并不完全准确,因为一个唯一索引或主键可以包含多个列。
Alex Jasmin'5

2
@Alexandre Jasmin:谢谢。关于多列的部分将在后面提到。
Mark Byers 2010年

关于空值,ansi标准允许数据集中具有唯一约束的多个空值,这也是在Oracle和PostgreSQL上的实现。我相信SQL Server虽然只允许一个空值。
David Aldridge

3
但还是我没有得到它,例如何时使用主键或何时使用唯一索引?或者在相同情况下可能两者都处于相同状态。
阿米特(Amit)

33

您可以这样看:

主键是唯一的

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

含义?; 好吧,主键用来标识元素,如果您有一个“ Person”,您希望拥有一个对您的Person来说是主要的Personal Identification Number(SSN等)。

另一方面,此人可能会收到一封唯一的电子邮件,但却无法识别该人。

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


在关系表中:您是说要使用人工主键(例如整数)引入新列,还是使用组合主键(person_id,car_id)?

3
主键(person_id,car_id)将是最好的。但是我通常会创建一个新列,请确保它会带来一些开销,但是我认为它很好。您永远不会知道是否要在以后的场景中与特定关系建立联系。
Filip Ekberg

1
代理主键对您的组合/联接表所做的另一件事是简化了手动任务的维护。
罗伯特·巴特

2
如果您要生子,则只需要一个主键。如果该值无处显示,并且什么都不用,为什么还要添加一列和一个序列?这是为了阻止Access要求PK而进行的工作。如果需要识别儿童记录,请进行PK,否则很浪费。

3
如果它与关系无关,那么它又有什么关系呢?您指向一个字段,然后说,这是主要的。和?那会怎样 如果没有自然的pk,我添加一列,一个序列和一个触发器,都是因为____?有些只是主要的。我无缘无故地规避规则。

10

外键和主键都具有唯一约束。从在线书籍:

一个FOREIGN KEY约束不必仅链接到另一个表中的PRIMARY KEY约束。也可以定义它引用另一个表中UNIQUE约束的列

对于事务复制,您需要主键。从在线书籍:

发布用于事务复制的表必须具有主键。如果表在事务复制发布中,则不能禁用与主键列关联的任何索引。这些索引是复制所必需的。若要禁用索引,必须首先从发布中删除表。

这两个答案都是针对SQL Server 2005的。


那吓到我了(第一句话)。为什么?我有一个具有任意ID的人员表,这是我的PK,但我决定将UK添加到Phone,Email和SSN ...,所以现在4个不同的表在4个不同的列上加入了person?我想我会为保持一致性而放弃任何灵活性。

5

选择何时使用代理主键而不是自然键是很棘手的。诸如总是或永不这样的答案很少有用。我发现这取决于情况。

例如,我有以下表格:

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_boothscars)和一个事务表(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中没有主键就不可能复制表,至少没有问题(详细信息)。


插入新行或更新该列时会产生开销。

3

如果某件事是主键,则取决于您的数据库引擎,整个表将按主键排序。这意味着在主键上的查找要快得多,因为它不需要进行任何取消引用,因为它与任何其他类型的索引都必须进行。除此之外,这只是理论上的。


3
该表将按聚簇索引排序,而不必按主键排序。
2009年

1
碰巧的是,大多数人将其主键设置为聚簇索引。
Ray Booysen 09年

当然,除非我们喜欢表中的热点和不平衡的索引树,否则我们通常知道这是一个非常糟糕的主意……
Mike Woodhouse,2009年

1
这并非总是一个坏主意。了解您的数据,了解您的RDBMS,了解选择的含义。选择很少总是好事或坏事。如果总是“ 1”,则数据库将强制它或不允许它。他们为您提供选择,因为“取决于情况”。

2

除了其他答案所说的以外,某些数据库和系统可能还需要提供主数据库。我想到一种情况。当将企业复制与Informix一起使用时,必须存在一个PK,表才能参与复制。


2

只要您不允许使用NULL值,就应该对它们进行相同的处理,但是在数据库上对NULL值的处理方式有所不同(AFAIK MS-SQL不允许使用多个(1)NULL值,mySQL和Oracle允许这样做,如果列是UNIQUE),则必须定义此列NOT NULL UNIQUE INDEX


1
与每个RDBMS一样,MS-SQL的确在具有唯一索引的列中允许多个NULL值。这样想:NULL不是值,因此当您插入第二个NULL时,它将永远不会与现有的NULL匹配。表达式(NULL == NULL)不等于true或false,其计算结果为NULL。
gregmac,

thanx gregmac,我不确定,如果MS遵循此规定。我想起了一些与此有关的MS Quirks,但是几年前(2000年之前),也可能是旧的Access-DB 咳嗽
Peter Parker

2

关系数据理论中没有主键之类的东西,因此您的问题必须在实践层面上得到解答。

唯一索引不是SQL标准的一部分。DBMS的特定实现将确定声明唯一索引的后果。

在Oracle中,声明主键将导致代表您创建唯一索引,因此问题几乎没有意义。我不能告诉您其他DBMS产品。

我赞成声明主键。这具有禁止键列中的NULL以及禁止重复项的效果。我也赞成声明REFERENCES约束以强制实体完整性。在许多情况下,在外键的同伴上声明索引将加快连接速度。这种索引通常不应唯一。


MS SQL Server中的主键始终都是UNIQUE且不是NULL-例如,它实际上只是一个唯一索引,但附加的限制是它不能为NULL。
marc_s

Oracle可以使用非唯一索引来强制执行唯一约束。如果MSSS无法做到,我会感到惊讶。说“它实际上只是一个唯一索引”是一种损害。

“在许多情况下,在外键的同伴上声明索引将加快连接速度。” 在数据仓库世界中,这几乎总是不正确的,在这种情况下,如果可能的话,首选哈希联接。
JAC2703 '18年

OP没有提到仓库。我不确定哈希腰在SQL Server上如何工作。在仓库更新时可以完成多少工作。
Walter Mitty

2

聚集索引与唯一索引相比有一些缺点。

如前所述,CLUSTERED INDEX对表中的数据进行物理排序。

这意味着,如果在包含聚簇索引的表上有大量插入或删除操作,那么每次更改数据时(实际上,几乎取决于填充因子),都需要更新物理表以保持排序。

在相对较小的表中,这很好,但是当进入具有GB数据值的表,并且插入器/删除器影响排序时,就会遇到问题。


那有什么好处呢?排序查询更快?当您一次(或很少)写入大部分数据并一直进行查询时,这对用例是否更好?
布法罗

1

我几乎从来没有创建没有数字主键的表。如果还有一个自然键应该是唯一的,我还要在其上放一个唯一的索引。整数上的联接比多列自然键更快,数据只需要在一个地方更改(自然键往往需要更新,这在主键-外键关系中是一件坏事)。如果您需要复制,请使用GUID而不是整数,但是在大多数情况下,我更喜欢用户可读的键,尤其是当他们需要看到它以区分John Smith和John Smith时。

几次不创建代理键是当我有一个涉及多对多关系的联接表时。在这种情况下,我将两个字段都声明为主键。


“我几乎从不创建没有数字主键的表”:为什么总是数字?主键不必是数字(顺便说一句,也不必是AUTO_INCREMENT)。
Hibou57

@ Hinou57,因为我发现自然键实际上很少是唯一的,并且它们几乎总是可变的。通常,在整数上的进阶连接比在varcahrr自然键或更差的复合键上的连接要快得多。我不会在大多数时候使用它们。这可能会因您存储在数据库中的信息类型而异,但是根据我的个人经验,我发现自然键随着时间的推移极其不稳定。
HLGEM

感谢您的答复HLGEM。不可靠意味着什么?性能?(我希望这不是数据完整性方面的可靠性问题)。我对您的话感到有些惊讶,因为尽管我使用整数键或更短的VARCHAR之类的更自然的键,但即使使用最简单的数据库引擎在各处都使用了哈希,这可能会产生很小的区别。
Hibou57

在很多情况下,它们是不可靠的,因为即使它们本来就不是唯一可靠的,它们也不可靠。它们是不可靠的,因为它们会更改,并且会影响uopdate中的数百万条记录。这是我的经验,曾经从数百种存储有关许多不同类型信息的数据的数据库中查看并管理或查询数据,或从中导入数据。
HLGEM

1

我的理解是,主键和具有非null约束的唯一索引是相同的(*);我想根据规范明确声明或暗示的内容(取决于您要表达和明确执行的内容)选择一个或另一个。如果它要求唯一性且不为null,则将其设为主键。如果发生这种情况,唯一索引的所有部分都不为空,并且没有任何要求,那么就使其成为唯一索引。

唯一的区别是,您可能有多个非空的唯一索引,而您没有多个主键。

(*)除了实际差异外,主键可以是某些操作(例如定义外键)的默认唯一键。例如 如果定义了引用表的外键而不提供列名,则如果引用的表具有主键,则主键将是被引用的列。否则,被引用的列将必须显式命名。

这里的其他人提到数据库复制,但是我不知道。


0

唯一索引可以具有一个NULL值。它创建非聚集索引。主键不能包含NULL值。它创建聚集索引。


0

在MSSQL中,主键应单调增加以在聚集索引上获得最佳性能。因此,带有标识插入的整数比可能不会单调增加的任何自然键更好。


-1

如果由我决定...

您需要满足数据库和应用程序的要求。

在每个表中添加一个自动递增的整数或long id列作为主键,可以满足数据库的需求。

然后,您将至少一个其他唯一索引添加到表中,以供您的应用程序使用。这将是employee_id,account_id或customer_id等的索引。如果可能,此索引不应为复合索引。

与综合索引相比,我更倾向于在多个字段上单独使用索引。只要where子句包含这些字段,数据库将使用单个字段索引,但是只有在您以完全正确的顺序提供字段时,数据库才会使用复合字段-这意味着除非您提供以下信息,否则数据库将无法使用复合索引中的第二个字段where子句中的第一和第二。

我全都使用计算索引或函数类型索引-并建议在复合索引上使用它们。通过在where子句中使用相同的函数,可以非常轻松地使用函数索引。

这样可以满足您的应用程序要求。

很有可能其他非主索引实际上是该索引键值到主键值而不是rowid()的映射。这允许进行物理排序操作和删除操作,而不必重新创建这些索引。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.