Почему оператор Contains() так резко снижает производительность Entity Framework?

ОБНОВЛЕНИЕ 3: согласно это объявление, это было рассмотрено командой EF в EF6 alpha 2.

ОБНОВЛЕНИЕ 2: я создал предложение по устранению этой проблемы. Чтобы проголосовать за него, перейдите сюда.

Рассмотрим базу данных SQL с одной очень простой таблицей.

CREATE TABLE Main (Id INT PRIMARY KEY)

Я заполняю таблицу 10 000 записей.

WITH Numbers AS
(
  SELECT 1 AS Id
  UNION ALL
  SELECT Id + 1 AS Id FROM Numbers WHERE Id <= 10000
)
INSERT Main (Id)
SELECT Id FROM Numbers
OPTION (MAXRECURSION 0)

Я создаю модель EF для таблицы и запускаю следующий запрос в LINQPad (я использую режим «Выражения C#», поэтому LINQPad не создает дамп автоматически).

var rows = 
  Main
  .ToArray();

Время выполнения ~0,07 секунды. Теперь я добавляю оператор Contains и повторно запускаю запрос.

var ids = Main.Select(a => a.Id).ToArray();
var rows = 
  Main
  .Where (a => ids.Contains(a.Id))
  .ToArray();

Время выполнения для этого случая составляет 20,14 секунды (в 288 раз медленнее)!

Сначала я подозревал, что T-SQL, сгенерированный для запроса, выполнялся дольше, поэтому я попытался вырезать и вставить его из панели SQL LINQPad в SQL Server Management Studio.

SET NOCOUNT ON
SET STATISTICS TIME ON
SELECT 
[Extent1].[Id] AS [Id]
FROM [dbo].[Primary] AS [Extent1]
WHERE [Extent1].[Id] IN (1,2,3,4,5,6,7,8,...

И результат был

SQL Server Execution Times:
  CPU time = 0 ms,  elapsed time = 88 ms.

Затем я подозревал, что проблема вызвана LINQPad, но производительность одинакова, запускаю ли я его в LINQPad или в консольном приложении.

Итак, похоже, что проблема где-то внутри Entity Framework.

Я делаю что-то не так здесь? Это критическая по времени часть моего кода, так что я могу сделать, чтобы повысить производительность?

Я использую Entity Framework 4.1 и Sql Server 2008 R2.

ОБНОВЛЕНИЕ 1:

В приведенном ниже обсуждении возник ряд вопросов о том, произошла ли задержка во время построения исходного запроса EF или во время анализа полученных данных. Чтобы проверить это, я запустил следующий код:

var ids = Main.Select(a => a.Id).ToArray();
var rows = 
  (ObjectQuery<MainRow>)
  Main
  .Where (a => ids.Contains(a.Id));
var sql = rows.ToTraceString();

что заставляет EF генерировать запрос, не выполняя его для базы данных. В результате для выполнения этого кода потребовалось ~20 секунд, так что кажется, что почти все время уходит на построение исходного запроса.

CompiledQuery спешит на помощь? Не так быстро... CompiledQuery требует, чтобы параметры, передаваемые в запрос, были фундаментальными типами (int, string, float и т. д.). Он не будет принимать массивы или IEnumerable, поэтому я не могу использовать его для списка идентификаторов.


person Mike    schedule 26.10.2011    source источник
comment
Пробовали ли вы var qry = Main.Where (a => ids.Contains(a.Id)); var rows = qry.ToArray(); посмотреть, какая часть запроса занимает время?   -  person Andrew Cooper    schedule 26.10.2011
comment
это не EF, который ухудшает ваш запрос, это фактический запрос, который вы пытаетесь выполнить; не могли бы вы объяснить, что вы пытаетесь сделать? возможно, есть лучший подход к вашим потребностям   -  person Kris Ivanov    schedule 26.10.2011
comment
@AndrewCooper Я только что попробовал, и из-за отложенного выполнения первый оператор (без ToArray) выполняется почти мгновенно. Запрос, включая фильтрацию «Содержит», на самом деле не выполняется, пока вы не выполните ToArray().   -  person Mike    schedule 26.10.2011
comment
@KrisIvanov У меня есть база данных с более чем 500 000 записей, и мне нужно выбрать 1000 из них за раз, основываясь только на их идентификаторах. Я мог бы выполнить 1000 отдельных запросов, но какой-то подход, который возвращает их все за один цикл, вероятно, был бы более эффективным... по крайней мере, я так думал, прежде чем столкнулся с проблемой выше. :)   -  person Mike    schedule 26.10.2011
comment
@Mike, эти 1000 идентификаторов случайны или следуют какой-то последовательности, например 10 <= ids <= 500?   -  person Kris Ivanov    schedule 26.10.2011
comment
Просто обновите информацию об этом: EF6 alpha 2 включает улучшение, которое ускоряет перевод Enumerable.Contains. См. объявление здесь: blogs.msdn.com/b/adonet/archive/2012/12/10/. Мои собственные тесты показывают, что перевод list.Contains(x) для списка со 100 000 элементов int теперь занимает менее секунды, и время растет примерно линейно с количеством элементов в списке. Спасибо за ваш отзыв и за помощь в улучшении EF!   -  person divega    schedule 11.12.2012
comment
Остерегайтесь этого... запросы с любым параметром IEnumerable не могут быть кэшированы, что может вызвать довольно серьезные побочные эффекты, когда ваши планы запросов сложны. Если вам приходится запускать операции много раз (например, используя Contains для получения фрагментов данных), у вас может быть довольно неприятное время перекомпиляции запроса! Проверьте источник сами, и вы увидите, что parent._recompileRequired = () => true; происходит для всех запросов, содержащих параметр IEnumerable‹T›. Бу!   -  person jocull    schedule 15.11.2013


Ответы (8)


ОБНОВЛЕНИЕ. С добавлением InExpression в EF6 производительность обработки Enumerable.Contains значительно улучшилась. Подход, описанный в этом ответе, больше не нужен.

Вы правы, что больше всего времени уходит на обработку перевода запроса. Модель поставщика EF в настоящее время не включает выражение, представляющее предложение IN, поэтому поставщики ADO.NET не могут изначально поддерживать IN. Вместо этого реализация Enumerable.Contains переводит его в дерево выражений ИЛИ, т.е. для чего-то, что в C# выглядит так:

new []{1, 2, 3, 4}.Contains(i)

... мы создадим дерево DbExpression, которое можно представить следующим образом:

((1 = @i) OR (2 = @i)) OR ((3 = @i) OR (4 = @i))

(Деревья выражений должны быть сбалансированы, потому что, если бы у нас были все OR на одном длинном позвоночнике, было бы больше шансов, что посетитель выражения столкнется с переполнением стека (да, мы действительно столкнулись с этим в нашем тестировании))

Позже мы отправим подобное дерево поставщику ADO.NET, который сможет распознать этот шаблон и свести его к предложению IN во время генерации SQL.

Когда мы добавили поддержку Enumerable.Contains в EF4, мы подумали, что было бы желательно сделать это без поддержки выражений IN в модели поставщика, и, честно говоря, 10 000 — это намного больше, чем количество элементов, которое мы ожидали от клиентов. Перечислимый.Содержит. Тем не менее, я понимаю, что это раздражает и что манипулирование деревьями выражений делает вещи слишком дорогими в вашем конкретном сценарии.

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

К обходным путям, уже предложенным в теме, я бы добавил следующее:

Рассмотрите возможность создания метода, который уравновешивает количество обращений к базе данных и количество элементов, которые вы передаете в Contains. Например, в ходе собственного тестирования я заметил, что вычисление и выполнение на локальном экземпляре SQL Server запроса из 100 элементов занимает 1/60 секунды. Если вы можете написать свой запрос таким образом, что выполнение 100 запросов со 100 различными наборами идентификаторов даст вам результат, эквивалентный запросу с 10 000 элементов, то вы можете получить результаты примерно за 1,67 секунды вместо 18 секунд.

Различные размеры фрагментов должны работать лучше в зависимости от запроса и задержки соединения с базой данных. Для определенных запросов, например, если переданная последовательность имеет дубликаты или если Enumerable.Contains используется во вложенном условии, вы можете получить повторяющиеся элементы в результатах.

Вот фрагмент кода (извините, если код, используемый для разделения входных данных на фрагменты, выглядит слишком сложным. Есть более простые способы добиться того же, но я пытался придумать шаблон, который сохраняет потоковую передачу для последовательности и Я не смог найти ничего подобного в LINQ, поэтому я, вероятно, перестарался с этой частью :)):

Использование:

var list = context.GetMainItems(ids).ToList();

Метод для контекста или репозитория:

public partial class ContainsTestEntities
{
    public IEnumerable<Main> GetMainItems(IEnumerable<int> ids, int chunkSize = 100)
    {
        foreach (var chunk in ids.Chunk(chunkSize))
        {
            var q = this.MainItems.Where(a => chunk.Contains(a.Id));
            foreach (var item in q)
            {
                yield return item;
            }
        }
    }
}

Методы расширения для нарезки перечислимых последовательностей:

public static class EnumerableSlicing
{

    private class Status
    {
        public bool EndOfSequence;
    }

    private static IEnumerable<T> TakeOnEnumerator<T>(IEnumerator<T> enumerator, int count, 
        Status status)
    {
        while (--count > 0 && (enumerator.MoveNext() || !(status.EndOfSequence = true)))
        {
            yield return enumerator.Current;
        }
    }

    public static IEnumerable<IEnumerable<T>> Chunk<T>(this IEnumerable<T> items, int chunkSize)
    {
        if (chunkSize < 1)
        {
            throw new ArgumentException("Chunks should not be smaller than 1 element");
        }
        var status = new Status { EndOfSequence = false };
        using (var enumerator = items.GetEnumerator())
        {
            while (!status.EndOfSequence)
            {
                yield return TakeOnEnumerator(enumerator, chunkSize, status);
            }
        }
    }
}

Надеюсь это поможет!

person divega    schedule 29.10.2011
comment
Чтобы объяснить !(status.EndOfSequence = true) в методе TakeOnEnumerator‹T›: Таким образом, побочным эффектом этого присваивания выражения всегда будет !true, что не влияет на общее выражение. По сути, он помечает stats.EndOfSequence как true только тогда, когда есть оставшиеся элементы для выборки, но вы достигли конца перечисления. - person arviman; 03.12.2014
comment
Возможно, производительность обработки Enumerable.Contains значительно улучшилась в EF 6 по сравнению с предыдущими версиями EF. Но, к сожалению, в наших случаях использования он все еще далек от удовлетворительного/готового к производству. - person Nik; 27.01.2019

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

Используйте обходной путь и обходной путь в случае проблем с производительностью, а EF означает прямой SQL. В этом нет ничего плохого. Глобальная идея о том, что использование EF = отказ от использования SQL, является ложью. У вас есть SQL Server 2008 R2, поэтому:

  • Создайте хранимую процедуру, принимающую табличный параметр для передачи ваших идентификаторов
  • Пусть ваша хранимая процедура возвращает несколько наборов результатов для оптимальной эмуляции логики Include
  • Если вам нужно сложное построение запросов, используйте динамический SQL внутри хранимой процедуры.
  • Используйте SqlDataReader для получения результатов и создания своих сущностей
  • Прикрепите их к контексту и работайте с ними, как если бы они были загружены из EF.

Если для вас критична производительность, лучшего решения не найти. Эта процедура не может быть сопоставлена ​​и выполнена EF, поскольку текущая версия не поддерживает ни табличные параметры, ни множественные наборы результатов.

person Ladislav Mrnka    schedule 26.10.2011
comment
@Laddislav Mrnka Мы столкнулись с аналогичной проблемой производительности из-за list.Contains(). Мы попробуем создать процедуры, передавая идентификаторы. Должны ли мы столкнуться с падением производительности, если запустим эту процедуру через EF? - person Kurubaran; 28.07.2015

Мы смогли решить проблему EF Contains, добавив промежуточную таблицу и присоединившись к этой таблице из запроса LINQ, для которого необходимо было использовать предложение Contains. Благодаря этому подходу мы смогли получить потрясающие результаты. У нас большая модель EF, и поскольку «Содержит» не разрешено при предварительной компиляции запросов EF, мы получали очень низкую производительность для запросов, в которых используется предложение «Содержит».

Обзор:

  • Создайте таблицу в SQL Server, например HelperForContainsOfIntType со столбцами HelperID из Guid типа данных и ReferenceID из int столбцов с типом данных. При необходимости создавайте разные таблицы с ReferenceID разных типов данных.

  • Создайте Entity/EntitySet для HelperForContainsOfIntType и других подобных таблиц в модели EF. При необходимости создайте разные Entity/EntitySet для разных типов данных.

  • Создайте вспомогательный метод в коде .NET, который принимает на вход IEnumerable<int> и возвращает Guid. Этот метод создает новый Guid и вставляет значения из IEnumerable<int> в HelperForContainsOfIntType вместе со сгенерированным Guid. Затем метод возвращает этот вновь сгенерированный Guid вызывающей стороне. Для быстрой вставки в таблицу HelperForContainsOfIntType создайте хранимую процедуру, которая принимает на вход список значений и выполняет вставку. См. табличные параметры в SQL Server 2008 (ADO.NET). Создайте разные помощники для разных типов данных или создайте общий вспомогательный метод для обработки разных типов данных.

  • Создайте скомпилированный запрос EF, аналогичный приведенному ниже:

    static Func<MyEntities, Guid, IEnumerable<Customer>> _selectCustomers =
        CompiledQuery.Compile(
            (MyEntities db, Guid containsHelperID) =>
                from cust in db.Customers
                join x in db.HelperForContainsOfIntType on cust.CustomerID equals x.ReferenceID where x.HelperID == containsHelperID
                select cust 
        );
    
  • Вызовите вспомогательный метод со значениями, которые будут использоваться в предложении Contains, и получите Guid для использования в запросе. Например:

    var containsHelperID = dbHelper.InsertIntoHelperForContainsOfIntType(new int[] { 1, 2, 3 });
    var result = _selectCustomers(_dbContext, containsHelperID).ToList();
    
person Dhwanil Shah    schedule 15.02.2012
comment
Спасибо за это! Я использовал вариант вашего решения для решения моей проблемы. - person Mike; 17.07.2013

Редактирование моего первоначального ответа. Существует возможный обходной путь, в зависимости от сложности ваших объектов. Если вы знаете SQL-запрос, который EF генерирует для заполнения ваших сущностей, вы можете выполнить его напрямую, используя DbContext.Database.SqlQuery. Я думаю, что в EF 4 вы могли бы использовать ObjectContext.ExecuteStoreQuery, но я не пробовал.

Например, используя код из моего исходного ответа ниже для создания инструкции sql с использованием StringBuilder, я смог сделать следующее

var rows = db.Database.SqlQuery<Main>(sql).ToArray();

а общее время увеличилось примерно с 26 секунд до 0,5 секунды.

Я буду первым, кто скажет, что это уродливо, и, надеюсь, появится лучшее решение.

обновить

Немного подумав, я понял, что если вы используете объединение для фильтрации результатов, EF не нужно создавать такой длинный список идентификаторов. Это может быть сложно в зависимости от количества одновременных запросов, но я считаю, что вы можете использовать идентификаторы пользователей или идентификаторы сеансов, чтобы изолировать их.

Чтобы проверить это, я создал таблицу Target с той же схемой, что и Main. Затем я использовал StringBuilder для создания INSERT команд для заполнения таблицы Target партиями по 1000, так как это максимальное количество SQL Server, которое может принять за один INSERT. Непосредственное выполнение операторов sql было намного быстрее, чем через EF (примерно 0,3 секунды против 2,5 секунды), и я считаю, что это нормально, поскольку схема таблицы не должна меняться.

Наконец, выбор с использованием join привел к гораздо более простому запросу и выполнению менее чем за 0,5 секунды.

ExecuteStoreCommand("DELETE Target");

var ids = Main.Select(a => a.Id).ToArray();
var sb = new StringBuilder();

for (int i = 0; i < 10; i++)
{
    sb.Append("INSERT INTO Target(Id) VALUES (");
    for (int j = 1; j <= 1000; j++)
    {
        if (j > 1)
        {
            sb.Append(",(");
        }
        sb.Append(i * 1000 + j);
        sb.Append(")");
    }
    ExecuteStoreCommand(sb.ToString());
    sb.Clear();
}

var rows = (from m in Main
            join t in Target on m.Id equals t.Id
            select m).ToArray();

rows.Length.Dump();

И sql, сгенерированный EF для соединения:

SELECT 
[Extent1].[Id] AS [Id]
FROM  [dbo].[Main] AS [Extent1]
INNER JOIN [dbo].[Target] AS [Extent2] ON [Extent1].[Id] = [Extent2].[Id]

(исходный ответ)

Это не ответ, но я хотел поделиться дополнительной информацией, и она слишком длинная, чтобы поместиться в комментарий. Я смог воспроизвести ваши результаты и добавить еще несколько вещей:

SQL Profiler показывает задержку между выполнением первого запроса (Main.Select) и второго запроса Main.Where, поэтому я подозреваю, что проблема заключалась в создании и отправке запроса такого размера (48 980 байт).

Однако динамическое построение одного и того же оператора sql в T-SQL занимает менее 1 секунды, а взятие ids из вашего оператора Main.Select, построение того же оператора sql и выполнение его с использованием SqlCommand заняло 0,112 секунды, включая время на запись содержимого. к консоли.

На данный момент я подозреваю, что EF выполняет некоторый анализ/обработку для каждого из 10 000 ids при построении запроса. Хотел бы я дать окончательный ответ и решение :(.

Вот код, который я пробовал в SSMS и LINQPad (пожалуйста, не критикуйте слишком резко, я тороплюсь уйти с работы):

declare @sql nvarchar(max)

set @sql = 'SELECT 
[Extent1].[Id] AS [Id]
FROM [dbo].[Main] AS [Extent1]
WHERE [Extent1].[Id] IN ('

declare @count int = 0
while @count < 10000
begin
    if @count > 0 set @sql = @sql + ','
    set @count = @count + 1
    set @sql = @sql + cast(@count as nvarchar)
end
set @sql = @sql + ')'

exec(@sql)

var ids = Mains.Select(a => a.Id).ToArray();

var sb = new StringBuilder();
sb.Append("SELECT [Extent1].[Id] AS [Id] FROM [dbo].[Main] AS [Extent1] WHERE [Extent1].[Id] IN (");
for(int i = 0; i < ids.Length; i++)
{
    if (i > 0) 
        sb.Append(",");     
    sb.Append(ids[i].ToString());
}
sb.Append(")");

using (SqlConnection connection = new SqlConnection("server = localhost;database = Test;integrated security = true"))
using (SqlCommand command = connection.CreateCommand())
{
    command.CommandText = sb.ToString();
    connection.Open();
    using(SqlDataReader reader = command.ExecuteReader())
    {
        while(reader.Read())
        {
            Console.WriteLine(reader.GetInt32(0));
        }
    }
}
person Jeff Ogata    schedule 26.10.2011
comment
Спасибо за вашу работу над этим. Зная, что вы смогли воспроизвести его, я чувствую себя лучше — по крайней мере, я не сумасшедший! К сожалению, ваш обходной путь не очень помогает в моем случае, потому что, как вы могли догадаться, приведенный здесь пример был максимально упрощен, чтобы изолировать проблему. Мой фактический запрос включает в себя довольно сложную схему, .Include() в нескольких других таблицах, а также несколько других операторов LINQ. - person Mike; 26.10.2011
comment
@Mike, я добавил еще одну идею, которая подойдет для сложных объектов. Надеюсь, это не будет слишком сложно реализовать, если у вас нет другого выбора. - person Jeff Ogata; 26.10.2011
comment
Я провел несколько тестов и думаю, что вы правы в том, что задержка заключается в создании SQL до его выполнения. Я обновил свой вопрос с деталями. - person Mike; 26.10.2011
comment
@Mike, ты смог попробовать присоединиться к идентификаторам (см. обновление в моем ответе)? - person Jeff Ogata; 26.10.2011
comment
Я использовал вариант вашего подхода для решения проблемы с производительностью. Это оказалось довольно уродливым, но, вероятно, лучшим вариантом, пока (и если) Microsoft не решит эту проблему. - person Mike; 15.11.2011

Я не знаком с Entity Framework, но будет ли производительность лучше, если вы сделаете следующее?

Вместо этого:

var ids = Main.Select(a => a.Id).ToArray();
var rows = Main.Where (a => ids.Contains(a.Id)).ToArray();

как насчет этого (при условии, что идентификатор является int):

var ids = new HashSet<int>(Main.Select(a => a.Id));
var rows = Main.Where (a => ids.Contains(a.Id)).ToArray();
person Shiv    schedule 06.03.2014
comment
Я не знаю, почему и как, но это сработало как шарм :) Большое спасибо :) - person Wahid Bitar; 10.03.2014
comment
Объяснение того, почему производительность лучше, заключается в том, что вызов int[].Contains в первом вызове равен O(n) — потенциально полное сканирование массива, тогда как вызов HashSet‹int›.Contains — O(1). См. stackoverflow.com /questions/9812020/ для производительности набора хэшей. - person Shiv; 11.03.2014
comment
@Shiv Я не верю, что это правильно. EF возьмет любую коллекцию и переведет ее в SQL. Тип коллекции не должен быть проблемой. - person Rob; 05.03.2015
comment
@Rob Я настроен скептически - не могу объяснить разницу в производительности, если это так. Возможно, придется проанализировать двоичный файл, чтобы увидеть, что он сделал. - person Shiv; 06.03.2015
comment
HashSet не является IEnumerable. IEnumerables, вызывающие .Contains в LINQ, работают плохо (по крайней мере, до EF6). - person Jason Beck; 06.11.2017
comment
@Rob здесь прав, я думаю. new []{1, 2, 3, 4}.Contains(column) будет просто преобразовано в ... WHERE column IN (1, 2, 3, 4) в SQL, поэтому использование HashSet не повлияет на производительность. Эта оптимизация стоит того, только если Main является IEnumerable. - person RoadRunner; 20.04.2020

Это было исправлено в Entity Framework 6 Alpha 2: http://entityframework.codeplex.com/SourceControl/changeset/a7b70f69e551

http://blogs.msdn.com/b/adonet/archive/2012/12/10/ef6-alpha-2-available-on-nuget.aspx

person Felipe Pessoto    schedule 13.12.2012

Кешируемая альтернатива Содержит?

Это просто укусило меня, поэтому я добавил свои два пенса к ссылке «Предложения функций Entity Framework».

Проблема определенно заключается в создании SQL. У меня есть клиент, по чьим данным генерация запроса составляла 4 секунды, но выполнение было 0,1 секунды.

Я заметил, что при использовании динамический LINQ и OR, генерация sql заняла столько же времени, но сгенерировала что-то, что можно было кэшировать. Поэтому при повторном выполнении оно сократилось до 0,2 секунды.

Обратите внимание, что SQL-запрос все еще был сгенерирован.

Просто еще кое-что, что следует учитывать, если вы можете переварить первоначальный удар, количество ваших массивов не сильно изменится и много запустите запрос. (Протестировано в LINQ Pad)

person Dave    schedule 04.10.2012
comment
Также проголосуйте за него на сайте codeplex ‹entityframework.codeplex.com/workitem/245 - person Dave; 05.10.2012

Проблема связана с генерацией SQL в Entity Framework. Он не может кэшировать запрос, если одним из параметров является список.

Чтобы заставить EF кэшировать ваш запрос, вы можете преобразовать свой список в строку и выполнить .Contains в строке.

Так, например, этот код будет работать намного быстрее, поскольку EF может кэшировать запрос:

var ids = Main.Select(a => a.Id).ToArray();
var idsString = "|" + String.Join("|", ids) + "|";
var rows = Main
.Where (a => idsString.Contains("|" + a.Id + "|"))
.ToArray();

Когда этот запрос сгенерирован, он, скорее всего, будет сгенерирован с использованием Like вместо In, поэтому он ускорит ваш C #, но потенциально может замедлить ваш SQL. В моем случае я не заметил снижения производительности при выполнении SQL, а C# работал значительно быстрее.

person user2704238    schedule 30.09.2015
comment
Хорошая идея, но это не будет использовать какой-либо индекс в рассматриваемом столбце. - person spender; 27.04.2017
comment
Да, это правда, поэтому я упомянул, что это может замедлить выполнение SQL. Я предполагаю, что это просто потенциальная альтернатива, если вы не можете использовать построитель предикатов и работаете с достаточно небольшим набором данных, чтобы вы могли позволить себе не использовать индекс. Я также полагаю, что я должен был упомянуть, что построитель предикатов является предпочтительным вариантом - person user2704238; 23.05.2017
comment
Какое УДИВИТЕЛЬНОЕ решение. Нам удалось увеличить время выполнения производственного запроса с ~ 12 600 миллисекунд до всего ~ 18 миллисекунд. Это ОГРОМНОЕ улучшение. Большое Вам спасибо !!! - person Jacob; 08.06.2019