使用异步调用时,我遇到了主要的SQL性能问题。我创建了一个小案例来演示该问题。
我在驻留在我们LAN中的SQL Server 2016上创建了一个数据库(因此没有localDB)。
在该数据库中,我有一个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数据集的zip)。
现在,如果我在SSMS中执行查询:
SELECT [Value]
FROM [Workingcopy]
WHERE id = 'perfunittest'
我立即得到结果,并且在SQL Servre Profiler中看到执行时间大约为20毫秒。一切正常。
使用纯文本从.NET(4.6)代码执行查询时SqlConnection
:
// 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中,我看到查询执行时间超过一秒。尽管探查器报告的已执行查询与非异步版本完全相同。
但情况变得更糟。如果我在连接字符串中使用数据包大小,则会得到以下结果:
数据包大小32768:[TIMING]:SqlValueStore中的ExecuteScalarAsync->经过的时间:450 ms
数据包大小4096:[时间]:SqlValueStore中的ExecuteScalarAsync->经过的时间:3667毫秒
数据包大小512:[TIMING]:SqlValueStore中的ExecuteScalarAsync->经过的时间:30776毫秒
30,000毫秒!这比非异步版本慢1000倍。并且SQL Server Profiler报告查询执行花费了10秒钟以上。这甚至不能解释其他20秒的去向!
然后,我切换回同步版本,并使用Packet Size,虽然它确实影响了一些执行时间,但与异步版本相比,它没有那么引人注目。
附带说明一下,如果将一个小的字符串(<100bytes)放入值中,则异步查询的执行速度与同步版本一样快(结果为1或2毫秒)。
我对此感到非常困惑,尤其是因为我使用的是内置的SqlConnection
,甚至没有使用ORM。另外,在四处搜寻时,我什么也没找到能解释这种现象的东西。有任何想法吗?
GetSqlChars
或GetSqlBinary
检索它们。还可以考虑将它们存储为FILESTREAM数据-没有理由在表的数据页中保存1.5MB的数据