Очень медленный процесс вставки с использованием Linq to Sql - PullRequest
9 голосов
/ 20 сентября 2010

Я вставляю большое количество записей с помощью LinqToSql из C # в SqlServer 2008 Express DB. Похоже, вставка очень медленно в этом. Ниже приведен фрагмент кода.

public void InsertData(int id)
{

  MyDataContext dc = new MyDataContext();

  List<Item> result = GetItems(id);

  foreach (var item in result)
  {
    DbItem dbItem = new DbItem(){ItemNo = item.No, ItemName=item.Name};
    dc.Items.InsertOnSubmit();
  }

  dc.SubmitChanges();
}

Я что-то не так делаю? Или использование Linq для вставки большого количества записей - плохой выбор?

Обновление: спасибо за все ответы. @ p.campbell: Извините за количество записей, это была опечатка, на самом деле она составляет около 100000. Записи также варьируются до 200 тысяч.

Согласно всем предложениям, я переместил эту операцию на части (также изменение требований и проектное решение) и извлекал данные небольшими порциями и вставлял их в базу данных по мере необходимости. Я поместил этот метод InsertData () в работу с потоками и теперь использую SmartThreadPool для создания пула из 25 потоков для выполнения той же операции. В этом сценарии я вставляю за раз только 100 записей. Теперь, когда я попробовал это с помощью Linq или SQL-запроса, это не имело никакого значения с точки зрения затраченного времени.

Согласно моему требованию, эта операция запланирована на каждый час и собирает записи для примерно 4k-6k пользователей. Итак, теперь я объединяю каждую операцию с пользовательскими данными (извлечение и вставку в БД) как одну задачу и назначаю ее одному потоку. Теперь весь этот процесс занимает около 45 минут для записи около 250 тыс.

Есть ли лучший способ выполнить такую ​​задачу? Или кто-нибудь может подсказать, как мне улучшить этот процесс?

Ответы [ 4 ]

12 голосов
/ 20 сентября 2010

Для вставки огромного количества данных в SQL в одном

Linq или SqlCommand, не предназначены для массового копирования данных в SQL .

Вы можете использовать класс SqlBulkCopy , который обеспечивает управляемый доступ к утилите bcp для массовой загрузки данных в Sql практически из любого источника данных.

Можно использовать класс SqlBulkCopyзаписывать данные только в таблицы SQL Server.Однако источник данных не ограничивается SQL Server;может использоваться любой источник данных, если данные могут быть загружены в экземпляр DataTable или считаны с экземпляром IDataReader.

Сравнение производительности

SqlBulkCopyявляется самым быстрым, даже при загрузке данных из простого файла CSV.

Linq просто сгенерирует загрузку Insert операторов в SQL и отправит их на ваш SQL Server.Это ничем не отличается от того, что вы используете специальные запросы с SqlCommand.Производительность SqlCommand и Linq практически идентична.

Доказательство

(SQL Express 2008, .Net 4.0)

SqlBulkCopy

Использование SqlBulkCopy для загрузки 100000 строк из файла CSV (включая загрузку данных)

using (SqlConnection conn = new SqlConnection("Integrated Security=SSPI;Persist Security Info=False;Initial Catalog=EffectCatalogue;Data Source=.\\SQLEXPRESS;"))
{
    conn.Open();
    Stopwatch watch = Stopwatch.StartNew();

    string csvConnString = "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=C:\\data\\;Extended Properties='text;'";
    OleDbDataAdapter oleda = new OleDbDataAdapter("SELECT * FROM [test.csv]", csvConnString);
    DataTable dt = new DataTable();
    oleda.Fill(dt);

    using (SqlBulkCopy copy = new SqlBulkCopy(conn))
    {
        copy.ColumnMappings.Add(0, 1);
        copy.ColumnMappings.Add(1, 2);
        copy.DestinationTableName = "dbo.Users";
        copy.WriteToServer(dt);
    }
    Console.WriteLine("SqlBulkCopy: {0}", watch.Elapsed);
}

SqlCommand

using (SqlConnection conn = new SqlConnection("Integrated Security=SSPI;Persist Security Info=False;Initial Catalog=TestDb;Data Source=.\\SQLEXPRESS;"))
{
    conn.Open();
    Stopwatch watch = Stopwatch.StartNew();
    SqlCommand comm = new SqlCommand("INSERT INTO Users (UserName, [Password]) VALUES ('Simon', 'Password')", conn);
    for (int i = 0; i < 100000; i++)
    {
        comm.ExecuteNonQuery();
    }
    Console.WriteLine("SqlCommand: {0}", watch.Elapsed);
}

LinqToSql

using (SqlConnection conn = new SqlConnection("Integrated Security=SSPI;Persist Security Info=False;Initial Catalog=TestDb;Data Source=.\\SQLEXPRESS;"))
{
    conn.Open();
    Stopwatch watch = Stopwatch.StartNew();
    EffectCatalogueDataContext db = new EffectCatalogueDataContext(conn);
    for (int i = 0; i < 100000; i++)
    {
        User u = new User();
        u.UserName = "Simon";
        u.Password = "Password";
        db.Users.InsertOnSubmit(u);
    }
    db.SubmitChanges();
    Console.WriteLine("Linq: {0}", watch.Elapsed);
}

Результаты

SqlBulkCopy: 00:00:02.90704339
SqlCommand: 00:00:50.4230604
Linq: 00:00:48.7702995
3 голосов
/ 20 сентября 2010

У вас есть SubmitChanges(), который вызывается один раз, и это хорошо.Это означает, что используются только одно соединение и транзакция.

Рассмотрите возможность рефакторинга вашего кода для использования InsertAllOnSubmit().

List<dbItem> newItems = GetItems(id).Select(x=> new DbItem{ItemNo = x.No,
                                                           ItemName=x.Name})
                                    .ToList();
db.InsertAllOnSubmit(newItems);
dc.SubmitChanges();

Операторы INSERT отправляются один за другим, как и предыдущие, но, возможно, это может быть более читабельным?

Некоторые другие вопросы, которые нужно спросить / рассмотреть:

  • Каково состояние индексов на целевой таблице?Слишком много замедлит запись.* Является ли база данных моделью простого или полного восстановления?
  • Захват операторов SQL, проходящих по проводам.Воспроизведите эти операторы в специальном запросе к вашей базе данных SQL Server.Я понимаю, что вы используете SQL Express и, вероятно, не имеете SQL Profiler.Используйте context.Log = Console.Out; для вывода ваших операторов LINQ To SQL на консоль .Однако для удобства предпочтите SQL Profiler.
  • Работают ли захваченные операторы SQL так же, как ваш клиентский код?Если это так, то проблема перфорации находится на стороне базы данных.
3 голосов
/ 20 сентября 2010

если вы вставляете большую запись данных, вы можете попробовать с помощью BULK INSERT .

Насколько мне известно, в Linq to SQL нет эквивалента массовой вставки.

1 голос
/ 30 ноября 2011

Вот хороший обзор того, как добавить класс Bulk-Insert в ваше приложение, что значительно повышает производительность вставки записей с помощью LINQ.

(весь исходный код предоставлен, готов к добавлению в ваше собственное приложение.)

http://www.mikesknowledgebase.com/pages/LINQ/InsertAndDeletes.htm

Вам просто нужно внести три изменения в свой код,и ссылка в предоставленном классе.Удачи!

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...