为什么我应该在 SQL Server 上“避免分区维度表”?

Adr*_*rie 5 data-warehouse database-design sql-server partitioning

SQL Cat 有一个技巧列表,标题为“构建大型关系数据仓库的 10 大最佳实践”

在部分下,4 - Design dimension tables appropriately他们指出:

避免对维度表进行分区。

他们没有提到为什么不应该这样做,我也没有在网上找到任何明确指出为什么要避免这样做的内容。

为什么要避免对维度表进行分区?

下面提供了一个更具体的例子来帮助回答,并讨论为什么不应该在大型关系数据仓库中进行分区。我不是在寻求有关改进特定于具体示例的数据模型的建议。如果该示例没有帮助提供有关为什么不应该进行分区维度的任何额外见解,那么请忽略它。


示例:您可以在答案中引用为什么分区维度是一个糟糕的/次优的想法(如果对您有帮助的话)...

在我们的环境中,我们有一个Account维度,它被分区DateEffective每月加载。我们的一些查询涉及WHERE DateEffective >= @ReportDate,这似乎是分区消除的一个很好的候选者。此外,如果我们需要重新加载当月的数据,我们将删除整个月的数据,这似乎也将从表分区中受益。


自发布问题以来更新我们的环境......

上面提到的表具有非对齐的非聚集索引(使用以下 Brent Ozar 代码进行调查)。

select
    [db_name]               = isnull(db_name(s.database_id),db_name())
    ,[schema_name]          = object_schema_name(i.object_id,db_id())
    ,[object_name]          = o.name
    ,index_name             = i.name
    ,index_type_desc        = i.type_desc
    ,data_space_name        = ds.name
    ,data_space_type_desc   = ds.type_desc
    ,s.user_seeks
    ,s.user_scans
    ,s.user_lookups
    ,s.user_updates
    ,s.last_user_seek
    ,s.last_user_update
from
    sys.objects as o

    inner join sys.indexes as i 
        on o.object_id = i.object_id

    inner join sys.data_spaces as ds 
        on ds.data_space_id = i.data_space_id

    left join sys.dm_db_index_usage_stats as s 
        on  i.object_id     = s.object_id 
        and i.index_id      = s.index_id
        and s.database_id   = db_id()
where 
        o.type      = 'u'
    and i.type      in (1, 2)
    and o.object_id in
    (
         select filter.object_id 
         from
         (
             select ob.object_id, ds.type_desc 
             from 
                sys.objects ob 
                inner join sys.indexes ind on ind.object_id = ob.object_id 
                inner join sys.data_spaces ds on ds.data_space_id = ind.data_space_id
             group by ob.object_id, ds.type_desc 
         ) as filter 
         group by filter.object_id 
         having count(*) > 1
     )
order by
    [object_name] desc
;
Run Code Online (Sandbox Code Playgroud)

这表明:

  • clustered该分区上的方案索引
  • non-clustered分区方案的 8 个索引中的5 个
  • 8 个non-clustered索引中的3 个primaryrows_filegroup
    • 其中 1 个是unique, non-clustered索引(为了完整起见:primary key non-clustered在源代码管理中的创建表脚本中定义为 a )


另一个更新

我找到了 Remus Rusanu 的这个答案,它阐明了与维度相关的分区表的复杂性。

他的陈述在我上面的例子中被我的解释所引用


未对齐的索引阻止了高效的分区切换操作

因此,当表被分区时,我们应该尝试对齐索引。在我的示例中,甚至不使用分区切换(?可能阻止?)来加载表,因为存在未对齐的索引。

使用对齐索引解决了这些问题,但也带来了一系列问题,因为这种物理、存储设计、选项会影响数据模型

我提供的示例肯定就是这种情况,并且需要进行一些更改才能实现对齐索引。

由于通常使用代理键作为primary key(a unique clustered index) 的维度,这提供了不断增加的窄键(即磁盘上的小数据大小)。这很重要,因为当维度和事实之间的连接可以更快地发生时发生的 B 树搜索。此外,这clustered index将是non-clustered index创建的任何es 的一部分,这也可以防止非聚集索引膨胀,在这里也创建更有效的索引查找/扫描。

为什么这很重要?

对齐的索引意味着无法再创建/强制执行唯一约束(分区列除外)

所有引用分区表的外键都必须包含分区键

这反过来要求所有引用分区表的表都包含分区键列值……以便正确声明外键约束。

影响是...

  • 在我们的环境中,DateEffective需要向引用帐户维度的每个表添加一列。DateEffective在我们拥有的事实表上实现一列是多余的,因为该查找由加载正确AccountID键值的ETL 过程负责。此外,某些事实以比date数据类型更具选择性的粒度声明,其中DateEffective显然是这样,使得将此列包含在事实表中更加荒谬(数据模型涟漪效应)。
  • 许多non-clustered indexes 需要更改以包含该DateEffective

然而 ...

  • 数据仓库通常具备foreign key实施限制。关于 SO 的一个很好的答案涵盖了这一点
  • 此外,从 2008 版开始,Sql Serverparallel bitmap filtered hash-joins可以优化星型连接(请参阅:通过位图过滤优化数据仓库查询性能),并且此优化不需要外键。
  • 似乎表明可以对维度表进行分区,因为所需的更改现在“仅”必须将分区键包含到非对齐索引中,因为我们的环境中不存在外键约束问题(我们的 ETL 过程管理这种完整性)。

小智 1

我怀疑这个建议是基于对维度表进行分区的可能实用性。在数据仓库中,事实表是这句格言的好例子:大数据是中等数据加上时间。维度表没有时间(不是真的),并且通常没有有用的分区属性。

你的似乎是一个很好的例子。为什么要Accounts 分区DateEffective?“因为有些报告选择该列”并不是一个充分的答案。该列上的索引将是传统的解决方案,并且具有不偏向物理数据结构的优点。

无论您拥有多少帐户,您的事实表至少要大 1-3 个数量级。您的服务器将按照该比例进行缩放。查找账户是一个相对简单的操作。从表面上看,它似乎不适合分区。