5cr*_*med 20 database-design primary-key
我最近一直在研究 ROWGUID 的概念并遇到了这个问题。 这个答案提供了洞察力,但在提到更改主键值时使我陷入了另一个困境。
我的理解一直是主键应该是不可变的,自从阅读这个答案以来,我的搜索只提供了与最佳实践相同的答案。
在什么情况下需要在创建记录后更改主键值?
Han*_*non 26
如果您使用某人的姓名作为主键并且他们的姓名已更改,则您需要更改主键。这是所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');
Run Code Online (Sandbox Code Playgroud)
ASELECT
对两个表:
SELECT *
FROM dbo.People p
INNER JOIN dbo.PeopleAKA pa ON p.PersonKey = pa.PersonKey;
Run Code Online (Sandbox Code Playgroud)
返回:
如果我们更新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;
Run Code Online (Sandbox Code Playgroud)
我们看:
查看上述UPDATE
语句的计划,我们清楚地看到两个表都由单个更新语句更新,外键定义为ON UPDATE CASCADE
:
最后,我们将清理我们的临时表:
DROP TABLE dbo.PeopleAKA;
DROP TABLE dbo.People;
Run Code Online (Sandbox Code Playgroud)
使用代理键执行此操作的首选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;
Run Code Online (Sandbox Code Playgroud)
为了完整性,更新语句的计划非常简单,并且显示了代理键的一个优点,即只需要更新一行,而不是在自然键场景中每一行都包含键:
SELECT *
FROM dbo.People p
INNER JOIN dbo.PeopleAKA pa ON p.PersonID = pa.PersonID;
DROP TABLE dbo.PeopleAKA;
DROP TABLE dbo.People;
Run Code Online (Sandbox Code Playgroud)
SELECT
以上两个语句的输出是:
本质上,结果大致相同。一个主要区别是宽自然键不会在出现外键的每个表中重复。在我的示例中,我使用一VARCHAR(200)
列来保存人名,这需要使用VARCHAR(200)
无处不在。如果有很多行和很多包含外键的表,那将累积起来浪费大量内存。请注意,我不是在谈论磁盘空间被浪费,因为大多数人说磁盘空间非常便宜以至于基本上是免费的。然而,内存是昂贵的,值得珍惜。当您考虑大约 15 个字符的平均名称长度时,使用 4 字节整数作为键将节省大量内存。
与关于键如何以及为什么会改变的问题相切的是关于为什么选择自然键而不是代理键的问题,这是一个有趣且可能更重要的问题,尤其是在性能是设计目标的情况下。在这里看到我的问题。
虽然您可以使用自然和/或可变的密钥作为您的 PK,但根据我的经验,这会导致问题,通常可以通过使用满足以下条件的 PK 来防止:
Guaranteed Unique, Always Exists, Immutable, and Concise.
Run Code Online (Sandbox Code Playgroud)
例如,美国的许多公司试图在他们的系统中使用社会安全号作为个人 ID 号(和 PK)。然后他们遇到了以下问题——数据输入错误导致需要修复的多个记录、没有 SSN 的人、SSN 被政府更改的人、拥有重复 SSN 的人。
这些场景中的每一个我都见过。我还看到一些公司不希望他们的客户“只是一个数字”,这意味着他们的 PK 最终是“第一个+中间+最后一个+DOB+zip”或一些类似的废话。虽然他们确实添加了足够的字段来几乎保证唯一性,但他们的查询是可怕的,更新这些字段中的任何一个都意味着要解决数据一致性问题。
根据我的经验,由数据库本身生成的 PK 几乎总是更好的解决方案。
我推荐这篇文章以获得更多的指导:http : //www.agiledata.org/essays/keys.html
小智 7
涉及同步时可以更改主键。当您的客户端断开连接并且它以特定时间间隔与服务器同步数据时,可能会出现这种情况。
几年前,我在一个系统上工作,其中本地机器上的所有事件数据都有负的行 ID,如 -1、-2 等。当数据同步到服务器时,服务器上的行 ID 被应用到客户。假设服务器上的下一行 Id 是 58。那么 -1 将变为 58、-2 59 等等。该行 ID 更改将级联到本地计算机上的所有子 FK 记录。该机制还用于确定之前同步过哪些记录。
我并不是说这是一个好的设计,但它是主键随时间变化的一个例子。
任何涉及PRIMARY KEY
定期更改的设计都是灾难的根源。更改它的唯一理由是合并两个以前独立的数据库。
正如@MaxVernon 所指出的那样,可能会偶尔发生更改 - 然后使用ON UPDATE CASCADE
,尽管现在大多数系统都使用 ID 作为代理PRIMARY KEY
。
Joe Celko和Fabian Pascal(一个值得关注的网站)等纯粹主义者不同意使用代理键,但我认为他们已经输掉了这场特殊的战斗。