为什么主键值会改变?


18

我最近一直在研究ROWGUID的概念,并遇到了这个问题。 这个答案给出了真知灼见,但由于提到更改主键值,使我陷入了另一个困境。

我一直认为主键应该是不变的,而且自阅读此答案以来,我的搜索仅提供了与最佳实践相同的答案。

创建记录后,在什么情况下需要更改主键值?


7
当选择一个不可变的主键时?
ypercubeᵀᴹ

2
到目前为止,以下所有答案均属次要问题。除非主键也恰好是聚簇索引,否则更改主键的值并不重要。仅当聚集索引的值更改时才真正重要。
肯尼斯·费舍尔

6
@KennethFisher,或者如果它被另一张或同一张表中的一个(或多个)FK引用,并且更改必须层叠到许多行(可能数百万或数十亿行)。
ypercubeᵀᴹ

9
询问Skype。几年前注册时,我输入的用户名不正确(姓氏中没有字母)。我尝试过多次以纠正它,但他们无法更改它,因为它用于主键,并且他们不支持更改它。在这种情况下,客户希望更改主键,但是Skype并没有提供支持。如果愿意,他们可以支持这种更改(或者可以创建更好的设计),但是目前没有任何允许的更改。因此我的用户名仍然不正确。
亚伦·伯特兰

3
所有现实世界的值都可能发生变化(由于多种原因)。这是替代键/合成键的最初动机之一:能够生成可以依靠的永不改变的人工值。
RBarryYoung

Answers:


24

如果您使用某人的名字作为主键,并且他们的名字已更改,则需要更改主键。这是ON UPDATE CASCADE用来因为它基本上是级联的变化到有主键外键关系的所有相关的表。

例如:

USE tempdb;
GO

CREATE TABLE dbo.People
(
    PersonKey VARCHAR(200) NOT NULL
        CONSTRAINT PK_People
        PRIMARY KEY CLUSTERED
    , BirthDate DATE NULL
) ON [PRIMARY];

CREATE TABLE dbo.PeopleAKA
(
    PersonAKAKey VARCHAR(200) NOT NULL
        CONSTRAINT PK_PeopleAKA
        PRIMARY KEY CLUSTERED
    , PersonKey VARCHAR(200) NOT NULL
        CONSTRAINT FK_PeopleAKA_People
        FOREIGN KEY REFERENCES dbo.People(PersonKey)
        ON UPDATE CASCADE
) ON [PRIMARY];

INSERT INTO dbo.People(PersonKey, BirthDate)
VALUES ('Joe Black', '1776-01-01');

INSERT INTO dbo.PeopleAKA(PersonAKAKey, PersonKey)
VALUES ('Death', 'Joe Black');

一个SELECT对两个表:

SELECT *
FROM dbo.People p
    INNER JOIN dbo.PeopleAKA pa ON p.PersonKey = pa.PersonKey;

返回值:

在此处输入图片说明

如果我们更新该PersonKey列,然后重新运行SELECT

UPDATE dbo.People
SET PersonKey = 'Mr Joe Black'
WHERE PersonKey = 'Joe Black';

SELECT *
FROM dbo.People p
    INNER JOIN dbo.PeopleAKA pa ON p.PersonKey = pa.PersonKey;

我们看:

在此处输入图片说明

查看上述UPDATE语句的计划,我们清楚地看到,通过定义为ON UPDATE CASCADE

在此处输入图片说明 单击上面的图像以更清晰地查看它

最后,我们将清理临时表:

DROP TABLE dbo.PeopleAKA;
DROP TABLE dbo.People;

首选1周的方式做到这一点使用代理键是:

USE tempdb;
GO

CREATE TABLE dbo.People
(
    PersonID INT NOT NULL IDENTITY(1,1)
        CONSTRAINT PK_People
        PRIMARY KEY CLUSTERED
    , PersonName VARCHAR(200) NOT NULL
    , BirthDate DATE NULL
) ON [PRIMARY];

CREATE TABLE dbo.PeopleAKA
(
    PersonAKAID INT NOT NULL IDENTITY(1,1)
        CONSTRAINT PK_PeopleAKA
        PRIMARY KEY CLUSTERED
    , PersonAKAName VARCHAR(200) NOT NULL
    , PersonID INT NOT NULL
        CONSTRAINT FK_PeopleAKA_People
        FOREIGN KEY REFERENCES dbo.People(PersonID)
        ON UPDATE CASCADE
) ON [PRIMARY];

INSERT INTO dbo.People(PersonName, BirthDate)
VALUES ('Joe Black', '1776-01-01');

INSERT INTO dbo.PeopleAKA(PersonID, PersonAKAName)
VALUES (1, 'Death');

SELECT *
FROM dbo.People p
    INNER JOIN dbo.PeopleAKA pa ON p.PersonID = pa.PersonID;

UPDATE dbo.People
SET PersonName = 'Mr Joe Black'
WHERE PersonID = 1;

为了完整起见,update语句的计划非常简单,并且显示了替代密钥的一个优点,即在自然键方案中,只需要更新一行而不是包含密钥的每一行

在此处输入图片说明

SELECT *
FROM dbo.People p
    INNER JOIN dbo.PeopleAKA pa ON p.PersonID = pa.PersonID;

DROP TABLE dbo.PeopleAKA;
DROP TABLE dbo.People;

SELECT上面两个语句的输出是:

在此处输入图片说明

本质上,结果大致相同。一个主要区别是,不会在出现外键的每个表中重复使用宽自然键。在我的示例中,我使用一VARCHAR(200)列来保存此人的姓名,因此必须使用VARCHAR(200) Anywhere。如果有很多行和很多包含外键的表,那将增加大量的内存浪费。请注意,我并不是在谈论磁盘空间的浪费,因为大多数人都说磁盘空间是如此廉价以至于基本上是可用的。但是,内存价格昂贵,值得珍惜。当您考虑名称的平均长度约为15个字符时,将4字节整数用作键将节省大量内存。

约的问题是如何为什么键可以改变就是为什么超过代理键选择自然键,这是一个有趣的也许是更重要的问题的问题,特别是在性能是一个设计目标。在这里看到我的问题。


1- http://weblogs.sqlteam.com/mladenp/archive/2009/10/06/Why-I-prefer-surrogate-keys-instead-of-natural-keys-in.aspx


3
为了避免CASCADE(在某些情况下会出现问题),您还可以使FK列为可空,因此,如果需要更改PK,则可以将相关行更新为NULL(成块,如果有很多行或按表) ,如果有很多表,或者两者都有),则更改PK值,然后再次更改FK。
亚伦·伯特兰

8

虽然您可以使用自然的和/或可变的密钥作为PK,但是根据我的经验,这会导致问题,而使用满足以下条件的PK通常可以防止出现问题:

 Guaranteed Unique, Always Exists, Immutable, and Concise.

例如,美国许多公司尝试在其系统中将社会安全号码用作个人ID号(和PK)。然后,他们遇到了以下问题-数据输入错误导致必须修复多条记录,没有SSN的人,政府更改了SSN的人,具有重复SSN的人。

我已经看到了这些情况中的每一个。我还看到过一些公司,他们不希望他们的客户“只是个数字”,这意味着他们的PK最终变成了“第一+中间+最后+ DOB + zip”或类似的废话。尽管他们确实添加了足够的字段来几乎保证唯一性,但是他们的查询却令人生畏,更新这些字段中的任何一个都意味着要解决数据一致性问题。

以我的经验,数据库本身生成的PK几乎总是一个更好的解决方案。

我推荐这篇文章以获取更多指针:http : //www.agiledata.org/essays/keys.html


6
答案中引用了Scott Ambler文章的一条很好的建议:“有些人会告诉您应该始终使用自然键,而另一些人会告诉您应该始终使用代理键。这些人总是被证明是错误的,通常他们所做的不过是与您共享“数据宗教”的偏见而已。现实情况是,自然键和代理键各有其优缺点,并且没有一种适合所有情况的策略。
nvogel

7

涉及同步时,可以更改主键。当您的客户端断开连接并且它以一定的时间间隔将数据与服务器同步时,可能就是这种情况。

几年前,我在一个系统上工作,该系统上本地计算机上的所有事件数据都有负的行ID,例如-1,-2等。当数据同步到服务器时,服务器上的行ID应用于客户。假设服务器上的下一行ID是58。然后-1变为58 -2 59依此类推。该行ID更改将级联到本地计算机上的所有子FK记录。该机制还用于确定先前同步了哪些记录。

我并不是说这是一个好的设计,但这是主键随时间变化的一个例子。


5

任何涉及更改 PRIMARY KEY定期带来灾难。更改它的唯一好理由是将两个以前分开的数据库合并在一起。

正如@MaxVernon指出的那样ON UPDATE CASCADE,尽管当今大多数系统都使用ID作为替代,但是偶尔可能会发生更改,然后再使用PRIMARY KEY

乔·塞尔科Joe Celko)法比安·帕斯卡Fabian Pascal)(一个值得关注的网站)之类的纯粹主义者不同意使用代理密钥,但我认为他们输掉了这场特殊的战斗。


3

稳定性是密钥的理想属性,但这是相对的,而不是绝对的规则。实际上,更改键的值通常很有用。用关系术语来说,数据只能通过其(超级)键来识别。因此,如果给定表中只有一个键,则A)更改键值,或B)用一些包含其他键值的相似或不同的行集替换表中的行集之间的区别实质上是语义而不是逻辑的问题。

一个更有趣的示例是具有多个键的表的情况,其中一个或多个键的值可能必须相对于其他键值进行更改。以带有两个键的Employee表为例:LoginName和Badge Number。这是该表中的示例行:

+---------+--------+
|LoginName|BadgeNum|
+---------+--------+
|ZoeS     |47832   |
+---------+--------+

如果ZoeS丢失了徽章,那么也许她被分配了一个新徽章并获得了一个新的徽章编号:

+---------+--------+
|LoginName|BadgeNum|
+---------+--------+
|ZoeS     |50282   |
+---------+--------+

稍后,她可能决定更改其登录名:

+---------+--------+
|LoginName|BadgeNum|
+---------+--------+
|ZSmith   |50282   |
+---------+--------+

两个键值都发生了变化-彼此相关。请注意,将哪个视为“主要”并不一定有任何区别。

在实践中,“不变性”,即绝对不改变值,是无法实现的,或者至少不可能进行验证。在某种程度上,更改会有所作为,最安全的做法可能是假定可能需要更改任何键(或任何属性)。


由于以下陈述,我拒绝了您的评论:“实际上,“不变性”,即绝对不改变值,是无法实现的,至少是无法验证的。不变性是可能的,并且是使用代理密钥的最重要原因之一。
拜伦·琼斯

3
您如何得知某人下周或十年之内不会更改键值?您可能会认为它们不会,但是您实际上无法阻止这种情况的发生(如果您全权负责,那么您可以设置障碍以使其他人永远处于永久状态,但这似乎是一个极端的情况)。真正重要的是,更改很少发生,而不是永远不会发生。
nvogel

3

有趣的是,关于ROWGUID之类的链接问题提供了自己的用例:当需要同步的数据库中的主键冲突时。如果您有两个需要协调的数据库,并且它们使用序列作为主键,则需要更改其中一个键,使其保持唯一性。

在理想世界中,这永远不会发生。首先,您将使用GUID作为主键。但是,实际上,开始设计时甚至可能没有分布式数据库,将其转换为GUID可能是一项优先工作,下面将其分配为优先事项,因为它被认为比实施关键更新的影响更大。如果您具有依赖整数键的大型代码库,并且需要进行重大修订才能转换为GUID,则可能会发生这种情况。还有一个事实是,稀疏的GUID(彼此之间不太紧密的GUID,如果您按应有的方式随机生成它们,则会发生这种情况)也会对某些类型的索引产生问题,这意味着您应该避免使用它们作为主键(由Byron Jones提及)。


0

一种可能的情况是,假设您有一个具有唯一ID的会员,并且您知道它们不会在会员之间重复,因为它们具有唯一的起始字符。关联公司将数据加载到主表中。处理记录,然后分配一个主ID。用户需要在加载记录后立即访问记录,即使尚未处理。您希望主ID基于处理的订单,并且您将不会总是按照记录的加载顺序进行处理。我知道有点虚构。


-1

想象一下这样的情况:有人选择国家保险号码(NIN)作为主键,而操作员以某种方式插入了错误的NIN行。插入值后,有两种方法可以纠正错误:

  1. 删除错误的记录并插入新记录
  2. 将值更新为正确的值,如果该列上存在参照完整性约束,则使用“更新级联”
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.