Ускорить вставки LINQ - PullRequest
       22

Ускорить вставки LINQ

16 голосов
/ 25 августа 2009

У меня есть файл CSV, и я должен вставить его в базу данных SQL Server. Есть ли способ ускорить вставки LINQ?

Я создал простой метод репозитория для сохранения записи:

    public void SaveOffer(Offer offer)
    {
        Offer dbOffer = this.db.Offers.SingleOrDefault (
             o => o.offer_id == offer.offer_id);

        // add new offer
        if (dbOffer == null)
        {
            this.db.Offers.InsertOnSubmit(offer);
        }
        //update existing offer
        else
        {
            dbOffer = offer;
        }

        this.db.SubmitChanges();
    }

Но используя этот метод, программа работает намного медленнее, чем вставка данных с использованием ADO.net SQL вставок (новый SqlConnection, новый SqlCommand для выбора, если существует, новый SqlCommand для обновления / вставки).

В строках 100k csv путь ADO.net занимает около часа против 1 минуты или около того. Для 2M csv строк ADO.net потребовалось около 20 минут. LINQ добавил около 30 тыс. Из этих 2 млн строк за 25 минут. В моей базе данных есть 3 таблицы, связанные в dbml, но две другие таблицы пусты. Тесты проводились с пустыми таблицами.

P.S. Я пытался использовать SqlBulkCopy, но мне нужно сделать некоторые преобразования в Offer, прежде чем вставлять его в базу данных, и я думаю, что это противоречит цели SqlBulkCopy.

Обновление / редактирование: После 18 часов версия LINQ добавила всего ~ 200 тыс. Строк.

Я тестировал импорт только со вставками LINQ, и он также очень медленный по сравнению с ADO.net. Я не видел большой разницы между просто вставками / отправкими и выборами / обновлениями / вставками / отправками.

Мне все еще нужно попробовать пакетную фиксацию, вручную подключившись к базе данных и скомпилированным запросам.

Ответы [ 11 ]

19 голосов
/ 25 августа 2009

SubmitChanges не производит пакетные изменения, он выполняет один оператор вставки для каждого объекта. Если вы хотите делать быстрые вставки, я думаю, вам нужно прекратить использовать LINQ.

Во время выполнения SubmitChanges запустите SQL Profiler и посмотрите, как выполняется SQL.

См. Вопрос «Может ли LINQ to SQL выполнять пакетное обновление и удаление? Или всегда выполняется обновление по одной строке за раз?» здесь: http://www.hookedonlinq.com/LINQToSQLFAQ.ashx

Ссылка на эту статью: http://www.aneyfamily.com/terryandann/post/2008/04/Batch-Updates-and-Deletes-with-LINQ-to-SQL.aspx, которая использует методы расширения для исправления неспособности linq выполнять пакетные вставки и обновления и т. Д.

7 голосов
/ 25 августа 2009

Вы пытались обернуть вставки внутри транзакции и / или задержать db.SubmitChanges, чтобы вы могли пакетировать несколько вставок?

Транзакции помогают пропускной способности, уменьшая потребность в fsync () и задерживая db.SubmitChanges уменьшит количество .NET <-> db roundtrips.

Редактировать: см. http://www.sidarok.com/web/blog/content/2008/05/02/10-tips-to-improve-your-linq-to-sql-application-performance.html для некоторых дополнительных принципов оптимизации.

6 голосов
/ 01 декабря 2011

Посмотрите на следующей странице простой обзор того, как изменить код для использования групповой вставки вместо использования функции LINQ InsertOnSubmit () .

Вам просто нужно добавить (предоставленный) класс BulkInsert в ваш код, внести несколько тонких изменений в ваш код, и вы увидите значительное улучшение производительности.

База знаний Mikes - BulkInserts с LINQ

Удачи!

4 голосов
/ 26 августа 2009

Алекс дал лучший ответ, но я думаю, что некоторые вещи просматриваются.

Одним из основных узких мест, которые у вас есть, является вызов SubmitChanges для каждого элемента в отдельности. Проблема, о которой большинство людей не знают, заключается в том, что если вы сами не открывали соединение DataContext самостоятельно, то DataContext будет многократно открывать и закрывать его. Однако, если вы откроете его сами, а затем закроете сами, когда закончите, все будет работать намного быстрее, так как ему не придется каждый раз подключаться к базе данных. Я выяснил это, пытаясь выяснить, почему DataContext.ExecuteCommand () был настолько невероятно медленным при выполнении нескольких команд одновременно.

Несколько других областей, где вы могли бы ускорить процесс:

Хотя Linq To SQL не поддерживает прямую пакетную обработку, вам следует подождать вызова SubmitChanges (), пока вы сначала не проанализируете все. Вам не нужно вызывать метод SubmitChanges () после каждого вызова InsertOnSubmit.

Если целостность данных в реальном времени не является супер-важной, вы можете получить список offer_id обратно с сервера, прежде чем начинать проверять, существует ли предложение. Это может значительно сократить количество обращений к серверу для получения существующего элемента, даже если его там нет.

4 голосов
/ 25 августа 2009

Интересно, страдаете ли вы от чрезмерно большого набора данных, накапливающихся в контексте данных, что замедляет разрешение строк во внутреннем кэше идентификаторов (который проверяется один раз во время SingleOrDefault, и для «промахов» «Я ожидаю увидеть второй удар, когда сущность материализуется).

Я не могу вспомнить 100%, работает ли короткое замыкание на SingleOrDefault (хотя будет в .NET 4.0 ).

Я бы попытался исключить контекст данных (отправить изменения и заменить на пустой) каждые n операций для некоторых n - возможно, 250 или чего-то еще.


Учитывая, что вы звоните SubmitChanges за isntance на данный момент, вы также можете тратить много времени на проверку дельты - бессмысленно, если вы изменили только одну строку. Звоните SubmitChanges только партиями; не за запись.

3 голосов
/ 25 августа 2009

Почему бы не передать предложение [] в этот метод и выполнить все изменения в кэше перед отправкой их в базу данных.Или вы можете использовать группы для отправки, чтобы у вас не заканчивался кэш.Главное, как долго вы будете пересылать данные, самое большое время тратится на закрытие и открытие соединения.

2 голосов
/ 25 августа 2009

Вам действительно нужно проверить, существует ли запись, прежде чем вставлять ее в БД. Я думал, что это выглядит странно, поскольку данные поступают из CSV-файла.

* 1003 P.S *. Я пытался использовать SqlBulkCopy, но мне нужно сделать некоторые преобразования на предложение, прежде чем вставить его в дб, и я думаю, что побеждает назначение SqlBulkCopy.

Я не думаю, что это вообще победит цель, с чего бы это? Просто заполните простой набор данных всеми данными из CSV и сделайте SqlBulkCopy. Я сделал то же самое с коллекцией из 30000+ строк, и время импорта изменилось с минут на секунды

2 голосов
/ 25 августа 2009

Преобразование этого в скомпилированный запрос - самый простой способ повысить производительность здесь:

Изменить следующее:

    Offer dbOffer = this.db.Offers.SingleOrDefault (
         o => o.offer_id == offer.offer_id);

до:

Offer dbOffer = RetrieveOffer(offer.offer_id);

private static readonly Func<DataContext, int> RetrieveOffer
{
   CompiledQuery.Compile((DataContext context, int offerId) => context.Offers.SingleOrDefault(o => o.offer_id == offerid))
}

Это изменение само по себе не сделает его таким же быстрым, как ваша версия ado.net, но оно будет значительным улучшением, поскольку без скомпилированного запроса вы динамически создаете дерево выражений при каждом запуске этого метода.

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

1 голос
/ 25 августа 2009

Я подозреваю, что это не операции вставки или обновления, которые занимают много времени, а код, который определяет, существует ли уже ваше предложение:

Offer dbOffer = this.db.Offers.SingleOrDefault (
         o => o.offer_id == offer.offer_id);

Если вы хотите оптимизировать это, я думаю, вы на правильном пути. Возможно, используйте класс «Секундомер», чтобы рассчитать время, которое поможет доказать, что я прав или нет.

Обычно, когда вы не используете Linq-to-Sql, у вас будет процедура вставки / обновления или сценарий sql, который определит, существует ли уже переданная вами запись. Вы выполняете эту дорогостоящую операцию в Linq, которая, безусловно, никогда не будет рассчитывать на совпадение со скоростью собственного sql (что происходит, когда вы используете SqlCommand и выбираете, существует ли запись) поиска первичного ключа.

0 голосов
/ 24 сентября 2010

Этот код работает нормально и предотвращает большие объемы данных:

if (repository2.GeoItems.GetChangeSet().Inserts.Count > 1000)
{
    repository2.GeoItems.SubmitChanges();
}

Затем, в конце массовой вставки, используйте это:

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