65.9K
CodeProject 正在变化。 阅读更多。
Home

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

emptyStarIconemptyStarIconemptyStarIconemptyStarIconemptyStarIcon

0/5 (0投票)

2015年10月13日

CPOL

4分钟阅读

viewsIcon

57527

当应用程序出现性能问题时,人们常常会认为是数据库出了问题。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 秒。

ANTS Performance Profiler 中的调用树视图,显示耗时最多的那一行

关键问题是为什么该查询会运行 12.7 秒,以及我们是否能对此做些什么。为此,我们需要更多地了解查询是如何运行的。在最新版本的 ANTS Performance Profiler 中,可以通过查看其执行计划来做到这一点。该计划向我们展示了 SQL Server 如何执行查询。ANTS 还提供了关于计划中可能导致问题的部分的具体警告。

ANTS Performance Profiler 中慢查询的执行计划

在这种情况下,我们看到发生了隐式转换——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,看看您能发现什么。

© . All rights reserved.