SQL 查询优化
Posted
技术标签:
【中文标题】SQL 查询优化【英文标题】:SQL Query Optimization 【发布时间】:2011-06-22 21:34:10 【问题描述】:当有 8000 行要处理时,此报告过去需要大约 16 秒。现在有 50000 行,报表需要 2:30 分钟。
这是我的第一次通过,昨天客户需要它,所以我按照需要完成的逻辑顺序编写了这段代码,但没有考虑优化。
现在随着数据的增加报告需要更长的时间,我需要重新审视并优化它。我在考虑索引视图、表函数等。
我认为最大的瓶颈是循环遍历临时表,制作 4 条选择语句,并更新临时表...50,000 次。
我想我可以将所有这些压缩到一个大的 SELECT 中,使用 (a) 4 个连接到同一个表以获得 4 个状态,但是我不确定如何在那里获得 TOP 1,或者我可以尝试 (b) 使用嵌套子查询,但与当前代码相比,两者看起来都非常混乱。
我不希望任何人为我编写代码,但如果一些 SQL 专家可以仔细阅读此代码并告诉我任何明显的低效率和替代方法,或加快此过程的方法,或我应该使用的技术,将不胜感激。
PS:假设这个数据库大部分是规范化的,但设计不佳,并且我无法添加索引。我基本上必须照原样使用它。
在代码显示(小于)的地方,我不得不替换“小于”符号,因为它正在裁剪我的一些代码。
谢谢!
创建过程 RptCollectionAccountStatusReport AS 设置无计数; 声明@Accounts 表 ( [AccountKey] INT IDENTITY(1,1) NOT NULL, [管理公司] NVARCHAR(50), [关联] NVARCHAR(100), [AccountNo] INT 唯一, [街道地址] NVARCHAR(65), [状态] NVARCHAR(50), [PrimaryStatus] NVARCHAR(100), [PrimaryStatusDate] SMALLDATETIME, [PrimaryDaysRemaining] INT, [SecondaryStatus] NVARCHAR(100), [SecondaryStatusDate] SMALLDATETIME, [SecondaryDaysRemaining] INT, [第三状态] NVARCHAR(100), [TertiaryStatusDate] SMALLDATETIME, [TertiaryDaysRemaining] INT, [外部状态] NVARCHAR(100), [外部状态日期] SMALLDATETIME, [ExternalDaysRemaining] INT ); 插入 @帐户 ( [管理公司], [协会], [户口号码], [街道地址], [状态]) 选择 mc.Name AS [ManagementCompany], a.LegalName AS [协会], c.CollectionKey AS [AccountNo], u.StreetNumber + ' ' + u.StreetName AS [StreetAddress], CASE WHEN c.InheritedAccount = 1 THEN 'ZZ' ELSE u.State END AS [State] 从 ManagementCompany mc WITH (NOLOCK) 加入 关联 a WITH (NOLOCK) ON a.ManagementCompanyKey = mc.ManagementCompanyKey 加入 单元 u WITH (NOLOCK) ON u.AssociationKey = a.AssociationKey 加入 集合 c WITH (NOLOCK) ON c.UnitKey = u.UnitKey 在哪里 c.已关闭为空; 声明@MaxAccountKey INT; 从@Accounts 中选择@MaxAccountKey = MAX([AccountKey]); 声明@index INT; 设置@index = 1; WHILE @index(小于)@MaxAccountKey BEGIN 声明@CollectionKey INT; SELECT @CollectionKey = [AccountNo] FROM @Accounts WHERE [AccountKey] = @index; 声明@PrimaryStatus NVARCHAR(100) = NULL; 声明@PrimaryStatusDate SMALLDATETIME = NULL; 声明@PrimaryDaysRemaining INT = NULL; 声明@SecondaryStatus NVARCHAR(100) = NULL; 声明@SecondaryStatusDate SMALLDATETIME = NULL; 声明@SecondaryDaysRemaining INT = NULL; 声明@TertiaryStatus NVARCHAR(100) = NULL; 声明@TertiaryStatusDate SMALLDATETIME = NULL; 声明@TertiaryDaysRemaining INT = NULL; 声明@ExternalStatus NVARCHAR(100) = NULL; 声明@ExternalStatusDate SMALLDATETIME = NULL; 声明@ExternalDaysRemaining INT = NULL; 选择前 1 @PrimaryStatus = a.StatusName,@PrimaryStatusDate = c.StatusDate,@PrimaryDaysRemaining = c.DaysRemaining FROM CollectionAccountStatus c WITH (NOLOCK) JOIN AccountStatus a WITH (NOLOCK) ON c.AccountStatusKey = a.AccountStatusKey WHERE c.CollectionKey = @CollectionKey AND a.StatusType = 'Primary Status' AND a.StatusName 'Cleared' 按 c.sysCreated DESC 排序; 选择前 1 @SecondaryStatus = a.StatusName,@SecondaryStatusDate = c.StatusDate,@SecondaryDaysRemaining = c.DaysRemaining FROM CollectionAccountStatus c WITH (NOLOCK) JOIN AccountStatus a WITH (NOLOCK) ON c.AccountStatusKey = a.AccountStatusKey WHERE c.CollectionKey = @CollectionKey AND a.StatusType = 'Secondary Status' AND a.StatusName 'Cleared' 按 c.sysCreated DESC 排序; 选择前 1 @TertiaryStatus = a.StatusName,@TertiaryStatusDate = c.StatusDate,@TertiaryDaysRemaining = c.DaysRemaining FROM CollectionAccountStatus c WITH (NOLOCK) JOIN AccountStatus a WITH (NOLOCK) ON c.AccountStatusKey = a.AccountStatusKey WHERE c.CollectionKey = @CollectionKey AND a.StatusType = 'Tertiary Status' AND a.StatusName 'Cleared' 按 c.sysCreated DESC 排序; 选择前 1 @ExternalStatus = a.StatusName,@ExternalStatusDate = c.StatusDate,@ExternalDaysRemaining = c.DaysRemaining FROM CollectionAccountStatus c WITH (NOLOCK) JOIN AccountStatus a WITH (NOLOCK) ON c.AccountStatusKey = a.AccountStatusKey WHERE c.CollectionKey = @CollectionKey AND a.StatusType = '外部状态' AND a.StatusName '已清除' 按 c.sysCreated DESC 排序; 更新 @Accounts 放 [PrimaryStatus] = @PrimaryStatus, [PrimaryStatusDate] = @PrimaryStatusDate, [PrimaryDaysRemaining] = @PrimaryDaysRemaining, [SecondaryStatus] = @SecondaryStatus, [SecondaryStatusDate] = @SecondaryStatusDate, [SecondaryDaysRemaining] = @SecondaryDaysRemaining, [TertiaryStatus] = @TertiaryStatus, [TertiaryStatusDate] = @TertiaryStatusDate, [TertiaryDaysRemaining] = @TertiaryDaysRemaining, [外部状态] = @外部状态, [外部状态日期] = @外部状态日期, [ExternalDaysRemaining] = @ExternalDaysRemaining 在哪里 [AccountNo] = @CollectionKey; SET @index = @index + 1; 结尾; 选择 [管理公司], [协会], [户口号码], [街道地址], [状态], [主要状态], 转换(VARCHAR,[PrimaryStatusDate],101)作为 [PrimaryStatusDate], [PrimaryDaysRemaining], [次要状态], 转换(VARCHAR,[SecondaryStatusDate],101)作为 [SecondaryStatusDate], [SecondaryDaysRemaining], [第三状态], 转换(VARCHAR,[TertiaryStatusDate],101)作为 [TertiaryStatusDate], [TertiaryDaysRemaining], [外部状态], 转换(VARCHAR,[ExternalStatusDate],101)作为[ExternalStatusDate], [ExternalDaysRemaining] 从 @Accounts 订购方式 [管理公司], [协会], [街道地址] 升序;【问题讨论】:
您使用的是什么版本的 SQL Server? 请不要使用 NOLOCK - 它会导致错误的结果! 【参考方案1】:不要试图猜测查询出错的地方 - 查看执行计划。它会告诉你是什么在消耗你的资源。
您可以直接从另一个表更新,甚至从表变量:SQL update from one Table to another based on a ID match
这将允许您将循环中的所有内容组合成一个(大量)语句。您可以使用不同的别名,例如,
JOIN AccountStatus As TertiaryAccountStatus...AND a.StatusType = 'Tertiary Status'
JOIN AccountStatus AS SecondaryAccountStatus...AND a.StatusType = 'Secondary Status'
-
我敢打赌,您在 AccountStatus.StatusType 字段上没有索引。您可以尝试改用该表的 PK。
HTH。
【讨论】:
【参考方案2】:首先使用临时表而不是表变量。这些可以被索引。
接下来,不要循环!几乎在所有情况下,循环都不利于性能。这个循环运行 50000 次而不是 50000 条记录一次,当你有 100 万条记录时,这将是可怕的!这是一个链接,可以帮助您了解如何进行基于集合的处理。它是为了避免游标而编写的,但循环类似于游标,所以它应该有所帮助。 http://wiki.lessthandot.com/index.php/Cursors_and_How_to_Avoid_Them
And (nolock) 会读取脏数据,这对报告非常不利。如果您使用的 SQl Server 版本高于 2000,则有更好的选择。
【讨论】:
【参考方案3】:SELECT @CollectionKey = [AccountNo] FROM @Accounts WHERE [AccountKey] = @index;
此查询将受益于表变量上的 PRIMARY KEY 声明。
当您说 IDENTITY 时,您是在要求数据库自动填充该列。 当您说 PRIMARY KEY 时,您是在要求数据库将数据组织成一个聚集索引。这两个概念非常不同。通常,您应该同时使用它们。
DECLARE @Accounts TABLE
(
[AccountKey] INT IDENTITY(1,1) PRIMARY KEY,
我无法添加索引。
在这种情况下,请将数据复制到可以添加索引的数据库中。并使用:SET STATISTICS IO ON
【讨论】:
此报告由公司高管每天运行 4-5 次,必须指向生产数据库。这不是一次性报告,因此将数据复制到另一个数据库似乎不是一种选择。 如果你使用临时表,你可以索引。以上是关于SQL 查询优化的主要内容,如果未能解决你的问题,请参考以下文章