Взять (10) против ТОП 10 с SqlDataReader?

У меня есть метод, который читает данные с использованием SqlDataReader и yield возвращает IEnumerable, например:

IEnumerable<string> LoadCustomers()
{
 using(SqlDataReader rdr = cmd.ExecuteReader())
 {
    while (rdr.Read())
    {
        yield return rdr.GetString(0); 
    }
 }
}

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

LoadCustomers.Take(10)

или передать 10 в качестве параметра в sql и сделать мой sql

SELECT TOP 10 FROM Customers ORDER BY CreationDate DESC

Согласно этому сообщению, весь набор результатов передается с сервера sql клиенту, даже если устройство чтения данных считывает только несколько строк (если соединение открыто) - следует избегать Take(10) подход из-за того, что эти дополнительные данные все равно передаются клиенту, или было бы преждевременной оптимизацией избегать этого (потому что код возврата yield закроет соединение после того, как прочитает 10 строк, а затем передача данных в любом случае прекратится)?

2 ответа

Решение

Поскольку оптимизация является "преждевременной", субъективна, я предпочитаю интерпретировать этот вопрос как "использует DataReader и остановка чтения после 10 строк имеют те же характеристики производительности, что и при использовании TOP(10) в запросе?

Ответ - нет. Переходя TOP(10) на сервер позволяет оптимизатору настраивать операции чтения, предоставления памяти, буферы ввода-вывода, детализацию блокировки и параллелизм со знанием того, что запрос вернет (и в этом случае также прочитает) максимум 10 строк. Оставляя вне TOP означает, что он должен подготовиться к случаю, когда клиент будет читать все строки - независимо от того, действительно ли вы остановились раньше.

Это неправда, что сервер будет отправлять строки независимо от того, читаете вы их или нет. Потянув ряды с SqlDataReader это концептуально построчная операция: когда вы выдаете Reader.MoveNext, вы получаете следующую строку с сервера и только эту строку. Но в интересах производительности строки буферизуются до того, как вы их запросите (как на сервере, так и в сетевых буферах). Таким образом, можно получить, скажем, 100 строк, извлеченных в буферах после вашего первого .MoveNext звоните, даже если вы прочитали только 10 из них.

Что касается накладных расходов, это не будет моей главной задачей, потому что эти буферы в конечном итоге имеют фиксированный размер: сервер не пойдет и не буферизует все строки набора результатов независимо от их количества (в целом это было бы очень неэффективно). Если вы прочитаете только 10 строк, то будет ли ваш запрос в конечном итоге возвращать 1000 или 1 000 000 строк, если он дойдет до конца, не будет иметь значения с точки зрения буферизации, но в первую очередь с точки зрения плана запроса. Тем не менее, это увеличивает накладные расходы.

Вы также можете использовать пагинацию Skip(0) и Take(10) для большей гибкости.

SQL SERVER 2012

SELECT name,
       CreationDate        
  FROM customer
 ORDER BY
       CreationDate      
OFFSET @skip ROWS
FETCH NEXT @take ROWS ONLY;

SQL 2005 по 2008

SET @take = (@skip + @take)

;WITH customer_page_cte AS
(SELECT 
        name, 
        CreationDate,
        ROW_NUMBER() OVER (ORDER BY CreationDate desc) AS RowNumber
 FROM customer
)

SELECT name, 
       CreationDate
  FROM customer_page_cte
 WHERE RowNumber > @skip AND RowNumber <= @take

C# с SQL 2012 - использовать хранимую процедуру для команды:)

var command = @"SELECT name,
                       CreationDate        
                  FROM customer
                 ORDER BY
                       CreationDate      
                 OFFSET @skip ROWS
                 FETCH NEXT @take ROWS ONLY;";

            using (var conn = new SqlConnection("Data Source=.;Initial Catalog=Stackru;Integrated Security=True"))
            {
                conn.Open();
                using (var cmd = new SqlCommand(command, conn))
                {
                    cmd.Parameters.AddWithValue("skip", 0);
                    cmd.Parameters.AddWithValue("take", 10);

                    var reader = cmd.ExecuteReader();
                    while (reader.Read())
                    {
                        Console.WriteLine(reader.GetString(0));
                    }
                }
            }
Другие вопросы по тегам