我正在努力调试特定查询的性能。查询是这样的:
select count(*)
FROM dbo.user d
INNER JOIN dbo.distinct_first_name dfn ON (
[dbo].jw(dfn.first_name, 'john') > 0.8
AND
(d.first_name = dfn.first_name
OR d.nick_name = dfn.first_name
OR d.middle_name = dfn.first_name)
)
查询在不同的名字表(包含大约15k行)上运行Jaro Winkler过滤器,然后内部将其连接到用户表以生成结果集。根据定义,这需要大约1分钟才能在用户表中运行大约500k行。
以下是我所知道的:
1)Jaro Winkler过滤器几乎是即时的(单独0.1秒)
2)如果我将user子句更改为仅包含其中一列(即删除OR),则只需0.4s
3)如果我将此更改为三个查询,并将它们背靠背运行,则需要大约2秒
4)如果我将Jaro Winkler过滤器更改为0.99(因此只有一个结果),则查询执行时间没有实质性差异
5)如果我用相等操作替换Jaro Winkler过滤器(dfn.first_name ='john'),则总查询时间减少到4s
(所有时间都相当缓慢;现实生活中的表现会更好。)
因此,由于某种原因,函数和OR的组合使查询优化器混乱。执行计划的信息量不大;它说90%的查询花费在:
上<RelOp NodeId="63" PhysicalOp="Clustered Index Seek" LogicalOp="Clustered Index Seek" EstimateRows="1.69029" EstimateIO="0.003125" EstimateCPU="0.000158859" AvgRowSize="17" EstimatedTotalSubtreeCost="71.4311" TableCardinality="15958" Parallel="0" EstimateRebinds="448881" EstimateRewinds="0.504024" EstimatedExecutionMode="Row">
<OutputList>
<ColumnReference Database="[mydb]" Schema="[dbo]" Table="[distinct_first_name]" Alias="[dfn]" Column="first_name" />
</OutputList>
<RunTimeInformation>
<RunTimeCountersPerThread Thread="0" ActualRows="857936" ActualEndOfScans="859454" ActualExecutions="859454" />
</RunTimeInformation>
<IndexScan Ordered="1" ScanDirection="FORWARD" ForcedIndex="0" ForceSeek="0" ForceScan="0" NoExpandHint="0" Storage="RowStore">
向上拆分查询实际上是一个选项,因为这是一个选项,我可能会稍微重新设计一下架构,但是我很难过这会让你陷入困境。有什么想法吗?
答案 0 :(得分:2)
您可能想尝试的一些事项:
dfn表的聚集索引是什么?它只是一个名字的表,仅此而已吗?如果是,请删除自动编号列(如果有)并将名称设置为聚簇索引。
'约翰'是你争吵的争论吗?我认为是。您可以先在两个名称数据集中的最小值上计算Jaro Winkler过滤器,然后将其插入临时表中。然后加入临时表上的另一个表。请记住,临时表也可以从索引中受益(如果添加它们)。
您可以通过创建多列索引来提高性能:名字,昵称,中间名称。由于您在where语句中引用了所有列,因此单个索引的有用性会下降。
我认为运行SQL Tuning顾问工具并查看它提出了哪些建议总是很有趣。只需将监视器连接到SQL Server实例,并将查询的执行记录到工作负载文件中。然后,您可以将工作负载文件提供给顾问程序工具,如果启用该选项,它将建议索引,统计信息甚至架构更改。
尽可能预先计算。如果我没记错,在Jaro Winkler过滤器中,字符串长度是一个重要因素。您可以使用名称的字符串长度向dfn表添加列。像函数和视图这样的东西很不错,但并不是最好的性能。该功能就像一个黑盒子,无法使用任何预先存在或预先计算的数据。
最重要的是:衡量您的结果。 SQL查询优化器有自己的想法。密切关注执行计划并尝试不同的方案。
基于文本列的查询总是更难以优化。您可能希望查看全文索引以提高性能,但这是一个需要调查的单独主题。
答案 1 :(得分:1)
首先,first_name_alphaonly
和nick_name_alphaonly
实际上都是非持久计算列,因此所有基数都关闭,然后相乘。
然后,在distinct_first_name
表上有857.936个单独的聚簇索引,并且仅在此之后才应用包含jw
函数的过滤器。
在计算列上创建索引会有所帮助。在加入之前对distinct_first_name
进行过滤(进入#temp表)也可能有所帮助。然后是关于将OR转换为UNION ALL的建议。
优化器afaik永远不会将OR重新排列到UNION本身。相信它被称为安全。