我最近一直在研究ROWGUID的概念,并遇到了这个问题。 这个答案给出了真知灼见,但由于提到更改主键值,使我陷入了另一个困境。
我一直认为主键应该是不变的,而且自阅读此答案以来,我的搜索仅提供了与最佳实践相同的答案。
创建记录后,在什么情况下需要更改主键值?
我最近一直在研究ROWGUID的概念,并遇到了这个问题。 这个答案给出了真知灼见,但由于提到更改主键值,使我陷入了另一个困境。
我一直认为主键应该是不变的,而且自阅读此答案以来,我的搜索仅提供了与最佳实践相同的答案。
创建记录后,在什么情况下需要更改主键值?
Answers:
如果您使用某人的名字作为主键,并且他们的名字已更改,则需要更改主键。这是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字节整数用作键将节省大量内存。
切约的问题是如何和为什么键可以改变就是为什么超过代理键选择自然键,这是一个有趣的也许是更重要的问题的问题,特别是在性能是一个设计目标。在这里看到我的问题。
虽然您可以使用自然的和/或可变的密钥作为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
任何涉及更改 PRIMARY KEY
定期带来灾难。更改它的唯一好理由是将两个以前分开的数据库合并在一起。
正如@MaxVernon指出的那样ON UPDATE CASCADE
,尽管当今大多数系统都使用ID作为替代,但是偶尔可能会发生更改,然后再使用PRIMARY KEY
。
乔·塞尔科(Joe Celko)和法比安·帕斯卡(Fabian Pascal)(一个值得关注的网站)之类的纯粹主义者不同意使用代理密钥,但我认为他们输掉了这场特殊的战斗。
稳定性是密钥的理想属性,但这是相对的,而不是绝对的规则。实际上,更改键的值通常很有用。用关系术语来说,数据只能通过其(超级)键来识别。因此,如果给定表中只有一个键,则A)更改键值,或B)用一些包含其他键值的相似或不同的行集替换表中的行集之间的区别实质上是语义而不是逻辑的问题。
一个更有趣的示例是具有多个键的表的情况,其中一个或多个键的值可能必须相对于其他键值进行更改。以带有两个键的Employee表为例:LoginName和Badge Number。这是该表中的示例行:
+---------+--------+
|LoginName|BadgeNum|
+---------+--------+
|ZoeS |47832 |
+---------+--------+
如果ZoeS丢失了徽章,那么也许她被分配了一个新徽章并获得了一个新的徽章编号:
+---------+--------+
|LoginName|BadgeNum|
+---------+--------+
|ZoeS |50282 |
+---------+--------+
稍后,她可能决定更改其登录名:
+---------+--------+
|LoginName|BadgeNum|
+---------+--------+
|ZSmith |50282 |
+---------+--------+
两个键值都发生了变化-彼此相关。请注意,将哪个视为“主要”并不一定有任何区别。
在实践中,“不变性”,即绝对不改变值,是无法实现的,或者至少不可能进行验证。在某种程度上,更改会有所作为,最安全的做法可能是假定可能需要更改任何键(或任何属性)。
有趣的是,关于ROWGUID之类的链接问题提供了自己的用例:当需要同步的数据库中的主键冲突时。如果您有两个需要协调的数据库,并且它们使用序列作为主键,则需要更改其中一个键,使其保持唯一性。
在理想世界中,这永远不会发生。首先,您将使用GUID作为主键。但是,实际上,开始设计时甚至可能没有分布式数据库,将其转换为GUID可能是一项优先工作,下面将其分配为优先事项,因为它被认为比实施关键更新的影响更大。如果您具有依赖整数键的大型代码库,并且需要进行重大修订才能转换为GUID,则可能会发生这种情况。还有一个事实是,稀疏的GUID(彼此之间不太紧密的GUID,如果您按应有的方式随机生成它们,则会发生这种情况)也会对某些类型的索引产生问题,这意味着您应该避免使用它们作为主键(由Byron Jones提及)。