Невозможно выполнить запрос, если строка слишком длинная - PullRequest
2 голосов
/ 11 апреля 2019

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

Это мой код:

public async Task<IEnumerable<User>> GetUsers(IEnumerable<int> ids)
{
    if (ids.IsNullOrEmpty())
    {
        return Enumerable.Empty<User>();
    }

    string query = $@"  SELECT  *
                        FROM    dbo.Users
                        WHERE   Id IN ({string.Join(",", ids)})";

    using (SqlConnection conn = new SqlConnection(ConnectionString))
    {
        return await conn.QueryAsync<TEntity>(query);
    }
}

ЕслиКоличество идентификаторов небольшое (менее 100K), работает нормально.Но если счет равен приблизительно 1 миллиону, это вызывает исключение тайм-аута SQL.

Когда я смотрю на профилировщик SQL, я вижу, что запрос даже не выполнялся.

Что яделать неправильно?Что может быть хорошим решением или хорошим способом обработки больших объемов данных в запросах C # => SQL Server?

Заранее спасибо.

Ответы [ 2 ]

3 голосов
/ 11 апреля 2019

Я вижу несколько проблем здесь и пытаюсь сделать шаг назад:

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

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

С временной таблицей это будет выглядеть как

    string query = $@"  CREATE TABLE @ids (id INT);

                        INSERT INTO @ids VALUES {string.Join(",", ids.Select(id => $"({id})"))}

                        SELECT  u.*
                        FROM    @ids Ids
                        JOIN    dbo.Users u ON u.Id = Ids.Id

Можно также использовать FROM (VALUES(...)), но, как упоминал мой комментатор ниже, это не гарантируется для работы. Это будет выглядеть примерно так:

    string query = $@"  SELECT  u.*
                        FROM    (
                                    VALUES {string.Join(",", ids.Select(id => $"({id})"))} 
                                ) AS Ids(Id)
                        JOIN    dbo.Users u ON u.Id = Ids.Id";

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

1 голос
/ 11 апреля 2019

Одним из подходов может быть разделение вашей коллекции ids на биты, скажем, 100 КБ (поскольку она работает с таким количеством идентификаторов), и многократный запрос к базе данных.

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