我们应该对数据库进行非规范化以提高性能吗

时间:2010-05-03 11:10:02

标签: .net nhibernate database-design sqlite database-normalization

我们要求每秒存储500次测量,来自多个设备。每个度量由时间戳,数量类型和几个向量值组成。现在每次测量有8个矢量值,我们可能会认为这个数字对于原型项目的需求是恒定的。我们正在使用HNibernate。测试是在SQLite(磁盘文件db,而不是内存)中完成的,但生产可能是MsSQL。

我们的Measurement实体类是一个包含单个测量的实体类,如下所示:

public class Measurement
{
    public virtual Guid Id { get; private set; }
    public virtual Device Device { get; private set; }
    public virtual Timestamp Timestamp { get; private set; }
    public virtual IList<VectorValue> Vectors { get; private set; }
}

矢量值存储在一个单独的表中,因此每个值都通过外键引用其父测量值。

我们做了一些事情来确保生成的SQL(合理)高效:我们使用Guid.Comb生成ID,我们在一个事务中刷新大约500个项目,ADO.Net批量大小设置为100(我认为SQLIte不支持批量更新?但以后可能会有用)。

问题

现在我们可以每秒插入150-200个测量值(这还不够快,尽管这是我们正在讨论的SQLite)。查看生成的SQL,我们可以看到在单个事务中插入(如预期的那样):

  • 1个时间戳
  • 1次测量
  • 8个矢量值

这意味着我们实际上要多做10倍的单表插入:每秒1500-2000。

如果我们将所有内容(所有8个矢量值和时间戳)放入测量表(添加9个专用列),似乎我们可以将插入速度提高10倍。

切换到SQL服务器将提高性能,但我们想知道是否有办法避免与数据库组织方式相关的不必要的性能成本。

[编辑]

对于内存中的SQLite,我得到大约350项/秒(3500个单表插入),我相信它与NHibernate一样好(以此帖子作为参考:http://ayende.com/Blog/archive/2009/08/22/nhibernate-perf-tricks.aspx)。

但我不妨切换到SQL服务器并停止假设,对吧?我会在测试后立即更新我的帖子。

[更新]

我已经转移到SQL服务器并使我的层次结构变平了,我通过每小时存储3000次测量来测试它几个小时,它似乎工作正常。

10 个答案:

答案 0 :(得分:10)

就个人而言,我会说它去:denormalize,然后创建一个ETL过程,将这些数据转换为更加规范化的格式,以便进行分析/定期使用。

基本上,理想的情况可能是拥有一个单独的数据库(或者甚至只需要在同一个数据库中使用单独的表),这些数据库将数据的获取视为完全独立的事物,而不是以您使用的格式需要处理它。

这并不意味着你需要丢弃你在当前数据库结构周围创建的实体:只是你还应该创建那些非规范化表并创建一个ETL来引入它们。你可以使用SSIS(虽然它仍然是非常错误和易怒的)定期将数据带入规范化的表格集,甚至是C#应用程序或其他批量加载过程。

编辑:当然,这是假设你的分析不需要实时完成:只是数据的集合。通常,人们不需要(有时甚至实际上不希望)实时更新分析数据。这是纸上听起来不错的事情之一,但在实践中它是不必要的。

如果分析此数据的某些人需要实时访问,您可以根据需要构建针对“裸机”非规范化事务数据的工具集:但是当您真正深入了解需求时,执行分析的人员不需要真正的实时(在某些情况下,他们更愿意使用更静态的数据集!):在这种情况下,定期ETL可以很好地工作。您只需与目标用户聚在一起,找出他们真正需要的东西。

答案 1 :(得分:4)

嗯,这取决于。 8个向量值是一个永不改变的硬数字吗?然后在您的情况下进行非规范化可能是有意义的(但只测试您正在使用的真实硬件和数据库将告诉)。如果下周可以进行9次测量,请不要这样做。

我想说在尝试决定做什么之前,你需要首先切换到SQL服务器和你将运行的设备。

切换运行探查器后。完全有可能nHibernate没有为您的插件创建性能最佳的SQl。

您有一组可能在插入上拆分的向量这一事实可能是您性能问题的一部分。最好有8个单独的变量,而不是必须拆分的集合。

你说的是每天超过4000万条记录,这需要一些主要硬件和一个设计精良的数据库。关系数据库也可能不是最佳选择(我不知道您希望如何使用这些数据)。你保存这些数据多长时间,这里的大小将非常迅速地失控。

是否可以每分钟对组中的记录进行一次?批量插入比逐行插入更快。

您的设计必须考虑如何使用数据以及插入数据。通常,为加速插入而做的事情可能会减慢选择速度,反之亦然。您可能需要一个每天加载一次的数据仓库进行分析(以及快速查询以便能够显示原始数据到第二个数据)。

答案 2 :(得分:3)

首先,转移到目标数据库;基于SqlLite的性能可能不表示基于MsSql的性能

第二,衡量绩效瓶颈的位置;我冒昧地说它是磁盘而内存数据库的表现要好得多。

然后根据需要使用ETL过程进行非规范化。

事件流处理有一句名言:“如果你点击了磁盘,你已经死了。”; - )

答案 3 :(得分:2)

你考虑过使用SqlBulkCopy吗?它工作得非常快。我已经在生产环境中使用它,并且使用sql server 2005机器在不到一秒的单个表上实现了10.000多个插入。您只需准备要在应用程序中批量插入的DataTable。这是一个样本。

        public static void SQLBulkCopyInsert(DataTable dtInsertRows, string destinationTableName, string[] columnMappings)
    {
        using (SqlBulkCopy sbc = new SqlBulkCopy(DBHelper.Secim2009DB.ConnectionString, SqlBulkCopyOptions.UseInternalTransaction))
        {                
            sbc.DestinationTableName = destinationTableName;
            // Number of records to be processed in one go
            sbc.BatchSize = 30000;
            // Map the Source Column from DataTabel to the Destination Columns in SQL Server 2005 Person Table

            foreach (string columnMapping in columnMappings)
            {
                sbc.ColumnMappings.Add(columnMapping, columnMapping);
            }

            // Number of records after which client has to be notified about its status
            sbc.NotifyAfter = dtInsertRows.Rows.Count;
            // Event that gets fired when NotifyAfter number of records are processed.
            sbc.SqlRowsCopied += new SqlRowsCopiedEventHandler(sbc_SqlRowsCopied);
            // Finally write to server
            sbc.WriteToServer(dtInsertRows);
            sbc.Close();
        }
    }

    public static void sbc_SqlRowsCopied(object sender, SqlRowsCopiedEventArgs e)
    {            

    }

答案 4 :(得分:1)

不要只是反规范化。使用有用的设计模式设计结果。有时,一个有用的性能设计模式提供的设计与遵循规范化规则得到的设计不同。

我不认为你的情况会因非正规化而得到帮助。几乎所有倡导非规范化的人都说,在存储新数据时,性能提升并不存在。它们在您检索数据时出现。你必须弄清楚这适用于你的情况。

我可以告诉你这么多。如果您最终通过多个并发进程进行存储,那么您的设计将导致严重的瓶颈,并且可能比标准化设计运行得慢。

但是不要相信我的话。实验。分析。学习。繁荣。

答案 5 :(得分:1)

“我们要求每秒存储500次测量,来自多个设备。”

不要使用DBMS来存储这类数据。

人们使用DBMS的原因是什么?

(a)他们可以对您尝试注册的数据强制执行约束。但你没有。测量数据就是它们的原因,需要接受它们。没有限制。

(b)在(1)违反约束和(2)严重的系统故障(如磁盘I / O错误)的情况下,他们可以确保您宝贵的业务数据的一致性和完整性。但由于你没有约束,(1)不适用。至于(2),如果磁盘I / O错误阻止它被记录,你将如何处理测量?无论如何,您的测量都会丢失。

所以imo,你没有任何理由使用DBMS。将您的测量负载转储到平面文件中并根据需要进行处理。

答案 6 :(得分:1)

您可以考虑其他数据库替代方案。 MSSQL提供了许多功能,但这会增加一些开销。

高性能处理的优秀资源(就像您要做的那样)位于http://highscalability.com/

他们的一个案例研究是在数据库中存储数千个设备统计信息。解决方案是多个MYSQL数据库,并根据设备ID路由请求。总体而言 - 该网站可以提供优秀的案例研究。也许你可以找到一个可能的解决方案。

TIMUR

答案 7 :(得分:1)

使用正确的DBMS和硬件。在具有不同硬件的另一个平台上进行测试将不会告诉您任何性能。

非规范化不太可能有助于编写性能,因为根据定义,它意味着您正在创建冗余数据,因此您将为每次写入做更多工作,而不是更少。

您引用的数据对于流数据场景并不是特例,并且使用正确的硬件可以完全实现,但我认为nHibernate将成为您的主要限制因素。我认为nHib不太可能是这种事情的明智选择。

您是否考虑过使用一些为流数据源和CEP提供特殊支持的技术?例如:OSISoft PI,Microsoft StreamInsight和SQL Server的文件流功能。

答案 8 :(得分:1)

你必须问自己,“我们为什么要正常化?”

主要有三个原因:

  1. 数据一致性
  2. 更新速度
  3. 尺寸
  4. 数据一致性

    下降和所有行意味着相同的东西都有相同的FK,这很好吗?很明显。这对于具有多个数据“编辑器”的DB来说非常重要。但这只是我们的流程。假设它是一个飞行数据库,并且有一个华盛顿特区国家机场的入口......还有一些为华盛顿特区的里根国家机场增加了一个新的入口...... FK将在那里,并用于儿童桌,但赢了不值得......但这样做仍然是件好事......

    更新速度

    我们应该做的是用新名称更新国家机场的行。因为只有一个父行,所以这是一个非常简单的变化。如果我的航班表有文本,我会更新数百万行。

    大小

    如果我在每条记录上都存储了“里根国家机场”,那么它需要的空间比FK更大.19。尺寸过去非常重要,但SAN使它变得无关紧要。


    Conclussions

    好的,您是否担心您的SOLO数据收集应用程序无法保持仪器的名称直接?数据一致性是否会成为挑战?

    好的,那么您认为您将更改仪器或数据点的名称多少次?我的意思是溶解O2溶解O2,浊度是浊度,对吗?但是如果你确实需要进行大规模更新,我打赌你会在运行之间停机。所以这不是问题。

    好的,那么尺寸,确定......这是很多测量;但是,不要让测量“溶解氧”,DO2很好......比一些FK更大的是“7”?花费空间来节省时间。

    不要规范化,因为你总是被告知好的数据库设计师所做的事情。知道你为什么这么做以及为什么选择你所选择的。

答案 9 :(得分:0)

是。我会考虑通过非规范化(数据展平)和按时间分块数据来减少插入的开销。我会设计我的数据库,以便每个记录每个设备存储一整秒的数据:

public class Measurement 
{ 
    public Guid ID { get; private set; } 
    public Device Device { get; private set; }
    public Sample[] { get; private set; }

    public DateTime FirstTimestamp { get; private set; } 
    public DateTime LastTimestamp { get; private set; } 
} 

public class Sample
{ 
    public DateTime Timestamp { get; private set; } 
    public VectorValue[] Vectors { get; private set; } 
}

有多种方法可以在单个记录中存储复杂类型(例如列表列表,在本例中)。 XML columnsCLR user-defined types就是两个例子。