T-SQL 查询优化
Posted
技术标签:
【中文标题】T-SQL 查询优化【英文标题】:T-SQL Query Optimization 【发布时间】:2008-12-04 03:58:00 【问题描述】:我正在对我们为客户提供的内部网络分析系统进行一些升级(在没有首选供应商或 Google Analytics 的情况下),并且我正在处理以下查询:
select
path as EntryPage,
count(Path) as [Count]
from
(
/* Sub-query 1 */
select
pv2.path
from
pageviews pv2
inner join
(
/* Sub-query 2 */
select
pv1.sessionid,
min(pv1.created) as created
from
pageviews pv1
inner join Sessions s1 on pv1.SessionID = s1.SessionID
inner join Visitors v1 on s1.VisitorID = v1.VisitorID
where
pv1.Domain = isnull(@Domain, pv1.Domain) and
v1.Campaign = @Campaign
group by
pv1.sessionid
) t1 on pv2.sessionid = t1.sessionid and pv2.created = t1.created
) t2
group by
Path;
我已经用 PageViews 表中的 200 万行测试了这个查询,它运行大约需要 20 秒。我注意到执行计划中有两次聚集索引扫描,两次都命中 PageViews 表。该表的 Created 列上有一个聚集索引。
问题在于,在这两种情况下,它似乎都遍历了所有 200 万行,我认为这是性能瓶颈。我能做些什么来防止这种情况发生吗,或者我在优化方面已经达到极限了吗?
作为参考,查询的目的是查找每个会话的第一个页面视图。
编辑: 非常沮丧之后,尽管在这里得到了帮助,但我无法使这个查询工作。因此,我决定在会话表中简单地存储对入口页面(现在是退出页面)的引用,这样我就可以执行以下操作:
select
pv.Path,
count(*)
from
PageViews pv
inner join Sessions s on pv.SessionID = s.SessionID
and pv.PageViewID = s.ExitPage
inner join Visitors v on s.VisitorID = v.VisitorID
where
(
@Domain is null or
pv.Domain = @Domain
) and
v.Campaign = @Campaign
group by pv.Path;
此查询在 3 秒或更短时间内运行。现在我要么必须在记录页面视图时实时更新进入/退出页面(最佳解决方案),要么在某个时间间隔运行批量更新。无论哪种方式,它都解决了问题,但不像我想要的那样。
编辑编辑:添加缺失的索引(从昨晚清理后)将查询减少到仅仅几毫秒)。呜呼!
【问题讨论】:
你能发布一些查询计划吗? 最好的方法是什么?我不确定如何将其导出为可读格式以便在此处发布。 哎呀,那里不是一个单一的索引搜索......这可以大大优化 @sambo99:嗯,是的!抱歉,我只是一个中级 SQL 人员 - 我正在探索我的领域之外的优化。 我们都知道这是学习这些东西的唯一途径。我可能会在途中建议您,也许您注意到尝试为两个条件设计一个查询并没有帮助自己。 【参考方案1】:对于初学者来说,
where pv1.Domain = isnull(@Domain, pv1.Domain)
不会SARG。我记得,你不能优化函数的匹配。
【讨论】:
有什么建议可以替换该行吗?我想允许特定域的匹配,或者如果没有指定域,则匹配所有域。除了在 if/else 语句中复制整个查询之外,还有更好的方法吗? FWIW,我尝试注释掉该行并且执行时间下降到 8 秒,所以这似乎是罪魁祸首。任何建议将不胜感激。 你可以使用 case 语句: where pv1.Domain = case when @Domain is null then pv1.Domain else @Domain end 将您的答案标记为已接受,因为您帮助我最接近解决方案。希望有一个会在我睡梦中出现。【参考方案2】:我回来了。要回答您的第一个问题,您可能只需对这两个条件进行联合,因为它们显然是不相交的。
实际上,您试图涵盖提供域和不提供域的情况。您需要两个查询。它们的优化方式可能完全不同。
【讨论】:
【参考方案3】:这些表中数据的性质是什么?您是否发现大部分数据被定期插入/删除?
这是表的完整架构吗?查询计划显示不同的索引.. 编辑:对不起,请阅读最后一行文字。我建议如果这些表经常被清除/插入,你可以考虑放弃聚集索引并将这些表用作堆表......只是一个想法
按照约翰的建议,肯定应该在 Campaign、Domain 上放置非聚集索引
【讨论】:
没有数据被删除。访客和会话很少更新,而 PageViews 每次有人访问页面时都会插入。这是所涉及表的完整架构。【参考方案4】:您的内部查询 (pv1) 将需要 (Domain) 上的非聚集索引。
由于 Created 上的聚集索引,第二个查询 (pv2) 已经可以找到它需要的行,但是 pv1 可能返回太多行,以至于 SQL Server 认为表扫描比它需要的所有锁更快拿。作为 SessionID 上的 pv1 组(因此必须按 SessionID 排序),SessionID、Created 和包含路径上的非聚集索引应该允许发生 MERGE 连接。如果没有,您可以使用“SELECT .. FROM pageviews pv2 INNER MERGE JOIN ...”强制合并连接
上面列出的两个索引将是:
在 PageViews(域)上创建非集群索引 ncixcampaigndomain
在 PageViews(SessionID, Created) 包含(路径)上创建非集群索引 ncixsessionidcreated
【讨论】:
将 Campaign 放在索引的首位 - 这将涵盖 Domain 为空的情况;其他方式不会。 该索引无论如何都不起作用 - 广告系列不在页面浏览量表中。另外,我可以在visitors 表上为campaign 创建一个索引,但在测试用例中,该列的所有行都具有相同的值。我的理解是,由于缺乏唯一性,该列上的索引无济于事。 SessionID + Created 上的索引不会从添加路径中受益 - 它在前两个字段中已经是唯一的。 好的 - 现在我将不得不真正看看它 - 到目前为止,我一直在本地优化。 :) 啊,我错过了“访客”中的广告系列。只是 pv1.Domain 上的索引。我会更新我的答案。【参考方案5】:SELECT
sessionid,
MIN(created) AS created
FROM
pageviews pv
JOIN
visitors v ON pv.visitorid = v.visitorid
WHERE
v.campaign = @Campaign
GROUP BY
sessionid
这样就可以为您提供广告系列的会话。现在让我们看看你在做什么。
好的,这摆脱了你的分组:
SELECT
campaignid,
sessionid,
pv.path
FROM
pageviews pv
JOIN
visitors v ON pv.visitorid = v.visitorid
WHERE
v.campaign = @Campaign
AND NOT EXISTS (
SELECT 1 FROM pageviews
WHERE sessionid = pv.sessionid
AND created < pv.created
)
【讨论】:
那么接下来就是你想要他们为会话点击的第一页,对吧? 是的。告诉你想要的,你有MSN messenger吗?这里有点令人费解。【参考方案6】:从杜夫勒多夫继续。
试试这个:
where
(@Domain is null or pv1.Domain = @Domain) and
v1.Campaign = @Campaign
好的,我有几个建议
创建这个覆盖索引:
create index idx2 on [PageViews]([SessionID], Domain, Created, Path)
如果您可以修改 Sessions 表以便它存储入口页面,例如。 EntryPageViewID 您将能够对此进行大量优化。
【讨论】:
在这里发布 EP 的最佳方式是什么? 此外,建议的修改允许域的条件规范,就像我想要的那样。所以谢谢你。不过仍然徘徊在9秒左右。最终查询仍然以 37% 的成本运行聚集索引扫描,迭代所有 200 万行。 这个东西在 9 秒内返回了多少个结果? 这是一个摘要 - 返回少于 5 行 atm。此外,将执行计划添加到原始问题中以供参考。 好吧,如果我是你,我不会休息,直到这需要不到 100 毫秒才能返回,我看看我能做什么,请发布 [Visitors] 和 [PageViews] 的表定义和索引跨度>以上是关于T-SQL 查询优化的主要内容,如果未能解决你的问题,请参考以下文章