none
Запрос через Linq выполняется очень долго

    Question

  • Здравствуйте!

    Начал использовать Linq, со многим еще надо разбираться. Такая проблема.

    Есть БД и таблица c ценами акций (TICKER - DATE - PRICE) . В ней где-то 2 млн записей.

    Простая задача - посмотреть, данные по каким акциям и годам имеются. А также для всего года или его части.

    Код в SQL Server Management Studio выполняется 2 секунды.

    SELECT TICKER, YEAR(DATE), MIN(DATE), MAX(DATE)
     FROM [Prices].[dbo].[MICEX]
     GROUP BY TICKER, YEAR(DATE)
     ORDER BY TICKER, YEAR(DATE) desc
    

    Из C# проги с использованием LINQ запрос выполняется секунд 40. (Даже в таком урезанном виде)

    string s = "";
    PricesDataContext PricesDB = new PricesDataContext();
    var info = from i in PricesDB.MICEXes
            group i by new { i.TICKER, i.DATE.Year };
    foreach (var i in info)
    {
       s += i.Key.TICKER + " " + i.Key.Year + "\n";
    }
    MessageBox.Show(s);
    
     В чем кроется неэффективность? Ведь не должно же быть такой разницы.

    Saturday, March 26, 2011 10:01 PM

Answers

  • Скорее всего, вы столкнулись с проблемой SELECT N + 1  - когда в foreach выполняется обращение к связанным свойствам (i.Key в вашем примере), то для каждой итерации будет выполнет отдельный запрос в БД. Это особенность работы Lazy Loading в Linq.

    Решение состоит в том, чтобы грузить связанные свойства либо с помощью расширяющего метода Include в построении запроса, либо строить в запросе select так, чтобы он загружал нужные параметры связанных объектов, после чего материализовать все это методом ToList() (второй ответ в теме).

    Если не требуется абстрагироваться от DataContext, то еще можно воспользоваться методом LoadProperty.




    • Proposed as answer by Naomi N Monday, March 28, 2011 2:46 PM
    • Marked as answer by Abolmasov Dmitry Tuesday, March 29, 2011 8:01 AM
    Monday, March 28, 2011 7:18 AM
  • Насколько я понимаю этот код, он не делает группировку на уровне SQL Server. Посмотрите профайлером какой SQL на самом деле выполняется, думаю что это полная выборка всех записей таблицы (запрос пойдёт на сервер только когда начнётся foreach). Для генерации правильного SQL попробуйте явно прописать что вы будете доставать (не всю сущность, а TICKER, YEAR(DATE), MIN(DATE), MAX(DATE))

    Получится что-то вида:  

    var info = from i in PricesDB.MICEXes
        group i by new { i.TICKER, i.DATE.Year } into g
        select new{
          Ticker = g.Key.TICKER,
          Year = g.Key.Year
        };
    
    

     Потом уже по этой коллекции foreach. Это должно сработать, но на всякий сллучай проверьте профайлером какой запрос получится. 

    • Proposed as answer by Naomi N Monday, March 28, 2011 2:46 PM
    • Marked as answer by Abolmasov Dmitry Tuesday, March 29, 2011 8:02 AM
    Monday, March 28, 2011 2:32 PM

All replies

  • s += i.Key.TICKER + " " + i.Key.Year + "\n";

    Вот эта операция не самая быстрая. Проверьте скорость именно LINQ запроса и убедитесь, что это он так долго выполняется. Для этого у info нужно будет вызвать ToList() и замерить время выполнения этого метода. Так же можно посмотреть какой запрос отправляется на сервер в итоге - возможно генерится что-либо лишнее.

     

    Monday, March 28, 2011 6:44 AM
  • Скорее всего, вы столкнулись с проблемой SELECT N + 1  - когда в foreach выполняется обращение к связанным свойствам (i.Key в вашем примере), то для каждой итерации будет выполнет отдельный запрос в БД. Это особенность работы Lazy Loading в Linq.

    Решение состоит в том, чтобы грузить связанные свойства либо с помощью расширяющего метода Include в построении запроса, либо строить в запросе select так, чтобы он загружал нужные параметры связанных объектов, после чего материализовать все это методом ToList() (второй ответ в теме).

    Если не требуется абстрагироваться от DataContext, то еще можно воспользоваться методом LoadProperty.




    • Proposed as answer by Naomi N Monday, March 28, 2011 2:46 PM
    • Marked as answer by Abolmasov Dmitry Tuesday, March 29, 2011 8:01 AM
    Monday, March 28, 2011 7:18 AM
  • Насколько я понимаю этот код, он не делает группировку на уровне SQL Server. Посмотрите профайлером какой SQL на самом деле выполняется, думаю что это полная выборка всех записей таблицы (запрос пойдёт на сервер только когда начнётся foreach). Для генерации правильного SQL попробуйте явно прописать что вы будете доставать (не всю сущность, а TICKER, YEAR(DATE), MIN(DATE), MAX(DATE))

    Получится что-то вида:  

    var info = from i in PricesDB.MICEXes
        group i by new { i.TICKER, i.DATE.Year } into g
        select new{
          Ticker = g.Key.TICKER,
          Year = g.Key.Year
        };
    
    

     Потом уже по этой коллекции foreach. Это должно сработать, но на всякий сллучай проверьте профайлером какой запрос получится. 

    • Proposed as answer by Naomi N Monday, March 28, 2011 2:46 PM
    • Marked as answer by Abolmasov Dmitry Tuesday, March 29, 2011 8:02 AM
    Monday, March 28, 2011 2:32 PM
  • Спасибо за ответы! Я сам не совсем понимал, что делал) Теперь начал изучать LINQ подробно.

    В описанном запросе, как написал Denis Reznik, делалась полная выборка. Это и было главной проблемой.

    Но и советы, связанные с проблемой SELECT N+1 буду иметь ввиду.

    Sunday, April 03, 2011 3:36 PM