Ускорьте вставки LINQ
У меня есть файл 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-строках это занимает около часа против 1 минуты или около того для пути ADO.net. Для строк 2M csv требуется ADO.net около 20 минут. LINQ добавил около 30 тыс. Из этих 2М строк за 25 минут. В моей базе данных есть 3 таблицы, связанные в dbml, но две другие таблицы пусты. Тесты были выполнены со всеми пустыми таблицами.
P.S. Я пытался использовать SqlBulkCopy, но мне нужно сделать некоторые преобразования в предложении, прежде чем вставлять их в db, и я думаю, что это побеждает цель SqlBulkCopy.
Обновление/редактирование:
После 18 часов версия LINQ добавила всего ~ 200 тыс. Строк.
Я тестировал импорт только с вставками LINQ, а также очень медленно по сравнению с ADO.net. Я не видел большой разницы между только вставками/подписями и выборами/обновлениями/вставками/подчинениями.
Мне еще нужно попробовать пакетное коммит, вручную подключившись к db и скомпилированным запросам.
Ответы
Ответ 1
SubmitChanges не выполняет пакетные изменения, он делает один оператор insert для каждого объекта. Если вы хотите делать быстрые вставки, я думаю, вам нужно прекратить использовать 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 для пакетных вставок и обновлений и т.д.
Ответ 2
Вы пробовали обернуть вставки в транзакции и/или задержать db.SubmitChanges, чтобы вы могли выполнять несколько вложений?
Транзакции помогают повысить пропускную способность, уменьшая потребности в fsync() и задерживая db.SubmitChanges уменьшит количество обращений в .NET ↔ db.
Изменить: см. http://www.sidarok.com/web/blog/content/2008/05/02/10-tips-to-improve-your-linq-to-sql-application-performance.html для некоторых других принципов оптимизации.
Ответ 3
Посмотрите на следующую страницу для простого ознакомления с тем, как изменить свой код на использование массовой вставки вместо функции LINQ InsertOnSubmit().
Вам просто нужно добавить класс BulkInsert в свой код, внести несколько незначительных изменений в ваш код, и вы увидите значительное улучшение производительности.
База знаний Mikes - BulkInserts с LINQ
Удачи!
Ответ 4
Интересно, страдает ли вы чрезмерно большой набор данных, накапливающихся в контексте данных, заставляя его медленно разрешать строки против внутреннего кеша идентификации (который проверяется один раз во время SingleOrDefault
, а для "промахов" "Я ожидаю увидеть второй удар, когда материализуется сущность.)
Я не могу вспомнить 100%, работает ли короткое замыкание на SingleOrDefault
(хотя он будет в .NET 4.0).
Я бы попробовал перетащить контекст данных (отправить-изменить и заменить пустым) каждые n операций для некоторого n - может 250 или что-то.
Учитывая, что вы на данный момент вызываете SubmitChanges
per isntance, вы также можете тратить много времени на проверку дельта - бессмысленно, если вы только изменили одну строку. Вызывайте только SubmitChanges
партиями; не на запись.
Ответ 5
Алекс дал лучший ответ, но я думаю, что кое-что еще просматривается.
Одним из основных узких мест, которые у вас здесь, является вызов SubmitChanges для каждого элемента отдельно. Проблема, о которой я не думаю, что большинство людей знают о том, что если вы вручную не открыли свое соединение DataContext, то DataContext будет многократно открывать и закрывать его сам. Однако, если вы откроете его самостоятельно, а затем закройте его самостоятельно, когда вы полностью закончите, все будет работать намного быстрее, так как ему не придется каждый раз подключаться к базе данных. Я узнал об этом, пытаясь выяснить, почему DataContext.ExecuteCommand() был настолько невероятно медленным при одновременном выполнении нескольких команд.
Несколько других областей, где вы могли бы ускорить работу:
В то время как Linq To SQL не поддерживает вашу прямолинейную пакетную обработку, вам нужно подождать, чтобы вызвать SubmitChanges(), пока вы сначала не проанализируете все. Вам не нужно вызывать SubmitChanges() после каждого вызова InsertOnSubmit.
Если целостность данных в реальном времени не является чрезвычайно важной, вы можете получить список offer_id обратно с сервера, прежде чем вы начнете проверять, существует ли предложение. Это может значительно сократить количество раз, когда вы вызываете сервер для получения существующего элемента, когда он даже не существует.
Ответ 6
Почему бы не передать предложение [] в этот метод и выполнить все изменения в кеше, прежде чем отправлять их в базу данных. Или вы можете использовать группы для отправки, поэтому у вас не хватает кеша. Главное, как долго вы будете отправлять данные, самое большое время тратится на закрытие и открытие соединения.
Ответ 7
Преобразование этого в скомпилированный запрос - самый простой способ, который я могу придумать, чтобы повысить вашу производительность здесь:
Измените следующее:
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, но это будет значительным улучшением, потому что без скомпилированного запроса вы динамически строите дерево выражений каждый раз, когда вы запускаете этот метод.
Как уже упоминалось в одном плакате, вы должны реорганизовать свой код, чтобы отправлять изменения вызываются только один раз, если вам нужна оптимальная производительность.
Ответ 8
Вам действительно нужно проверить, существует ли запись, прежде чем вставлять ее в БД. Я думал, что это выглядит странно, поскольку данные поступают из файла csv.
P.S. Я пытался использовать SqlBulkCopy, но мне нужно сделать некоторые преобразования по предложению, прежде чем вставлять его в дБ, и я думаю, что это побеждает цель SqlBulkCopy.
Я не думаю, что это вообще побеждает цель, почему? Просто заполните простой набор данных со всеми данными из csv и выполните SqlBulkCopy. Я сделал аналогичную вещь с коллекцией из 30000+ строк, и время импорта прошло от нескольких минут до нескольких секунд.
Ответ 9
Я подозреваю, что это не операции вставки или обновления, которые занимают много времени, а код, который определяет, существует ли ваше предложение:
Offer dbOffer = this.db.Offers.SingleOrDefault (
o => o.offer_id == offer.offer_id);
Если вы попытаетесь оптимизировать это, я думаю, вы будете на правильном пути. Возможно, используйте класс секундомера для выполнения определенных сроков, которые помогут доказать, что я прав или не прав.
Обычно, когда вы не используете Linq-to-Sql, у вас будет процедура вставки/обновления или sql script, которая будет определять, существует ли уже прошедшая запись. Вы выполняете эту дорогостоящую операцию в Linq, которая, безусловно, никогда не надеется соответствовать скорости собственного sql (что и происходит, когда вы используете SqlCommand и выбираете, существует ли запись), просматривая первичный ключ.
Ответ 10
Ну, вы должны понимать, что linq динамически создает код для всех операций ADO, которые вы делаете вместо рукописного, поэтому всегда будет занимать больше времени, чем ваш ручной код. Его простой способ писать код, но если вы хотите говорить о производительности, код ADO.NET всегда будет быстрее в зависимости от того, как вы его пишете.
Я не знаю, будет ли linq пытаться повторно использовать свой последний оператор или нет, если это произойдет, то разделение вставки с пакетом обновления может немного улучшить производительность.
Ответ 11
Этот код работает нормально и предотвращает большие объемы данных:
if (repository2.GeoItems.GetChangeSet().Inserts.Count > 1000)
{
repository2.GeoItems.SubmitChanges();
}
Затем, в конце объемной вставки, используйте это:
repository2.GeoItems.SubmitChanges();