C# 从文本文件导入SQL Server数据库,ADO.NET是否太慢?

C# 从文本文件导入SQL Server数据库,ADO.NET是否太慢?,c#,sql-server,ado.net,C#,Sql Server,Ado.net,我的程序现在仍在运行,以便将数据从日志文件导入远程SQL Server数据库。日志文件的大小约为80MB,包含约470000行,以及约25000行数据。我的程序每秒只能导入300行,这太糟糕了( InsertData只是使用ADO.NET的普通插入方法。它使用解析方法: public Data(string strLine) { string[] list = strLine.Split(new[] {'\t'}); try { Senttime = Da

我的程序现在仍在运行,以便将数据从日志文件导入远程SQL Server数据库。日志文件的大小约为80MB,包含约470000行,以及约25000行数据。我的程序每秒只能导入300行,这太糟糕了(

InsertData只是使用ADO.NET的普通插入方法。它使用解析方法:

public Data(string strLine)
{
    string[] list = strLine.Split(new[] {'\t'});
    try
    {
        Senttime = DateTime.Parse(list[0] + " " + list[1]);
    }
    catch (Exception)
    {
    }

    Clientip = list[2];
    Clienthostname = list[3];

    Partnername = list[4];
    Serverhostname = list[5];
    Serverip = list[6];

    Recipientaddress = list[7];
    Eventid = Convert.ToInt16(list[8]);
    Msgid = list[9];
    Priority = Convert.ToInt16(list[10]);
    Recipientreportstatus = Convert.ToByte(list[11]);
    Totalbytes = Convert.ToInt32(list[12]);
    Numberrecipient = Convert.ToInt16(list[13]);
    DateTime temp;
    if (DateTime.TryParse(list[14], out temp))
    {
        OriginationTime = temp;
    }
    else
    {
        OriginationTime = null;
    }
    Encryption = list[15];
    ServiceVersion = list[16];
    LinkedMsgid = list[17];
    MessageSubject = list[18];
    SenderAddress = list[19];
}
InsertData方法:

private static void InsertData(string strLine, SqlConnection cn)
{
    var dt = new Data(strLine); //parse the log line into proper fields 
    const string cnnStr =
        "INSERT INTO LOGDATA ([SentTime]," + "[client-ip]," +
        "[Client-hostname]," + "[Partner-Name]," + "[Server-hostname]," +
        "[server-IP]," + "[Recipient-Address]," + "[Event-ID]," + "[MSGID]," +
        "[Priority]," + "[Recipient-Report-Status]," + "[total-bytes]," +
        "[Number-Recipients]," + "[Origination-Time]," + "[Encryption]," +
        "[service-Version]," + "[Linked-MSGID]," + "[Message-Subject]," +
        "[Sender-Address]) " + " VALUES (     " + "@Senttime," + "@Clientip," +
        "@Clienthostname," + "@Partnername," + "@Serverhostname," + "@Serverip," +
        "@Recipientaddress," + "@Eventid," + "@Msgid," + "@Priority," +
        "@Recipientreportstatus," + "@Totalbytes," + "@Numberrecipient," +
        "@OriginationTime," + "@Encryption," + "@ServiceVersion," +
        "@LinkedMsgid," + "@MessageSubject," + "@SenderAddress)";


    var cmd = new SqlCommand(cnnStr, cn) {CommandType = CommandType.Text};

    cmd.Parameters.AddWithValue("@Senttime", dt.Senttime);
    cmd.Parameters.AddWithValue("@Clientip", dt.Clientip);
    cmd.Parameters.AddWithValue("@Clienthostname", dt.Clienthostname);
    cmd.Parameters.AddWithValue("@Partnername", dt.Partnername);
    cmd.Parameters.AddWithValue("@Serverhostname", dt.Serverhostname);
    cmd.Parameters.AddWithValue("@Serverip", dt.Serverip);
    cmd.Parameters.AddWithValue("@Recipientaddress", dt.Recipientaddress);
    cmd.Parameters.AddWithValue("@Eventid", dt.Eventid);
    cmd.Parameters.AddWithValue("@Msgid", dt.Msgid);
    cmd.Parameters.AddWithValue("@Priority", dt.Priority);
    cmd.Parameters.AddWithValue("@Recipientreportstatus", dt.Recipientreportstatus);
    cmd.Parameters.AddWithValue("@Totalbytes", dt.Totalbytes);
    cmd.Parameters.AddWithValue("@Numberrecipient", dt.Numberrecipient);
    if (dt.OriginationTime != null)
        cmd.Parameters.AddWithValue("@OriginationTime", dt.OriginationTime);
    else
        cmd.Parameters.AddWithValue("@OriginationTime", DBNull.Value);
            //if OriginationTime was null, then insert with null value to this column
    cmd.Parameters.AddWithValue("@Encryption", dt.Encryption);
    cmd.Parameters.AddWithValue("@ServiceVersion", dt.ServiceVersion);
    cmd.Parameters.AddWithValue("@LinkedMsgid", dt.LinkedMsgid);
    cmd.Parameters.AddWithValue("@MessageSubject", dt.MessageSubject);
    cmd.Parameters.AddWithValue("@SenderAddress", dt.SenderAddress);
    cmd.ExecuteNonQuery();
}
我的程序如何运行得更快? 非常感谢!请使用

编辑:我创建了
IDataReader
的最小实现,并创建了
批处理
类型,这样我就可以使用
SqlBulkCopy
插入内存中的任意数据

IDataReader dr = batch.GetDataReader();
using (SqlTransaction tx = _connection.BeginTransaction())
{
    try
    {
        using (SqlBulkCopy sqlBulkCopy =
            new SqlBulkCopy(_connection, SqlBulkCopyOptions.Default, tx))
        {
            sqlBulkCopy.DestinationTableName = TableName;
            SetColumnMappings(sqlBulkCopy.ColumnMappings);
            sqlBulkCopy.WriteToServer(dr);
            tx.Commit();
        }
    }
    catch
    {
        tx.Rollback();
        throw;
    }
}
实现的其余部分留给读者作为练习:)


提示:您需要实现的
IDataReader
的唯一位是
Read
GetValue
FieldCount
Hmmm,让我们稍微细分一下

在伪代码中,您所做的是ff:

  • 打开文件
    • 打开连接
    • 对于包含数据的每一行:
    • 解析字符串
    • 将数据保存在SQL Server中
    • 关闭连接
    • 关闭文件
  • 这样做的基本问题是:

    • 在等待行解析时,您保持一个SQL连接处于打开状态(很容易受到超时等因素的影响)
    • 您可能正在逐行保存数据,每行保存在自己的事务中。除非您向我们展示
      InsertData
      方法在做什么,否则我们不会知道
    • 因此,在等待SQL完成插入时,您将保持文件的打开状态
    最好的方法是将文件作为一个整体进行解析,然后批量插入。您可以使用(如Matt Howells所建议的)或SQL Server集成服务来实现这一点


    如果您想继续使用ADO.NET,可以将INSERT语句集中在一起,然后将它们传递到一个大的SQLCommand中,而不是这样做,例如,为每个INSERT语句设置一个SQLCommand对象。

    为每行数据创建SQLCommand对象。因此,最简单的改进就是创建一个

    private static SqlCommand cmdInsert
    
    并使用parameters.Add()方法声明参数。然后,对于每个数据行,使用

    cmdInsert.Parameters["@paramXXX"].Value = valueXXX;
    

    第二个性能改进可能是跳过为每一行创建数据对象,直接从list[]数组分配参数值

    SQLBulkCopy是一个不错的选择。我曾经在SQL6.5/7.0中使用bcp从CSV导入数据,发现它非常快。SqlBulkCopy本质上与托管代码公开的功能相同。我的日志文件包含3行标题,需要2个字段来表示日期时间。我必须将它们组合起来才能转换为日期时间值。如何实现这一点?首先将文件转换为适合SqlBulkCopy的格式(从旧文件中读取行,将行写入新文件),然后使用SqlBulkCopy。您可以执行您正在执行的任何解析操作,将数据转换为
    对象[]
    ,该对象表示需要插入表中的数据行。然后使用
    SqlBulkCopy
    代替insert语句。
    cmdInsert.Parameters["@paramXXX"].Value = valueXXX;