如果我有一个包含列的表:
- 艺术家
- 专辑
- 歌曲
- 听数
...是在 Artist、Album 和 Song 上放置一个集群主键,还是拥有一个自动递增的 id 列并对 Artist、Album 和 Song 设置一个唯一约束更好。
数据库一致性有多重要?如果我的一半表具有聚集的主键,而另一半具有唯一约束的 id 列,那是坏的还是无关紧要?两种方式对我来说似乎都是一样的,但我不知道行业标准是什么,或者哪个更好以及为什么。
如果我有一个包含列的表:
...是在 Artist、Album 和 Song 上放置一个集群主键,还是拥有一个自动递增的 id 列并对 Artist、Album 和 Song 设置一个唯一约束更好。
数据库一致性有多重要?如果我的一半表具有聚集的主键,而另一半具有唯一约束的 id 列,那是坏的还是无关紧要?两种方式对我来说似乎都是一样的,但我不知道行业标准是什么,或者哪个更好以及为什么。
我永远不会在长文本列上放置主键,例如:艺术家、专辑和歌曲。使用作为集群 PK 的自动增量 ID。如果您希望艺术家、专辑和歌曲是唯一的,请在三者上添加唯一索引。如果您想按专辑或歌曲搜索,独立于独立的艺术家,您需要为每个索引建立一个索引,这会拉入 PK,因此拥有一个小的 PK 可以节省您在其他索引上的时间。节省的不仅仅是磁盘空间,还有内存缓存,以及页面上的更多键。
您确实需要将两个问题分开:
1)主键是一个逻辑构造 - 唯一且可靠地标识表中每一行的候选键之一。这可以是任何东西,真的 - 一个 INT、一个 GUID、一个字符串 - 选择对您的场景最有意义的东西。您在外键约束中引用主键,因此这些对于数据库的完整性至关重要。使用它们 - 总是 - 期间。
2)聚集键(在表上定义“聚集索引”的一列或多列)——这是一个与物理存储相关的东西,在这里,一个小的、唯一的、稳定的、不断增长的数据类型是你的最佳选择- INT 或 BIGINT 作为您的默认选项。
默认情况下,SQL Server 表上的主键也用作集群键 - 但不需要这样,您可以轻松选择不是主键的列作为集群键。
然后还有另一个问题需要考虑:表上的集群键也将添加到表上每个非聚集索引的每个条目中 - 因此您真的希望确保它尽可能小。通常,具有 2+ 十亿行的 INT 对于绝大多数表来说应该足够了 - 与 VARCHAR(20) 或集群键相比,您可以在磁盘和服务器内存中节省数百兆字节的存储空间。
更多值得深思的东西——金伯利·特里普(Kimberly Tripp)的优秀作品——读一读,再读一遍,消化一下!这是 SQL Server 索引的福音,真的。
马克
聚集索引非常适合基于范围的查询。例如,日志日期或订单日期。当您插入新行时,将一个放在艺术家、专辑和歌曲上 [可能] 会导致碎片。
如果您的数据库支持它,请在 Artist、Album 和 Song 上添加一个非集群主键并称之为好。或者只是在艺术家、专辑和歌曲上添加一个唯一键。
仅当您必须对另一个表具有引用完整性时,拥有自动递增主键才会真正有用。
在不知道确切要求的情况下,通常您可能会有一个艺术家表,也可能有专辑表。歌曲表将是艺术家 ID、专辑 ID 和歌曲的唯一组合。我会根据应用程序通过索引或约束来强制唯一性,并使用 id 作为主键。
首先,这里已经有问题了,因为数据没有标准化。应尽可能避免在一堆文本列上创建任何类型的索引。即使这些列不是文本(我怀疑它们是),将艺术家、专辑和歌曲放在同一个表中仍然没有意义。一个更好的设计是:
Artists (
ArtistID int NOT NULL IDENTITY(1, 1) PRIMARY KEY CLUSTERED,
ArtistName varchar(100) NOT NULL)
Albums (
AlbumID int NOT NULL IDENTITY(1, 1) PRIMARY KEY CLUSTERED,
ArtistID int NOT NULL,
AlbumName varchar(100) NOT NULL,
CONSTRAINT FK_Albums_Artists FOREIGN KEY (ArtistID)
REFERENCES Artists (ArtistID))
Songs (
SongID int NOT NULL IDENTITY(1, 1) PRIMARY KEY CLUSTERED,
AlbumID int NOT NULL,
SongName varchar(100) NOT NULL,
NumberOfListens int NOT NULL DEFAULT 0
CONSTRAINT FK_Songs_Albums FOREIGN KEY (AlbumID)
REFERENCES Albums (AlbumID))
一旦你有了这个设计,你就可以搜索个人专辑和艺术家以及歌曲。您还可以添加覆盖索引以加快查询速度,并且索引将比原始设计小得多,因此速度更快。
如果您不需要进行范围查询(您可能不需要),那么您可以用更适合您的设计的 a 替换IDENTITY
键;ROWGUID
在这种情况下,这并不重要,我会坚持简单的IDENTITY
.
您必须小心集群键。如果您聚集在一个完全不连续的键上(并且艺术家、专辑和歌曲名称绝对符合非连续的条件),那么您最终会出现页面拆分和其他问题。你不想要这个。正如 Marc 所说,该键的副本会添加到每个索引中,当您的键长为 300 或 600 字节时,您绝对不希望这样做。
如果你想通过艺人、专辑、歌名快速查询特定歌曲的收听次数,上面的设计其实很简单,只需要正确索引即可:
CREATE UNIQUE INDEX IX_Artists_Name ON Artists (ArtistName)
CREATE UNIQUE INDEX IX_Albums_Artist_Name ON Albums (ArtistID, AlbumName)
CREATE UNIQUE INDEX IX_Songs_Album_Name ON Songs (AlbumID, SongName)
INCLUDE (NumberOfListens)
现在这个查询会很快:
SELECT ArtistName, AlbumName, SongName, NumberOfListens
FROM Artists ar
INNER JOIN Albums al
ON al.ArtistID = ar.ArtistID
INNER JOIN Songs s
ON s.AlbumID = al.AlbumID
WHERE ar.ArtistName = @ArtistName
AND al.AlbumName = @AlbumName
AND s.SongName = @SongName
如果您查看执行计划,您会看到 3 次索引搜索——它的速度与您获得的一样快。我们保证了与原始设计完全相同的独特性,并针对速度进行了优化。更重要的是,它是标准化的,因此艺术家和专辑都有自己的特定身份,这使得从长远来看更容易管理。搜索“艺术家 X 的所有专辑”要容易得多。搜索“专辑 Y 上的所有歌曲”要容易得多,而且速度要快得多。
在设计数据库时,规范化应该是您的首要关注点,索引应该是您的第二个关注点。而且您可能会发现,一旦您进行了标准化设计,最佳索引策略就会变得显而易见。