【问题标题】:Database or flat file for 600K records?600K 记录的数据库或平面文件?
【发布时间】:2010-11-06 07:28:11
【问题描述】:

我正在编写一个 C# 应用程序,它需要在某个时间点将大约 600K 记录插入数据库。

它们是非常简单的记录:只有 3 个 long。

我正在使用参数来设置命令,然后遍历内存中的数据以进行插入,在每个循环中将值分配给命令参数并运行 command.ExecuteNonQuery()

在 SqlServer 上完成大约需要 50 秒,在 MySql 上更慢,而在平面文件中插入相同的数据只需要几毫秒。

是我做错了什么还是数据库太慢了?

【问题讨论】:

  • 您同时使用 SQL Server 和 MySQL。您的解决方案是否必须同时使用这两个数据库?
  • 是的,如果需要,我可以针对不同的后端调整代码
  • 如果这是一次性操作,并且只是一种将这些数字存储在平面文件中的保存/加载方式,我会这样做。无需使用电锯(DB)来钉钉子,锤子就足够了
  • 有点晚了,但 HDF 不是上述两种——一种针对数字数据优化的结构化、模式驱动的数据格式。 hdfgroup.org/HDF5

标签: c# mysql sql-server


【解决方案1】:

您是否在进行批量插入?如果你还没有,我会使用它。

INSERT INTO dbo.NewTable(fields) 
SELECT fields 
FROM dbo.oldTable 
WHERE ...

在上面的示例中,您希望确保 select 语句中使用的表具有适当的索引...正确地将聚集索引分配给最相关的字段。

如果select语句很慢,检查执行计划可能找到瓶颈。

【讨论】:

【解决方案2】:

您会看到写入平面文件的速度更快,原因如下:

  • ExecuteNonQuery 不会将多个插入语句分组到批处理中,因此每条记录都会发生完整的进程间通信周转。分组发送插入语句。
  • 您拥有的数据已经是一个平面文件的形式,因此您可以一次写入全部触发,或者通过缓冲写入几次。
  • 数据库操作倾向于使用需要 n log n 时间的树,而简单的数组形结构将需要线性时间。另一方面,如果您要合并到已排序的平面文件中,则需要一段时间。

【讨论】:

    【解决方案3】:

    因此,单行大约需要 8 毫秒,而整个文件大约需要 8 毫秒。公平吗?

    一个数据库当然还有更多的潜力:

    1. 解析、验证、执行 SQL
    2. 计算任何索引的值
    3. 如果这是单个事务,则管理回滚日志
    4. 写入自己的文件

    我假设您在本地运行,因此不包含网络延迟。

    所以我猜数据库比较慢。不过,我没想到要慢 60 万倍。

    【讨论】:

    • 请在列表中添加约束和触发器 :)
    • 幸运的是我没有使用触发器:-P.
    【解决方案4】:

    您可能在数据库服务器上一遍又一遍地运行该命令,如果您构建一个包含多个插入的命令文本然后运行它会怎样?即

    string commandText = "insert into x ( y, z) values ( 1, 2 );\r\n"
    commandText += "insert into x ( y, z) values ( 2, 3 );"
    
    command.Text = commandText;
    command.ExecuteNonQuery();
    

    【讨论】:

    • 它极大地改进了 MySql 中的东西,但仍然不是最快的东西:我用 400K 记录做的旧测试需要 77 秒来执行插入 1 到 1,而在“批处理模式下完成”时需要 14 秒",但是你还必须检查你没有通过 mysql 包限制(你可以在 my.cnf 上调整它)。但是你不能用 SqlServer 2005 做到这一点,对吗?
    • 我不知道 sqlserver 的限制,但是,我必须告诉你,将这些行插入到平面文件中比联系数据库服务器要快
    【解决方案5】:

    我对 MySQL 帮不上什么忙。但是,SQL Server 2005 及更高版本具有一些非常有趣的 XML 支持,可能会对您有所帮助。我建议您研究一下 Updategrams,该功能允许您提交要插入、更新或删除的一批数据。这可能会帮助您提高 SQL Server 的性能,因为您只需要发出一条语句而不是 600,000 条语句。我不确定它是否会像写入原始文件一样快,但它应该比发出单个语句要快得多。

    您可以从这里开始学习更新图:http://msdn.microsoft.com/en-us/library/aa258671(SQL.80).aspx

    【讨论】:

    • 从来没有看到 XML 与更快的性能相关联。 XML 的冗长通常是一个瓶颈。
    • @Joshua:鉴于当前发送的单个 sql 语句的数量庞大,我认为在这种情况下 XML 可能是一种真正的节省。 ;)
    【解决方案6】:

    如果您不需要许多并发用户,请尝试使用 MS-Jet,即“Microsoft Access”作为您的 DBMS。 MSJet 的性能可以比 SqlServer 快大约 10 倍。顺便说一句,在 50 秒(12k/秒)内插入 60 万条记录对于 SqlServer 来说非常快。

    【讨论】:

    • 我在手动测试中看到过(实际上是在Access表上复制粘贴记录),怎么能这么快呢?
    • 很多原因 - 首先,没有日志记录。这样做的好处是速度很快,但坏处是如果出现任何问题,你就很不走运了。您无法使用 Access 恢复到某个时间点。
    • 或者您可以关闭 SQL Server 上的日志记录。我认为没有理由从 SQL Server 退回到 Access。
    【解决方案7】:
    【解决方案8】:

    正如 Alex 所说:使用 SqlBulkCopy,在性能方面没有什么比它更好的了。

    使用起来有点麻烦,示例代码看这里:

    http://github.com/SamSaffron/So-Slow/blob/1552b1293525bfe36f6c9b522e370de626ac6f05/Importer.cs

    【讨论】:

      【解决方案9】:

      如果您只需要插入数据并且从不读取它,那么您可以编写一个 noop 函数并假装您将它们插入到 /dev/nul 中。真正的问题是您打算如何使用上述数据?您是否需要查询、过滤、排序、引用各个记录? IE。如果平面文件看起来也一样好,您为什么还要考虑从数据库开始?

      使用 SQL Server,您当然可以使用数据库获得更好的性能,并且插入速度至少约为每秒 50-100k。您当前的阻塞点可能是每个插件上的 lgo 冲洗。您必须批量提交并确保您的日志位于快速的主轴阵列上。开始一个事务,插入大致足够的记录来填充一个日志页(64kb)然后提交。同样值得使用 5-10 个 SqlCommands 和连接的电池,并使用异步命令(带回调的 BeginExecuteNonReader)并行启动多个插入,这样您就可以利用现在在网络往返和执行上下文准备中松散的所有死时间。

      【讨论】:

      • 嗨,“您必须批量提交并确保您的日志位于快速的主轴阵列上”是什么意思?我将它们全部插入事务中,是的,每秒达到 50k 记录会很棒,但是,我需要为此使用批处理模式吗?
      【解决方案10】:

      我的猜测是您正在执行事务性插入:插入看起来像这样:

      INSERT INTO dbo.MyTable (Field1, Field2, Field3)
      VALUES (50, 100, 150)
      

      这行得通,但就像您发现的那样,它无法扩展。为了将大量数据快速推送到 SQL Server 中,有一些工具和技术可以实现。

      可能最简单的方法是使用 BCP。这里有几个关于它的链接:

      接下来,您需要设置 SQL Server 以插入尽可能多的记录。您的数据库是处于完全恢复模式还是简单恢复模式?要找出答案,请进入 SQL Server Management Studio,右键单击数据库名称,然后单击属性。完全恢复模式将记录每个事务,但简单恢复模式会运行得更快一些。数据文件和日志文件是否位于不同的阵列上?每个阵列中有多少个驱动器,它是什么 RAID 类型(1、5、10)?例如,如果数据和日志文件都在 C 盘上,那么性能就会很差。

      接下来,您还需要布置餐桌。表上有约束和索引吗?您是否已经有其他记录,并且您有其他人同时查询它?如果是这样,请考虑为没有索引或约束的数据加载构建一个空表。尽可能快地转储那里的所有数据,然后应用约束或索引,或者将数据移动到最终目的地。

      【讨论】:

        【解决方案11】:

        我的 SQL Server 2005 解决方案

        StringBuilder sb = new StringBuilder();
        bool bFirst = true;
        
        foreach(Record r in myData)
        {
            if (bFirst)
                sb.AppendLine("INSERT INTO tbl (f1, f2, f3)");
            else
                sb.AppendLine("UNION ALL");
            bFirst = false;
        
            sb.AppendLine("SELECT " + r.data1.ToString() + "," + 
                r.data2.ToString() + "," + r.data3.ToString());
        }
        
        SqlCommand cmd = new SqlCommand(sb.ToString(), conn);
        cmd.ExecuteNonQuery();
        

        想知道它会如何执行;)

        【讨论】:

          【解决方案12】:

          Ayende 有一些有趣的代码可以精确地处理这些ExecuteNonQuery 情况。 Opening Up Query Batching 是他谈论SqlCommandSet 的介绍帖子,然后在There Be Dragons: Rhino.Commons.SqlCommandSet 中发布代码。

          如果您可以针对 SQL2008 进行优化,您还可以尝试使用闪亮的新表值参数。 This sqlteam article 是对他们的一个很好的介绍。

          【讨论】:

            猜你喜欢
            • 2015-05-13
            • 2011-09-08
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2011-10-04
            • 1970-01-01
            • 2011-01-22
            • 1970-01-01
            相关资源
            最近更新 更多