从.NET代码插入SQL Server表的最快方法?

时间:2011-01-20 12:30:21

标签: sql-server-2008 sqlbulkcopy

最快的方法是:

  • 一张表,没有我无法预先填写的参考文件(即那里有一个参考密钥,但我已填写所有数据)
  • 很多数据。我们每天谈论数亿行,通过API动态进行
  • 必须/应该在近乎实时的情况下尽快处理请求(即没有写入文件以便每天上传一个)。 2秒是正常的最大延迟
  • 用于数据/应用程序和SQL Server的单独机器

我现在做的事情:

  • 将最多32 * 1024行聚合到一个数组中,然后对其进行排队。
  • 以2-3个线程读取队列。使用SqlBulkCopy插入数据库。

我每秒输入大约60k-75k行,这还不够,但非常接近。我想要达到250.000行。

到目前为止还没有真正使用过。我得到20%的时间“网络I / O”块,有一个核心80%加载CPU端。光盘写出7mb-14mb,大部分是空闲的。 RAID 10上有6只猛禽的平均队列长度为...... 0.25。

任何人都知道如何加快速度?更快的服务器(到目前为止它是虚拟的,8GB RAM,4个核心,物理磁盘通过数据)。


添加一些说明:

  • 这是2008 R2服务器上的2008 R2企业SQL Server。机器有4个核心,8gb内存。全64位。平均80%的负载来自这台机器,显示出大约20%的CPU负载。
  • 表格很简单,没有主键,只有关系引用的索引(工具参考)和唯一的(在一组工具中,因此不强制执行)时间戳。
  • 表格中的字段包括:时间戳,工具参考(无强制外键),数据类型(字符1,指示发布数据的多个字符之一),价格(双精度)和数量(整数)。正如您所看到的,这是一张非常薄的桌子。有问题的数据是金融工具的刻度数据。
  • 问题还在于硬件等问题 - 主要是因为我认为没有真正的瓶颈。我插入多个交易,它给了我一个好处,但一个小的。光盘,CPU没有显示出显着的负载,网络等待时间很长(300毫秒/秒,目前为30%),但这是在同一个虚拟化平台上运行JSUT的两个服务器,并且有足够的内核可以运行所有服务器。我几乎愿意“购买另一台服务器”,但我想首先确定瓶颈....特别是考虑到在一天结束时我没有抓住瓶颈是什么。日志记录无关紧要 - 批量插入不会作为数据(没有聚集索引)进入数据日志。

垂直分区是否有帮助,例如通过一个字节(tinyint)来分割仪器世界,例如16个表格,我这样同时最多可以进行16次插入?实际上,数据来自不同的交易所,我可以为每个交易所制作一个分区。这将是一个自然的分割场(实际上是在乐器中,但我可以在这里复制这些数据)。


更多澄清:速度更高(90k),现在明显受到机器之间网络IO的限制,这可能是VM切换。

我现在所做的是每32k行进行一次连接,建立临时表,使用SqlBUlkdCopy插入,然后使用ONE sql语句复制到主表 - 最小化主表上的任何锁定时间。

现在大部分等待时间仍在网络IO上。似乎我遇到了VM明智的问题。将在未来几个月转向物理硬件;)

6 个答案:

答案 0 :(得分:3)

如果你每秒管理70k行,那么到目前为止你很幸运。但我怀疑这是因为你有一个非常简单的架构。

我无法相信你在这个问题上提出这种负担

  • 虚拟服务器
  • 单阵列
  • SATA磁盘

网络和CPU是共享的,IO受到限制:您无法使用所有资源。 您看到的任何负载统计数据都不是很有用。我怀疑您看到的网络负载是2个虚拟服务器之间的流量,如果您解决此问题,您将成为IO绑定

在继续之前,请阅读10 lessons from 35K tps。他没有使用虚拟盒子。

如果您想增加数量,假设没有SAN且没有DR功能,这就是我要做的事情。

  • 购买2台大型物理服务器,CPU RAM种类无关紧要,最大内存,转x64安装
  • 磁盘+控制器=最快的主轴,最快的SCSI。或者是一个伟大的NAS
  • 1000MB + NICs
  • 带有6-10磁盘的RAID 10,用于仅限数据库的一个日志文件
  • 数据文件的剩余磁盘RAID 5或RAID 10

作为参考,我们的峰值负载是每小时1200万行(16核,16GB,SAN,x64),但我们的负载复杂。我们没有能力。

答案 1 :(得分:1)

桌子上有没有你可以做的索引吗?编辑:在你打字的时候询问。

是否可以将价格转换为整数,然后除以1000或任何查询?

答案 2 :(得分:1)

您是否尝试过将pk添加到表中?这会提高速度吗?

还有一种基于集合的方法来使用计数表从http://www.sqlservercentral.com/articles/T-SQL/62867/导入csv数据(靠近底部,需要免费注册但值得)。

你可能想尝试一下并测试它的性能......用一个小的计数正确索引的计数表。

答案 3 :(得分:1)

从我在这里读到的答案来看,你似乎确实遇到了硬件问题,而不是代码问题。理想情况下,通过提供更多磁盘I / O或网络带宽,或者在托管数据库的同一虚拟机上运行程序,可以提高性能。

但是我确实想分享表参数插入非常适合大数据传输的想法;虽然SqlBulkCopy看起来同样快,但它的灵活性却大大降低。

我在这里写了一篇关于这个主题的文章:http://www.altdevblogaday.com/2012/05/16/sql-server-high-performance-inserts/

总的答案是你大致想要创建一个表类型:

CREATE TYPE item_drop_bulk_table_rev4 AS TABLE (
    item_id BIGINT,
    monster_class_id INT,
    zone_id INT,
    xpos REAL,
    ypos REAL,
    kill_time datetime
)

然后,您创建一个存储过程以直接从table参数复制到实际表中,因此中间步骤较少:

CREATE PROCEDURE insert_item_drops_rev4
    @mytable item_drop_bulk_table_rev4 READONLY
AS

INSERT INTO item_drops_rev4 
    (item_id, monster_class_id, zone_id, xpos, ypos, kill_time)
SELECT 
    item_id, monster_class_id, zone_id, xpos, ypos, kill_time 
FROM 
    @mytable

后面的SQL Server代码如下所示:

DataTable dt = new DataTable();
dt.Columns.Add(new DataColumn("item_id", typeof(Int64)));
dt.Columns.Add(new DataColumn("monster_class_id", typeof(int)));
dt.Columns.Add(new DataColumn("zone_id", typeof(int)));
dt.Columns.Add(new DataColumn("xpos", typeof(float)));
dt.Columns.Add(new DataColumn("ypos", typeof(float)));
dt.Columns.Add(new DataColumn("timestamp", typeof(DateTime)));

for (int i = 0; i < MY_INSERT_SIZE; i++) {
    dt.Rows.Add(new object[] { item_id, monster_class_id, zone_id, xpos, ypos, DateTime.Now });
}

// Now we&#039;re going to do all the work with one connection!
using (SqlConnection conn = new SqlConnection(my_connection_string)) {
    conn.Open();
    using (SqlCommand cmd = new SqlCommand("insert_item_drops_rev4", conn)) {
        cmd.CommandType = CommandType.StoredProcedure;

        // Adding a "structured" parameter allows you to insert tons of data with low overhead
        SqlParameter param = new SqlParameter("@mytable", SqlDbType.Structured);
        param.Value = dt;
        cmd.Parameters.Add(param);
        cmd.ExecuteNonQuery();
    }
}

答案 4 :(得分:1)

一切都很慢。

前段时间我们解决了一个类似的问题(插入数以万计的价格数据,因为我记得每个时间帧大约有50K,我们有大约8个时间帧都发生冲突:00,所以它是关于400K记录)它对我们来说非常快(MS SQL 2005)。想象一下今天它将如何运作(SQL 2012):

<...init...>
if(bcp_init(m_hdbc, TableName, NULL, NULL, DB_IN) == FAIL)
    return FALSE;

int col_number = 1;

// Bind columns
if(bcp_bind(m_hdbc, (BYTE *)&m_sd.SymbolName, 0, 16, (LPCBYTE)"", 1, 0, col_number++) == FAIL) return FALSE;
if(bcp_bind(m_hdbc, (BYTE *)&m_sd.Time, 0, 4, 0, 0, 0, col_number++) == FAIL) return FALSE;
if(bcp_bind(m_hdbc, (BYTE *)&m_sd.Open, 0, 8, 0, 0, 0, col_number++) == FAIL) return FALSE;
if(bcp_bind(m_hdbc, (BYTE *)&m_sd.High, 0, 8, 0, 0, 0, col_number++) == FAIL) return FALSE;
if(bcp_bind(m_hdbc, (BYTE *)&m_sd.Low, 0, 8, 0, 0, 0, col_number++) == FAIL) return FALSE;
if(bcp_bind(m_hdbc, (BYTE *)&m_sd.Close, 0, 8, 0, 0, 0, col_number++) == FAIL) return FALSE;
if(bcp_bind(m_hdbc, (BYTE *)&m_sd.Volume, 0, 8, 0, 0, 0, col_number++) == FAIL) return FALSE;


<...save into sql...>
BOOL CSymbolStorage::Copy(SQL_SYMBOL_DATA *sd)
{
    if(!m_bUseDB)
        return TRUE;

    memcpy(&m_sd, sd, sizeof(SQL_SYMBOL_DATA));

    if(bcp_sendrow(m_hdbc) != SUCCEED)
        return FALSE;

    return TRUE;
}

答案 5 :(得分:0)

你能用水平分区吗? 请参阅:http://msdn.microsoft.com/en-us/library/ms178148.aspx&amp; http://msdn.microsoft.com/en-us/library/ms188706.aspx

您可能还想查看此问题,并可能更改恢复模型: Sql Server 2008 Tuning with large transactions (700k+ rows/transaction)

一些问题: 您使用的是哪个版本的SQL Server?

为什么一个核心为80%?这可能是瓶颈,所以可能值得研究。

你使用什么操作系统,是64位?