【问题标题】:Transactional And Reporting Databases - How?交易和报告数据库 - 如何?
【发布时间】:2010-10-06 13:33:38
【问题描述】:

在构建具有高度规范化数据库的事务系统时,运行报告样式查询甚至查询以在 UI 上显示数据可能涉及多个连接,这在数据繁重的情况下会并且通常会影响性能。连接很昂贵。

通常,所支持的指导是,您永远不应该在事务性数据库模型之外运行这些查询,而应该使用为特定 UI 视图或报告量身定制的非规范化扁平化模型,从而消除对许多连接的需求。在这种情况下,数据重复不是问题。

这个概念非常有道理,但是当专家发表这些声明时,我很少看到正是如何实现这一点。例如,(坦率地说,我很欣赏一个使用任何平台的例子)在一个运行在 sql server 后端的中型系统中,你有一个规范化的事务模型。您还有一些需要查询的报告和网站。因此,您创建了一个“报告”数据库,将标准化数据展平。你如何保持同步?事务日志传送?如果是这样,您如何转换数据以适应报告模型?

【问题讨论】:

  • 从交易输入到报告中,您可以允许您的数据延迟多少?
  • 让我们做两个常见的场景: 1. 基本上是最新的或尽快。 2. 每日
  • 硬件可能比软件便宜,你也应该找到硬件的瓶颈。

标签: .net sql-server database


【解决方案1】:

在我们的店里,我们设置了一个连续的transactional replication从OLTP系统到另一个用于报告的DB服务器。您不希望为此目的使用日志传送,因为它每次恢复日志时都需要对数据库进行排他锁,这会阻止您的用户运行报告。

现在使用 SQL Server 中的优化器,我认为规范化数据库上的连接对于报告来说“过于昂贵”的概念有点过时了。我们的设计完全是第 3 范式,主表中有几百万行,运行任何报表都没有问题。话虽如此,如果迫在眉睫,您可以考虑在您的报告服务器上创建一些 indexed views 以提供帮助。

【讨论】:

  • 但是事务复制不允许您转换数据,对吗?这必须在它进入报告数据库后发生吗?
  • 我想您可以考虑自定义复制在订阅者上使用的存储过程,或者使用订阅者表上的触发器做一些事情。但正如我所提到的,我会从简单开始并尝试从您的规范化模式运行报告。不要在遇到性能问题之前就假设它们使事情变得过于复杂。
  • @Saraz:您可以将报告非规范化架构构建为规范化 OLTP 架构的 扩展。使用索引策略,例如大(宽)覆盖索引和特殊索引视图。这些索引由复制自动维护,代价是插入/更新速度较慢。通常还部署快照隔离以保护报告免受与应用更改的复制代理的锁定争用。拥有两个完全不同的架构(如此不同以至于无法复制)真的很难保持同步。
【解决方案2】:

我们使用事务复制到另一个数据库。

我们过滤数据,因此我们只在复制数据库中获取我们需要的数据

我们也只选择我们想要的列,所以表格“更小”。

然后我们通过视图或构建触发器将数据从一个表添加到另一个表中组合复制数据库中的数据。

【讨论】:

    【解决方案3】:

    正确的索引、覆盖索引和重新格式化查询可能会给您带来很多好处。但是,如果您已经这样做了,那么您可以镜像您的数据库、复制它们,或者创建一个 etl 包并创建一个分析服务多维数据集。

    【讨论】:

      【解决方案4】:

      Canned answer:

      在很多情况下,索引视图可能会解决您的短期性能目标,但在以后的某个时间会适得其反。所以如果你选择使用索引视图,你可能需要一个退出策略。让我描述一下索引视图的一些常见问题。

      索引视图可能会增加锁定争用。

      很容易演示。创建下表:

      CREATE TABLE dbo.ChildTable(ChildID INT NOT NULL 
        CONSTRAINT PK_ChildTable PRIMARY KEY,
        ParentID INT NOT NULL,
        Amount INT NOT NULL);
      GO   
      

      从 SSMS 中的一个选项卡,运行此脚本:

      BEGIN TRAN;
      INSERT INTO dbo.ChildTable(ChildID, ParentID, Amount)
        VALUES(1,1,1); 
      

      从另一个选项卡,运行一个类似的:

      BEGIN TRAN;
      INSERT INTO dbo.ChildTable(ChildID, ParentID, Amount)
        VALUES(2,1,1);
      ROLLBACK;
      

      请注意,两个插入都已完成,它们不会相互阻塞。在两个选项卡中回滚,并创建索引视图:

      CREATE VIEW dbo.ChildTableTotals WITH SCHEMABINDING
      AS
      SELECT ParentID, 
        COUNT_BIG(*) AS ChildRowsPerParent, 
        SUM(Amount) AS SumAmount
      FROM dbo.ChildTable
      GROUP BY ParentID;
      GO
      CREATE UNIQUE CLUSTERED INDEX ChildTableTotals_CI 
        ON dbo.ChildTableTotals(ParentID);
      

      重新运行两个插入。注意第二个没有完成;它被阻止了。原因很简单:第一个insert修改了indexed view中对应的entry,所以insert获取并持有了锁。

      同样容易证明,当您创建索引视图时,死锁也可能变得更容易。

      注意:这不是索引视图实现方式的问题。如果您推出自己的汇总表,并开发直接修改它以使其保持最新的触发器,您将遇到同样的问题。只有当你不一直维护你的汇总表时,你才能解决这个锁定问题,但是对此更详细的讨论超出了本文的范围。

      编辑:这个例子在你看来可能是做作的,但它所展示的问题是非常真实和非常普遍的。 OLTP 环境中的索引视图用途有限,因为它们严重增加了锁争用并导致许多死锁。有人在 OLTP 中创建它们但最终放弃是很常见的,因为它们引入的问题多于解决的问题。

      有两种常见的方式来演示并发引起的问题 - 我们要么编写循环并从多个连接运行它们,要么显式地在两个或多个连接中开始事务。我鼓励大家想出一个更简单的方法来演示这个问题。

      【讨论】:

      • 任何(对我隐藏)导入“NOT NULL”+同一字段上的 PRIMARY KEY 约束(在“CREATE TABLE dbo.ChildTable(ChildID INT NOT NULL CONSTRAINT PK_ChildTable PRIMARY KEY”中)?
      • 并且由于这个(相当人为的)示例,您想完全排除索引视图的好处? (为什么你想要一个简单的插入开始翻译?)
      • @noel Abrahams:这个例子在你看来可能是做作的,但它所展示的问题是非常真实且非常普遍的。我鼓励你想出一个更简单的方法来演示这个问题。
      【解决方案5】:

      简答:尝试使用indexed views。基础表存在许多限制,但您可以开箱即用地获得同步。

      【讨论】:

      • 我以前被告知过。我会进一步调查。谢谢。
      • -1:你会引入很多锁竞争和死锁。很可能您将不得不完全放弃它们。
      • 嗯?你到底在说什么?! :-)
      • @AlexKuznetsov,我仍然无法理解“我什么时候应该使用索引视图而不是真实表?” stackoverflow.com/questions/3861476/…
      • @vgv8:我不明白你的问题。
      猜你喜欢
      • 2014-01-08
      • 2018-11-28
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-03-17
      • 2016-11-30
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多