импорт из текстового файла в базу данных SQL Server, слишком медленно ADO.NET?

2

Моя программа все еще работает для импорта данных из файла журнала в удаленную базу данных SQL Server. Файл журнала имеет размер около 80 МБ и содержит около 470000 строк, из которых около 25000 строк данных. Моя программа может импортировать только 300 строк в секунду, что очень плохо.: (

public static int ImportData(string strPath)
{
    //NameValueCollection collection = ConfigurationManager.AppSettings;

    using (TextReader sr = new StreamReader(strPath))
    {
        sr.ReadLine(); //ignore three first lines of log file
        sr.ReadLine();
        sr.ReadLine();
        string strLine;
        var cn = new SqlConnection(ConnectionString);
        cn.Open();

        while ((strLine = sr.ReadLine()) != null)
        {
            {
                if (strLine.Trim() != "") //if not a blank line, then import into database
                {
                    InsertData(strLine, cn);
                    _count++;
                }
            }
        }
        cn.Close();
        sr.Close();

        return _count;
    }
}

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();
}

Как моя программа может работать быстрее? Большое вам спасибо!

Теги:
sql-server
ado.net

3 ответа

13
Лучший ответ

Используйте SqlBulkCopy.

Изменить: я создал минимальную реализацию IDataReader и создал тип Batch, чтобы я мог вставлять произвольные данные в памяти с помощью 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.

  • 2
    SQLBulkCopy это путь. Раньше я использовал bcp в SQL 6.5 / 7.0 дней для импорта данных из CSV и обнаружил, что это невероятно быстро. SqlBulkCopy - это та же функциональность, что и управляемый код.
  • 0
    Мой файл журнала содержит 3 три строки заголовка, и ему нужно 2 поля для выражения даты и времени. Я должен объединить их, чтобы преобразовать в значение Datetime. Как я могу это сделать?
Показать ещё 2 комментария
4

Хм, пусть немного сломается.

В псевдокоде, что вы сделали, является ff:

  • Откройте файл
    • Открыть соединение
    • Для каждой строки с данными:
    • Разбор строки
    • Сохранить данные в SQL Server
    • Закрыть соединение
    • Закройте файл

Теперь основные проблемы при этом:

  • Вы сохраняете открытое соединение SQL в ожидании вашего синтаксического анализа строки (довольно восприимчивы к тайм-аутам и т.д.).
  • Возможно, вы сохраняете данные по строкам, каждый в своей транзакции. Мы не узнаем, пока вы не покажете нам, что делает InsertData метод
  • Следовательно, вы сохраняете файл открытым, ожидая, когда SQL завершит вставку

Оптимальный способ сделать это - проанализировать файл в целом, а затем вставить его в массе. Вы можете сделать это с помощью SqlBulkCopy (как это предложил Мэтт Хауэллс) или с помощью служб интеграции SQL Server.

Если вы хотите придерживаться ADO.NET, вы можете объединить свои инструкции INSERT и затем передать их в один большой SQLCommand, вместо того, чтобы делать это таким образом, например, для создания одного объекта SQLCommand в инструкции вставки.

2

Вы создаете объект SqlCommand для каждой строки данных. Поэтому самым простым улучшением было бы создание

private static SqlCommand cmdInsert

и объявить параметры с помощью метода Parameters.Add(). Затем для каждой строки данных задайте значения параметров, используя

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

Вторым улучшением производительности может быть пропустить создание объектов данных для каждой строки и назначить значения параметров непосредственно из массива list [].

  • 0
    Спасибо. Я попытаюсь! :)

Ещё вопросы

Сообщество Overcoder
Наверх
Меню