SQL Server 是否正在扼杀您的应用程序性能?





0/5 (0投票)
当应用程序出现性能问题时,人们常常会认为是数据库出了问题。Ben Emmett 探讨了为什么事实并非总是如此,并展示了如何使用 ANTS Performance Profiler 来深入了解 .NET 应用程序对 SQL Server 的使用情况。
许多 .NET 开发人员抱怨 SQL Server 损害了他们的应用程序性能。毕竟,如果网页加载缓慢,很有可能大部分时间都花在了等待数据库响应上。
但尴尬的事实是,问题往往出在应用程序本身。如果应用程序检索数据效率低下,数据库就很难表现良好。因此,不应该首先责怪 SQL Server,而应该检查应用程序为什么会运行低效的查询。
例如,以一个搜索图书数据库中 ISBN 的 WinForms 应用程序为例。您也可以自己尝试一下——源代码和设置说明可在 https://github.com/bcemmett/MagicBooks 获取。
为了简单起见,这里只有一个表,如下所示:
CREATE TABLE [dbo].[Books](
[BookId] [int] IDENTITY(1,1) NOT NULL,
[ISBN] [varchar](20) NOT NULL,
[Title] [nvarchar](100) NOT NULL,
[Author] [nvarchar](100) NOT NULL,
[Copies] [int] NOT NULL,
[Large] [bit] NOT NULL,
[PublishDate] [date] NOT NULL
) ON [PRIMARY]
CREATE NONCLUSTERED INDEX [NonClusteredIndex_Isbn] ON [dbo].[Books] ([ISBN] ASC, [BookId] ASC)
INCLUDE ([Title], [Author], [Copies], [Large], [PublishDate])
数据库访问由 Entity Framework 处理,使用了以下模型:
public partial class Book
{
public int BookId { get; set; }
public string Isbn { get; set; }
public string Title { get; set; }
public string Author { get; set; }
public int Copies { get; set; }
public bool Large { get; set; }
public DateTime PublishDate { get; set; }
}
该应用程序搜索与我们提供的 ISBN 匹配的记录,但此搜索耗时超过 12 秒。很自然地会责怪数据库,但实际上,有一个索引覆盖了表中的每一列。这应该会让搜索非常快速。最好先检查应用程序。
使用像 Redgate 的 ANTS Performance Profiler 这样的 .NET 性能分析器,我们可以查看慢速时间段的调用树,并找出花费时间最多的那一行。在这种情况下,是检索 Book 对象列表的那一行。我们还可以看到为什么它很慢:因为它导致了 SELECT TOP (1)
… 数据库查询的执行,耗时 12.7 秒。
关键问题是为什么该查询会运行 12.7 秒,以及我们是否能对此做些什么。为此,我们需要更多地了解查询是如何运行的。在最新版本的 ANTS Performance Profiler 中,可以通过查看其执行计划来做到这一点。该计划向我们展示了 SQL Server 如何执行查询。ANTS 还提供了关于计划中可能导致问题的部分的具体警告。
在这种情况下,我们看到发生了隐式转换——CONVERT_IMPLICIT(nvarchar(20, [Extent1].[ISBN], 0) = [@p__linq__0]
,这意味着 Book 表的 ISBN 列被转换为 NVarChar(20)
类型。使用 ANTS 查看已执行查询的详细信息,我们可以看到 @p__linq__0
参数是以 NVarChar
类型提供的。查看数据库架构,我们发现 ISBN 列的类型是 VarChar。
那么,发生了什么?
如果 Entity Framework 模型有一个映射到列的字符串属性,它会假定该列的类型为 NVarChar
。这通常是一个明智的选择,因为 .NET 中的字符串是 Unicode,并且要在 SQL Server 中表示完整的 Unicode,必须使用 NVarChar
而不是 VarChar
。
由于我们将 @p__linq__0
参数作为 NVarChar
提供,SQL Server 必须将其与 ISBN 列中的 VarChar
数据进行比较。不幸的是,在许多 SQL Server 排序规则中,无法从 NVarChar
隐式转换为 VarChar
,因为转换可能会丢失精度。相反,数据只能以另一种方式转换——从 VarChar
转换为更宽的 NVarChar
。
因此,SQL Server 不会进行将我们单个 NVarChar
参数转换为 VarChar
并与索引的 ISBN 列进行比较,而是被迫将整个 VarChar
ISBN 列转换为 NVarChar
以进行比较。这导致执行计划中出现的昂贵的表扫描操作,该操作占用了查询成本的 98.59%。
幸运的是,修复起来很容易。Entity Framework 的列注解允许我们用属性来装饰模型,这些属性明确指定了列的数据类型。所以,在我们的模型中,我们可以这样做:
public int BookId { get; set; }
[Column(TypeName = "varchar")]
public string Isbn { get; set; }
public string Title { get; set; }
...
Entity Framework 现在会知道将参数指定为 VarChar
,这将避免数据类型转换,并确保查询能够使用索引。如果我们使用 ANTS 重新分析应用程序,查询只需几毫秒即可完成,并且我们可以看到改进后的计划,显示正在使用索引查找运算符。
故事的寓意
虽然数据库服务器确实可能存在问题,但应用程序运行导致 SQL Server 无法良好执行的查询的情况也非常普遍。借助 ANTS Performance Profiler,您可以了解这些查询的性能影响,深入查看它们的执行计划,并更深入地了解它们为何花费如此多的时间。
您可以通过从 https://github.com/bcemmett/MagicBooks 获取源代码,以及下载 ANTS Performance Profiler 的 14 天免费试用版来尝试此示例。或者,更好的是,在您自己的应用程序上试用 ANTS,看看您能发现什么。