使用C#中的SqlDataReader()与SSMS

时间:2017-10-20 16:41:13

标签: c# sql-server performance ssms sqldatareader

我正在做一个简单的SQL查询来获取大量数据。 查询的复杂性不是问题。执行大约需要200ms。 但是,数据量似乎是个问题。

我们检索大约40k行。 每行有8列,每行数据量约为几百千字节。比如说,我们为此查询共下载15megs。

令我难以置信的是: 当我从一个基本的C#代码执行查询时,需要1分钟和44秒。 但是当我从SSMS做到它需要10秒。当然我是从同一台机器上做的,我使用的是同一个数据库。 我清楚地看到UI和实时填充的行。在10secs中,整个数据表已满。

我们尝试过:

  • 设置与SSMS相同的SET内容,
  • 更改事务隔离级别,
  • 忽略执行计划(使用OPTION(RECOMPILE)),
  • 忽略锁定(使用WITH(NOLOCK))。

它没有改变任何东西。 有道理:它的读取速度很慢。不是查询(恕我直言)。

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倍?这个诀窍是什么?

谢谢你们。

2 个答案:

答案 0 :(得分:0)

对于笑话,您是否尝试放弃使用datareader并将结果打入DataTable的想法?我看到datareader在某些情况下会很慢。

答案 1 :(得分:0)

  

令我难以置信的是:当我从基本的C#执行查询时   代码需要1分钟和44秒。但是当我从SSMS做到这一点需要10个   秒

您无法直接在SSMS中执行参数化查询,因此您需要比较不同的内容。在SSMS中使用局部变量而不是参数时,SQL Server使用总体平均密度统计信息来估计行数。使用参数化查询,SQL Server使用统计直方图和提供的参数值进行初始编译。不同的估计可能导致不同的计划,尽管直方图的估计通常更准确,并产生更好的计划(理论上)。

尝试使用sp_executesql和参数更新统计信息并从SSMS执行查询。我希望与应用程序代码具有相同的性能,无论好坏。