我正在做一个简单的SQL查询来获取大量数据。 查询的复杂性不是问题。执行大约需要200ms。 但是,数据量似乎是个问题。
我们检索大约40k行。 每行有8列,每行数据量约为几百千字节。比如说,我们为此查询共下载15megs。
令我难以置信的是: 当我从一个基本的C#代码执行查询时,需要1分钟和44秒。 但是当我从SSMS做到它需要10秒。当然我是从同一台机器上做的,我使用的是同一个数据库。 我清楚地看到UI和实时填充的行。在10secs中,整个数据表已满。
我们尝试过:
它没有改变任何东西。 有道理:它的读取速度很慢。不是查询(恕我直言)。
while(reader.Read())
需要时间。
而且,我们尝试了一个空的while循环。因此,这不包括装箱/拆箱的东西或将结果放入内存。
这是一个测试程序,我们弄清楚它是需要时间的Read():
using System;
using System.Data;
using System.Data.SqlClient;
using System.Threading.Tasks;
using System.Transactions;
namespace SqlPerfTest
{
class Program
{
const int GroupId = 1234;
static readonly DateTime DateBegin = new DateTime(2017, 6, 19, 0, 0, 0, DateTimeKind.Utc);
static readonly DateTime DateEnd = new DateTime(2017, 10, 20, 0, 0, 0, DateTimeKind.Utc);
const string ConnectionString = "CENSORED";
static void Main(string[] args)
{
TransactionOptions transactionOptions = new TransactionOptions
{
IsolationLevel = System.Transactions.IsolationLevel.ReadUncommitted
};
using (var transactionScope = new TransactionScope(TransactionScopeOption.Required, transactionOptions))
{
using (SqlConnection connection = new SqlConnection(ConnectionString))
{
connection.Open();
SetOptimizations(connection);
ShowUserOptions(connection);
DoPhatQuery(connection).Wait(TimeSpan.FromDays(1));
}
transactionScope.Complete();
}
}
static void SetOptimizations(SqlConnection connection)
{
SqlCommand cmd = connection.CreateCommand();
Console.WriteLine("===================================");
cmd.CommandText = "SET QUOTED_IDENTIFIER ON";
cmd.ExecuteNonQuery();
Console.WriteLine(cmd.CommandText);
cmd.CommandText = "SET ANSI_NULL_DFLT_ON ON";
cmd.ExecuteNonQuery();
Console.WriteLine(cmd.CommandText);
cmd.CommandText = "SET ANSI_PADDING ON";
cmd.ExecuteNonQuery();
Console.WriteLine(cmd.CommandText);
cmd.CommandText = "SET ANSI_WARNINGS ON";
cmd.ExecuteNonQuery();
Console.WriteLine(cmd.CommandText);
cmd.CommandText = "SET ANSI_NULLS ON";
cmd.ExecuteNonQuery();
Console.WriteLine(cmd.CommandText);
cmd.CommandText = "SET CONCAT_NULL_YIELDS_NULL ON";
cmd.ExecuteNonQuery();
Console.WriteLine(cmd.CommandText);
cmd.CommandText = "SET ARITHABORT ON";
cmd.ExecuteNonQuery();
Console.WriteLine(cmd.CommandText);
cmd.CommandText = "SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED";
cmd.ExecuteNonQuery();
Console.WriteLine(cmd.CommandText);
cmd.CommandText = "SET DEADLOCK_PRIORITY -1";
cmd.ExecuteNonQuery();
Console.WriteLine(cmd.CommandText);
cmd.CommandText = "SET QUERY_GOVERNOR_COST_LIMIT 0";
cmd.ExecuteNonQuery();
Console.WriteLine(cmd.CommandText);
cmd.CommandText = "SET TEXTSIZE 2147483647";
cmd.ExecuteNonQuery();
Console.WriteLine(cmd.CommandText);
}
static void ShowUserOptions(SqlConnection connection)
{
SqlCommand cmd = connection.CreateCommand();
Console.WriteLine("===================================");
cmd.CommandText = "DBCC USEROPTIONS";
using (SqlDataReader reader = cmd.ExecuteReader(CommandBehavior.SequentialAccess))
{
Console.WriteLine(cmd.CommandText);
while (reader.HasRows)
{
while (reader.Read())
{
Console.WriteLine("{0} = {1}", reader.GetString(0), reader.GetString(1));
}
reader.NextResult();
}
}
}
static async Task DoPhatQuery(SqlConnection connection)
{
Console.WriteLine("===================================");
SqlCommand cmd = connection.CreateCommand();
cmd.CommandText =
@"SELECT
p.[Id],
p.[UserId],
p.[Text],
FROM [dbo].[Post] AS p WITH (NOLOCK)
WHERE p.[Visibility] = @visibility
AND p.[GroupId] = @groupId
AND p.[DatePosted] >= @dateBegin
AND p.[DatePosted] < @dateEnd
ORDER BY p.[DatePosted] DESC
OPTION(RECOMPILE)";
cmd.Parameters.Add("@visibility", SqlDbType.Int).Value = 0;
cmd.Parameters.Add("@groupId", SqlDbType.Int).Value = GroupId;
cmd.Parameters.Add("@dateBegin", SqlDbType.DateTime).Value = DateBegin;
cmd.Parameters.Add("@dateEnd", SqlDbType.DateTime).Value = DateEnd;
Console.WriteLine(cmd.CommandText);
Console.WriteLine("===================================");
DateTime beforeCommit = DateTime.UtcNow;
using (SqlDataReader reader = await cmd.ExecuteReaderAsync(CommandBehavior.CloseConnection))
{
DateTime afterCommit = DateTime.UtcNow;
Console.WriteLine("Query time = {0}", afterCommit - beforeCommit);
DateTime beforeRead = DateTime.UtcNow;
int currentRow = 0;
while (reader.HasRows)
{
while (await reader.ReadAsync())
{
if (currentRow++ % 1000 == 0)
Console.WriteLine("[{0}] Rows read = {1}", DateTime.UtcNow, currentRow);
}
await reader.NextResultAsync();
}
Console.WriteLine("[{0}] Rows read = {1}", DateTime.UtcNow, currentRow);
DateTime afterRead = DateTime.UtcNow;
Console.WriteLine("Read time = {0}", afterRead - beforeRead);
}
}
}
}
如上所示,我们重现了与SSMS相同的SET内容。 我们还尝试了人类已知的所有技巧来加速一切。 使用异步的东西。使用WITH(NOLOCK),NO RECOMPILE,在连接字符串中定义更大的PacketSize并没有帮助,并使用Sequential Reader。 不过,SSMS的速度要快50倍。
更多信息
我们的数据库是Azure数据库。我们实际上有2个数据库,一个在欧洲,一个在美国西部。 由于我们位于欧洲,因此当我们使用欧洲数据库时,相同的查询会更快。但它仍然像30秒,在SSMS中就像是瞬间。 数据传输速度确实会对此产生影响,但这不是主要问题。
我们还可以通过投影更少的列来减少数据传输的时间。它确实加快了Read()
迭代的速度。假设我们只检索我们的ID列:那么我们有一个while(Read())
,持续5秒。
但它不是一个选项,因为我们需要所有这些列。
我们知道如何解决&#39;这个问题:我们可以以不同的方式处理我们的问题,并且每天进行小型查询并将这些结果缓存在Azure表或其他内容中。 但我们想知道为什么 SSMS更快。什么是诀窍。
我们在C#中使用了Entity Framework,在C#中使用了Dapper,上面的示例就像本机C#。我在interwebz中看到了一些可能存在类似问题的人。对我来说,感觉SqlDataReader
慢了。
比如,它没有使用多个连接或其他东西来管理行的下载。
问题
所以我的问题是:管理工作室如何设法下载查询结果的速度快50倍?这个诀窍是什么?
谢谢你们。
答案 0 :(得分:0)
对于笑话,您是否尝试放弃使用datareader并将结果打入DataTable的想法?我看到datareader在某些情况下会很慢。
答案 1 :(得分:0)
令我难以置信的是:当我从基本的C#执行查询时 代码需要1分钟和44秒。但是当我从SSMS做到这一点需要10个 秒
您无法直接在SSMS中执行参数化查询,因此您需要比较不同的内容。在SSMS中使用局部变量而不是参数时,SQL Server使用总体平均密度统计信息来估计行数。使用参数化查询,SQL Server使用统计直方图和提供的参数值进行初始编译。不同的估计可能导致不同的计划,尽管直方图的估计通常更准确,并产生更好的计划(理论上)。
尝试使用sp_executesql
和参数更新统计信息并从SSMS执行查询。我希望与应用程序代码具有相同的性能,无论好坏。