我最近一直在研究ROWGUID的概念,并遇到了
this问题.
This回答给出了洞察力,但是在提到改变主键价值的情况下,让我陷入了不同的兔子洞.
我的理解一直是主键应该是不可变的,而且我在阅读这个答案后的搜索只提供了与最佳实践相同的答案.
在创建记录后,在什么情况下需要更改主键值?
解决方法
如果您使用某个人的姓名作为主键并更改了其名称,则需要更改主键.这就是使用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;
使用代理键执行此操作的首选方法是:
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,'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).如果有很多行和很多包含外键的表,那么会增加大量浪费的内存.注意,我不是在谈论磁盘空间被浪费,因为大多数人都说磁盘空间太便宜而且基本上是免费的.然而,记忆是昂贵的,值得珍惜.当您考虑大约15个字符的平均名称长度时,对密钥使用4字节整数将节省大量内存.
关于如何以及为什么键可以改变的问题是关于为什么选择自然键而不是代理键的问题,这是一个有趣且可能更重要的问题,特别是在性能是设计目标的情况下.请参阅我的问题here.