【问题标题】:Proper table design for sparse primary key稀疏主键的适当表设计
【发布时间】:2017-08-10 03:28:35
【问题描述】:

在我的系统中,我有基于存储在数据库中的规则创建的临时实体,并且这些实体没有持久化。

现在,我需要存储关于这些实体的信息,因为它们是根据规则创建的并且没有存储,所以它们没有 ID。

我想出了一个公式来根据用于生成这些临时实体的规则为这些临时实体生成一个 ID:id = rule id + "-" + 规则中的实体索引。此公式生成 164-3、123-0、432-2 等形式的唯一字符串...

我的问题是当我的键没有关系或顺序时,我应该如何构建我的表(关于主键和聚集索引)?请记住,我只会(99.9% 的时间)使用上面提到的 id 查询表。

经过大量阅读后我想到的选项,但不知道哪个更好:

1) 具有聚集索引的 varchar 列上的主键。 -根据各种消息来源,由于碎片化和密钥的广泛性,这会很糟糕。此外,它们的排序格式也很奇怪。

2) 没有聚集索引(堆表)的 varchar 列上的主键。 - 由于索引和碎片问题,根据各种来源,这也是一个坏主意。

3) 具有聚集索引的标识 int 列,以及具有唯一索引的 varchar 列作为主键。 - 在这里看不到代理键的好处,因为它主要有助于范围查询和排序,我永远不会根据这个键查询表,因为它在任何时候都是未知的。

4) 2 列复合键:规则 ID + 规则索引列。 - 现在我没有字符串,但我有两列将被复制到 FK 和非聚集索引。另外我不确定在这种情况下我会使用什么索引。

任何人都可以在这里发光吗?任何帮助表示赞赏。

--编辑
我将执行比插入更多的选择;
我将执行比更新更多的插入;
所有选择将至少包含规则 ID;

如果我使用代理主键和 (rule id, index) 上的唯一索引,那么我可以在按规则 id 检索数据后使用代理进行后续操作,这样会更快。此外,插入会更快。 但是,因为数据将根据代理键存储,所以我可能有具有相同规则 id 但索引不同的记录,存储在磁盘上的距离很远,这意味着即使在规则 id 上有索引,检索数据可能有点慢。

如果我使用 (rule id, index) 作为聚集主键,具有相同规则 id 的行将彼此靠近存储,并且通过规则 id 选择数据将足够有效。但是,我怀疑插入会很慢。

上面的理由正确吗?

【问题讨论】:

    标签: sql-server database-design


    【解决方案1】:

    除非另有证明,否则使用堆通常是个坏主意。即便如此,您仍需要一个非常充分的理由来不使用聚集索引(任何人都会使事情变得更好,即使在 identity 列上也是如此)。

    将此密钥存储在单个列中是可以的;例如,如果你想要自然排序,你可以用零填充你的数字。但是,这将扩大密钥。

    拥有一个复合主键(以及随后的外键)是完全可以接受的,尤其是在处理自然键时,比如你拥有的那个。这将为您提供最窄的键 - int + int 或类似的 - 同时消除排序问题。我建议将此 PK 集群化以减少额外的密钥查找。

    这里的碎片化不会是一个大问题;至少,不比任何其他索引决策更大。建立在这样一个键上的任何索引都容易出现碎片、聚集或没有。在任何情况下,您的 DBA 都应该知道如何将这样的索引保持在最佳状态。

    关于索引中列的顺序,通常适用以下规则:

    1. 如果会发生部分键匹配(通过键的一部分而不是另一部分进行过滤),最常使用的那个应该排在第一位;
    2. 如果 No.1 不适用并且所有查询中都使用了键的所有部分,则应优先使用具有最高 cardinality 的列。

    剩余列的顺序(如果多于 1 个)并不重要,因为 SQL Server 只为复合索引中的第一列创建分布统计信息。但是,最好按照基数递减的顺序列出它们。

    编辑:查看您的更新以及更多详细信息,这里是最合适的选项。假设您的表格如下所示:

    -- Sample table
    create table dbo.TempEntities (
        RuleId int not null,
        IndexId int not null,
        -- Remaining columns listed here
        EntityData xml not null
    );
    go
    

    从这里开始,最直接的方法就是使用自然键作为聚集索引:

    -- Option 1 - natural clustered index
    alter table dbo.TempEntities
    add constraint PK_TempEntities primary key clustered (RuleId, IndexId);
    go
    

    但是,如果您有任何子表会引用这个子表,这可能不是最方便的解决方案,因为自然键容易更新,这会造成混乱,您可以避免它。相反,可以引入代理键,如下所示:

    -- Option 2 - surrogate clustered, natural nonclustered
    alter table dbo.TempEntities add Id bigint identity(1,1) not null;
    
    alter table dbo.TempEntities
    add constraint PK_TempEntities primary key clustered (Id);
    
    alter table dbo.TempEntities
    add constraint UQ_TempEntities_RuleIdIndexId unique (RuleId, IndexId);
    go
    

    将代理 PK 集群化是有意义的,因为它会减少页面拆分,从而加快插入速度(尽管与选项 1 相比多了一个索引)。如果您不了解您的查询,这可能是最平衡的解决方案。

    在代理键和自然键之间改组 clustered 属性主要具有学术价值,并且只能在高负载系统上产生影响,该系统每秒 24*7 计划发生数百次插入。如果您的系统确实如此,请寻求专业顾问,他们将分析您的疑问并提供适合您情况的解决方案。

    【讨论】:

    • 谢谢。什么是最佳顺序(规则 ID,索引)或(索引,规则 ID)?我的索引会是什么样子?
    • @victor,更新了我的答案。不确定“看起来像” - 你的意思是实际的 alter table ... add constraint ... 声明?
    • 我的意思是集群、非集群、列的顺序等......经过大量阅读后,我认为最好使用集群主键(规则 ID、索引),因为我只会通过以下方式查询规则 id,它将使用索引。你认为如果有一个 surogate int clustered pkey,加上 (rule,index) 上的唯一索引,查询的性能会更高,因为在我按规则 id 查询之后,我可以使用代理 id 进行后续操作?
    • 我编辑了我的问题并添加了有关查询的更多信息。在阅读您的答案后,我还添加了两个设计。你能评论一下吗?
    • 是的。经过数百页,我得出了相同的结论:代理有助于插入; index on (rule id, index id) 在按这些值过滤时有帮助;当我需要 WHERE_IN 时,还有一个用于“ruleid-indexid”的索引 varchar(20),例如其中 X in (Y),使用多列无法令人满意地完成。我认为字符串不会那么糟糕,因为它们代表了一个定义明确的整数序列,加上一个破折号。它们可以分类,只是更贵;此外,它们是独一无二的,这对索引非常有用。
    猜你喜欢
    • 2016-10-29
    • 2018-06-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多