Взять (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));
}
}
}