在使用异步调用时,我遇到了严重的SQL性能问题。我创建了一个小示例以演示这个问题。
我在 SQL Server 2016 上创建了一个数据库,它位于我们的局域网中(因此不是本地数据库)。
在该数据库中,我有一个名为WorkingCopy
的表,其中包含2个列:
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数据集的压缩包))。
现在,如果我在SSMS中执行这个查询:
SELECT [Value]
FROM [Workingcopy]
WHERE id = 'perfunittest'
我立即获得了结果,并且在 SQL Server 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毫秒!而且在SQL Server Profiler中,我看到查询执行持续时间超过了1秒。尽管Profiler报告的已执行查询与非异步版本完全相同。更糟糕的是,如果我在连接字符串中玩弄数据包大小,我会得到以下结果:
- 数据包大小32768:[TIMING]:ExecuteScalarAsync in SqlValueStore ->经过时间:450毫秒 - 数据包大小4096:[TIMING]:ExecuteScalarAsync in SqlValueStore ->经过时间:3667毫秒 - 数据包大小512:[TIMING]:ExecuteScalarAsync in SqlValueStore ->经过时间:30776毫秒
30000毫秒!比非异步版本慢1000倍以上。而SQL Server Profiler报告查询执行时间超过10秒。这甚至无法解释其他20秒去哪里了!
然后我切换回同步版本,并尝试改变数据包大小,虽然影响了一点执行时间,但远没有异步版本那么戏剧性。
顺便说一下,如果只将一个小字符串(<100字节)放入值中,异步查询执行速度跟同步版本一样快(1或2毫秒的结果)。
我真的被这个问题困扰着,尤其是我正在使用内置的SqlConnection,甚至不是ORM。此外,当我搜索时,找不到可以解释这种行为的任何内容。有任何想法吗?
GetSqlChars
或GetSqlBinary
以流式方式检索它们。另外,考虑将它们存储为FILESTREAM数据 - 没有理由将1.5MB的数据保存在表的数据页中。 - Panagiotis Kanavos