数据库查询性能优化指南

在现代Web应用中,数据库的性能至关重要。如果一个网页加载缓慢,很可能大部分时间都花在了数据库的等待上。但一个尴尬的事实是,问题往往出在应用程序上。如果应用程序以低效的方式检索数据,数据库就很难表现出色。因此,不应该一开始就责怪SQL Server,而应该检查应用程序为何运行了糟糕的查询。

例如,考虑一个WinForms应用程序,它在数据库中搜索书籍的ISBN。可以在找到源代码和设置说明。为了简单起见,这里只有一个表,如下所示:

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秒才能运行。人们很容易将责任归咎于数据库,但实际上有一个索引覆盖了表中的每个列。这应该使搜索相当快。更好的做法是首先看看应用程序。

使用.NET性能分析器,如Redgate的ANTS Performance Profiler,可以查看慢速时间段的调用树,并看到大部分时间花费在哪一行。在这种情况下,它是检索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被迫将整个VarChar ISBN列转换为NVarChar,以便进行比较,而不是将单个NVarChar参数转换为VarChar,并与索引的ISBN列进行比较。这导致了执行计划中看到的昂贵的表扫描操作,占查询成本的98.59%。

幸运的是,修复方法很简单。Entity Framework的列注释允许用属性装饰模型,明确指定列的DataType。因此,在模型中可以这样做:

public int BookId { get; set; } [Column(TypeName = "varchar")] public string Isbn { get; set; } public string Title { get; set; } ...

Entity Framework现在知道要将参数指定为VarChar,这将避免数据类型转换,并确保查询能够使用索引。如果在ANTS中重新分析应用程序,查询只需要几毫秒,可以看到改进的计划,显示使用了索引查找操作符。

沪ICP备2024098111号-1
上海秋旦网络科技中心:上海市奉贤区金大公路8218号1幢 联系电话:17898875485