- r - 以节省内存的方式增长 data.frame
- ruby-on-rails - ruby/ruby on rails 内存泄漏检测
- android - 无法解析导入android.support.v7.app
- UNIX 域套接字与共享内存(映射文件)
我在使用异步调用时遇到了主要的 SQL 性能问题。我创建了一个小案例来演示这个问题。
我在位于我们 LAN 中的 SQL Server 2016 上创建了一个数据库(因此不是 localDB)。
在那个数据库中,我有一个包含 2 列的 WorkingCopy
表:
Id (nvarchar(255, PK))
Value (nvarchar(max))
DDL
CREATE TABLE [dbo].[Workingcopy]
(
[Id] [nvarchar](255) NOT NULL,
[Value] [nvarchar](max) NULL,
CONSTRAINT [PK_Workingcopy]
PRIMARY KEY CLUSTERED ([Id] ASC)
WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF,
IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON,
ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]
在那个表中,我插入了一条记录(id
='PerfUnitTest',Value
是一个 1.5mb 的字符串(一个更大的 JSON 数据集的 zip)) .
现在,如果我在 SSMS 中执行查询:
SELECT [Value]
FROM [Workingcopy]
WHERE id = 'perfunittest'
我立即得到了结果,我在 SQL Servre Profiler 中看到执行时间大约为 20 毫秒。一切正常。
当使用普通 SqlConnection
从 .NET (4.6) 代码执行查询时:
// at this point, the connection is already open
var command = new SqlCommand($"SELECT Value FROM WorkingCopy WHERE Id = @Id", _connection);
command.Parameters.Add("@Id", SqlDbType.NVarChar, 255).Value = key;
string value = command.ExecuteScalar() as string;
执行时间也约为 20-30 毫秒。
但是将其更改为异步代码时:
string value = await command.ExecuteScalarAsync() as string;
执行时间突然1800 ms!同样在 SQL Server Profiler 中,我看到查询执行持续时间超过一秒。尽管分析器报告的执行查询与非异步版本完全相同。
但情况变得更糟。如果我在连接字符串中使用数据包大小,我会得到以下结果:
Packet size 32768 : [TIMING]: ExecuteScalarAsync in SqlValueStore -> elapsed time : 450 ms
Packet Size 4096 : [TIMING]: ExecuteScalarAsync in SqlValueStore -> elapsed time : 3667 ms
Packet size 512 : [TIMING]: ExecuteScalarAsync in SqlValueStore -> elapsed time : 30776 ms
30,000 毫秒!!这比非异步版本慢 1000 多倍。 SQL Server Profiler 报告查询执行时间超过 10 秒。这甚至无法解释其他 20 秒都去了哪里!
然后我切换回同步版本,还尝试了数据包大小,虽然它确实对执行时间产生了一点影响,但没有异步版本那么显着。
作为旁注,如果它只将一个小字符串(< 100 字节)放入值中,则异步查询执行与同步版本一样快(结果在 1 或 2 毫秒内)。
我真的对此感到困惑,特别是因为我使用的是内置的 SqlConnection
,甚至不是 ORM。同样在四处搜索时,我没有发现任何可以解释这种行为的东西。有什么想法吗?
最佳答案
在没有显着负载的系统上,异步调用的开销稍大。尽管 I/O 操作本身无论如何都是异步的,但阻塞比线程池任务切换更快。
多少开销?让我们看看你的时间数字。阻塞调用 30 毫秒,异步调用 450 毫秒。 32 kiB 数据包大小意味着您需要大约五十个单独的 I/O 操作。这意味着我们在每个数据包上有大约 8 毫秒的开销,这与您对不同数据包大小的测量非常吻合。这听起来不像是异步的开销,尽管异步版本需要比同步版本做更多的工作。听起来同步版本是(简化)1个请求-> 50个响应,而异步版本最终是1个请求-> 1个响应-> 1个请求-> 1个响应-> ...,一遍又一遍地付出代价再次。
更深入。 ExecuteReader
与 ExecuteReaderAsync
一样好用.接下来的操作是Read
其次是 GetFieldValue
- 那里发生了一件有趣的事情。如果两者中的任何一个是异步的,则整个操作很慢。因此,一旦您开始使事情真正异步,肯定会有一些非常不同的事情发生 - Read
会很快,然后是异步 GetFieldValueAsync
会很慢,或者你可以从慢ReadAsync
开始, 然后都是 GetFieldValue
和 GetFieldValueAsync
很快。第一次异步读取流很慢,慢完全取决于整行的大小。如果我添加更多相同大小的行,读取每一行所花费的时间就好像我只有一行一样,所以很明显数据仍在逐行流式传输 - 它只是似乎更喜欢在您开始任何 异步读取后立即读取整行。如果我异步读取第一行,同步读取第二行 - 正在读取的第二行将再次快速读取。
因此我们可以看到问题是单个行和/或列的大小过大。您总共有多少数据并不重要 - 异步读取一百万个小行与同步读取一样快。但是只添加一个太大而无法放入单个数据包的字段,你会神秘地承担异步读取该数据的成本——就好像每个数据包都需要一个单独的请求数据包,而服务器不能只发送所有数据一次。使用 CommandBehavior.SequentialAccess
确实按预期提高了性能,但同步和异步之间的巨大差距仍然存在。
我获得的最佳表现是在正确完成整件事时。这意味着使用 CommandBehavior.SequentialAccess
,以及显式流式传输数据:
using (var reader = await cmd.ExecuteReaderAsync(CommandBehavior.SequentialAccess))
{
while (await reader.ReadAsync())
{
var data = await reader.GetTextReader(0).ReadToEndAsync();
}
}
有了这个,同步和异步之间的差异变得难以衡量,并且更改数据包大小不再像以前那样产生可笑的开销。
如果您希望在边缘情况下获得良好的性能,请确保使用可用的最佳工具 - 在这种情况下,流式传输大型列数据而不是依赖像 ExecuteScalar
这样的助手或 GetFieldValue
.
关于c# - 使用 SqlCommand Async 方法处理大数据时性能糟糕,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/42415969/
我需要使用 ADO.NET 将一系列命令发送到 SQL 2008,以便逐个执行。我应该为我发送的每个 SQL 创建一个新的 SQLCommand 吗?或者重用相同的 SQLCommand 并仅更改 C
我们可以在将参数传递给 sql 查询时同时使用这两种方法吗? 那么它们之间有什么区别呢? 最佳答案 MSDN article on AddWithValue() 解释两者之间的差异。内是对细微差异的解
我已经彻底搜索,但找不到 SET 的方法我的列数据到我使用 SQL 初始化的变量 UPDATE陈述。 这是导致问题的一段代码: int maxId; SqlConnection dataConnect
使用 SqlParameters是防止数据库查询中的 SQL 注入(inject)的推荐方法。我在哪里可以找到内部清理这些参数的代码/函数?我想在我的自定义实现中重新使用这个函数。我尝试使用 Refl
我目前正在使用 SQL Server 2014 中的 C# 脚本更新和读取值。当使用 SQlCommand 执行非查询时,运行脚本时会弹出错误: IndexOutOfRangeException: A
我最近一直在做一个旧项目。我在项目中发现以前的同事已经创建了一些用于打开与数据库的连接的常用方法。但是我怀疑这个过程是否真的在查询完成后通过 IDispose 处理连接。 方法如下: 连接数据库。 /
抱歉格式化,有点粗糙 我在 C# 中通过 SQLCommand 插入语句时遇到问题。插入完成后,ID 将返回给程序。 我填充了一些变量并编写了一个 SQLCommand。考虑了所有参数。 using
我有以下 SQL 语句(如果我在我的数据库中像这样执行它,它会起作用): IF EXISTS (SELECT * FROM dbo.PopularityPokemon WHERE Dex_ID = '
我有一个 C# 程序,我在其中创建了各种类,所有类都需要调用数据库。所以我决定创建一个静态类来处理所有调用,以便我可以非常轻松地应用影响深远的更改。在那个类中,我有调用 SqlCommand 各个部分
试图只返回前几行,因为我的数据库太大了,但是当我测试我的 SQL 时,我做了一个 select * 并且只返回了第一行。 SqlCommand sqlCmd = new SqlCommand(); S
我对从 SqlCommand 返回的值有疑问,我有这段代码: string sqlSelect = "Select TOP 1 Quotation.SentToSupp as SentToSupp F
我正在 .Net 中创建测试方法,需要更改数据库以设置测试场景。我们使用 SqlCommands 发送查询来更改数据库,为此连接字符串是受信任的连接字符串。所有 DROPS 和 ALTER 查询对于表
我试图通过 SqlCommand 将表名作为参数传递给我的查询,但它似乎不起作用。这是我的代码; SqlConnection con = new SqlConnection( "server=.;us
当我在 SSMS 中执行以下命令时,我得到了预期的结果 - SELECT * FROM [Event] WHERE Active = 'True' AND [Name] LIKE '%P%' 即显示名
我在 C# 中运行一条返回一个值(字符串)的 SQL 命令。然后我试图将它保存到一个变量,但它总是抛出这个异常: A first chance exception of type 'System.In
我有这个 SqlCommand,但我不知道如何给出返回值? string param = Request.QueryString["id"]; System.Data.SqlClient.SqlCon
我对使用 C# 和 ASP.NET 还很陌生,我很好奇在 SQL 数据库上运行多个查询时是否有命名 SqlCommand 的约定。例如,我已经创建了我的 SqlConnection,我希望调用一个函数
好吧,这是微不足道的,但它一直困扰着我。我有这样的代码 oCmd = new SqlCommand("getBooking", oCon); oCmd.CommandType = CommandTyp
如果我有这个变量: int value = 4; 将作为一些 sql 参数传递: SqlCommand sqlcmd = new SqlCommand(); sqlcmd.Parameters.Add
我使用以下代码从 SQL Server 表中进行选择: using (SqlConnection con = new SqlConnection(SqlConnectionString)) {
我是一名优秀的程序员,十分优秀!