为什么这样更快并且使用安全吗?(第一个字母在字母表中)


10

长话短说,我们正在用很小的人表中的值更新小的人表。在最近的测试中,此更新大约需要5分钟才能运行。

我们偶然发现了似乎最简单的优化方法,该方法似乎完美无缺!现在,同一查询可以在不到2分钟的时间内运行,并且可以完美地产生相同的结果。

这是查询。最后一行添加为“优化”。为什么查询时间急剧减少?我们错过了什么吗?这会在将来引发问题吗?

UPDATE smallTbl
SET smallTbl.importantValue = largeTbl.importantValue
FROM smallTableOfPeople smallTbl
JOIN largeTableOfPeople largeTbl
    ON largeTbl.birth_date = smallTbl.birthDate
    AND DIFFERENCE(TRIM(smallTbl.last_name),TRIM(largeTbl.last_name)) = 4
    AND DIFFERENCE(TRIM(smallTbl.first_name),TRIM(largeTbl.first_name)) = 4
WHERE smallTbl.importantValue IS NULL
-- The following line is "the optimization"
AND LEFT(TRIM(largeTbl.last_name), 1) IN ('a','à','á','b','c','d','e','è','é','f','g','h','i','j','k','l','m','n','o','ô','ö','p','q','r','s','t','u','ü','v','w','x','y','z','æ','ä','ø','å')

技术说明:我们知道要测试的字母列表可能还需要几个字母。我们还意识到使用“ DIFFERENCE”时明显的错误余量。

查询计划(常规): https : //www.brentozar.com/pastetheplan/?
id = rypV84y7V查询计划(带有“优化”):https : //www.brentozar.com/pastetheplan/?id=r1aC2my7E


4
对您的技术说明的小答复:AND LEFT(TRIM(largeTbl.last_name), 1) BETWEEN 'a' AND 'z' COLLATE LATIN1_GENERAL_CI_AI应该在那里做您想做的事情,而无需列出所有字符并且拥有难以阅读的代码
Erik A,

您是否有行中的最终条件WHERE为假?特别要注意的是,比较可能区分大小写。
jpmc26

@ErikvonAsmuth提出了一个很好的观点。但是,仅需一点技术说明:对于SQL Server 2008和2008 R2,最好使用版本“ 100”排序规则(如果适用于所使用的区域性/区域设置)。这样就可以了Latin1_General_100_CI_AI。对于SQL Server 2012和更高版本(至少通过SQL Server 2019),最好对所使用的语言环境使用最高版本的启用补充字符的排序规则。因此,Latin1_General_100_CI_AI_SC在这种情况下。大于100的版本(到目前为止仅日语)没有(或不需要)_SC(例如Japanese_XJIS_140_CI_AI)。
所罗门·鲁兹基

Answers:


9

它取决于表中的数据,索引....很难说,而无法比较执行计划/ io +时间统计信息。

我期望的区别是两个表之间的JOIN之前发生了额外的过滤。在我的示例中,我将更新更改为选择以重用表。

有“最优化”的执行计划 在此处输入图片说明

执行计划

您清楚地看到了过滤器操作的发生,在我的测试数据中,没有记录被过滤掉,因此也没有进行任何改进。

执行计划,无“优化” 在此处输入图片说明

执行计划

筛选器不见了,这意味着我们将不得不依靠联接来筛选出不需要的记录。

其他原因 更改查询的另一个原因/后果可能是,更改查询时创建了一个新的执行计划,该计划恰好更快。例如,引擎选择其他Join运算符,但这只是猜测。

编辑:

得到两个查询计划后澄清:

该查询正在从大表中读取550M行,并将其过滤掉。 在此处输入图片说明

这意味着谓词是执行大部分过滤的谓词,而不是搜索谓词。结果是读取了数据,但返回的次数较少。

使sql server使用其他索引(查询计划)/添加索引可以解决此问题。

那么,为什么优化查询没有同样的问题?

因为使用了不同的查询计划,所以使用扫描而不是搜索。

在此处输入图片说明 在此处输入图片说明

无需进行任何查找,但仅返回4M行即可使用。

下一个差异

忽略更新差异(优化查询中未更新任何内容),在优化查询中使用了哈希匹配:

在此处输入图片说明

而不是对未优化的嵌套循环联接:

在此处输入图片说明

当一个表较小而另一表较大时,嵌套循环最好。由于它们都接近相同的大小,因此我认为在这种情况下,哈希匹配是更好的选择。

总览

优化查询 在此处输入图片说明

优化查询的计划具有并行性,使用哈希匹配联接,并且需要执行更少的残留IO过滤。它还使用位图来消除不能产生任何联接行的键值。(也没有任何更新)

非优化查询 在此处输入图片说明 非优化查询的计划没有并行性,使用嵌套循环联接,并且需要对550M记录进行残留IO过滤。(也正在进行更新)

您可以采取什么措施来改善非优化查询?

  • 将索引更改为在键列列表中具有first_name和last_name:

    在dbo.largeTableOfPeople上创建索引IX_largeTableOfPeople_birth_date_first_name_last_name(birth_date,first_name,last_name)include(id)

但是由于使用函数且此表很大,因此这可能不是最佳解决方案。

  • 更新统计信息,使用重新编译来尝试并获得更好的计划。
  • (HASH JOIN, MERGE JOIN)查询添加OPTION
  • ...

测试数据+使用的查询

CREATE TABLE #smallTableOfPeople(importantValue int, birthDate datetime2, first_name varchar(50),last_name varchar(50));
CREATE TABLE #largeTableOfPeople(importantValue int, birth_date datetime2, first_name varchar(50),last_name varchar(50));


set nocount on;
DECLARE @i int = 1
WHILE @i <= 1000
BEGIN
insert into #smallTableOfPeople (importantValue,birthDate,first_name,last_name)
VALUES(NULL, dateadd(mi,@i,'2018-01-18 11:05:29.067'),'Frodo','Baggins');

set @i += 1;
END


set nocount on;
DECLARE @j int = 1
WHILE @j <= 20000
BEGIN
insert into #largeTableOfPeople (importantValue,birth_Date,first_name,last_name)
VALUES(@j, dateadd(mi,@j,'2018-01-18 11:05:29.067'),'Frodo','Baggins');

set @j += 1;
END


SET STATISTICS IO, TIME ON;

SELECT  smallTbl.importantValue , largeTbl.importantValue
FROM #smallTableOfPeople smallTbl
JOIN #largeTableOfPeople largeTbl
    ON largeTbl.birth_date = smallTbl.birthDate
    AND DIFFERENCE(RTRIM(LTRIM(smallTbl.last_name)),RTRIM(LTRIM(largeTbl.last_name))) = 4
    AND DIFFERENCE(RTRIM(LTRIM(smallTbl.first_name)),RTRIM(LTRIM(largeTbl.first_name))) = 4
WHERE smallTbl.importantValue IS NULL
-- The following line is "the optimization"
AND LEFT(RTRIM(LTRIM(largeTbl.last_name)), 1) IN ('a','à','á','b','c','d','e','è','é','f','g','h','i','j','k','l','m','n','o','ô','ö','p','q','r','s','t','u','ü','v','w','x','y','z','æ','ä','ø','å');

SELECT  smallTbl.importantValue , largeTbl.importantValue
FROM #smallTableOfPeople smallTbl
JOIN #largeTableOfPeople largeTbl
    ON largeTbl.birth_date = smallTbl.birthDate
    AND DIFFERENCE(RTRIM(LTRIM(smallTbl.last_name)),RTRIM(LTRIM(largeTbl.last_name))) = 4
    AND DIFFERENCE(RTRIM(LTRIM(smallTbl.first_name)),RTRIM(LTRIM(largeTbl.first_name))) = 4
WHERE smallTbl.importantValue IS NULL
-- The following line is "the optimization"
--AND LEFT(RTRIM(LTRIM(largeTbl.last_name)), 1) IN ('a','à','á','b','c','d','e','è','é','f','g','h','i','j','k','l','m','n','o','ô','ö','p','q','r','s','t','u','ü','v','w','x','y','z','æ','ä','ø','å')




drop table #largeTableOfPeople;
drop table #smallTableOfPeople;

8

不清楚第二个查询是否实际上是一种改进。

执行计划中包含的QueryTimeStats与问题中所说明的相比,没有太大的不同。

慢计划的耗时为257,556 ms(4分17秒)。190,992 ms尽管以3的并行度运行,快速计划的运行时间仍为3分钟11秒。

此外,第二个计划正在数据库中运行,该数据库在加入后无需执行任何工作。

第一计划

在此处输入图片说明

第二计划

在此处输入图片说明

因此,额外的时间可以很好地解释为更新350万行所需的工作(在update运算符中查找这些行,锁存页面,将更新写入页面和事务日志所需的工作不可忽略)

如果在像这样比较时实际上这是可重现的,那么说明是您在这种情况下只是很幸运。

具有37个IN条件的过滤器仅消除了表中4,008,334中的51行,但优化器认为它将消除更多的行

在此处输入图片说明

   LEFT(TRIM(largeTbl.last_name), 1) IN ( 'a', 'à', 'á', 'b',
                                          'c', 'd', 'e', 'è',
                                          'é', 'f', 'g', 'h',
                                          'i', 'j', 'k', 'l',
                                          'm', 'n', 'o', 'ô',
                                          'ö', 'p', 'q', 'r',
                                          's', 't', 'u', 'ü',
                                          'v', 'w', 'x', 'y',
                                          'z', 'æ', 'ä', 'ø', 'å' ) 

这种不正确的基数估计通常是一件坏事。在这种情况下,它产生了一个形状不同(和平行)的计划,尽管大规模低估了哈希值,但显然(?)对您更有效。

如果没有TRIMSQL Server,则可以将其转换为基本列直方图中的范围间隔,并给出更准确的估计,但是使用SQL Server TRIM只能进行猜测。

猜测的性质可能有所不同,但LEFT(TRIM(largeTbl.last_name), 1)在某些情况下对单个谓词的估计只是*估计为table_cardinality/estimated_number_of_distinct_column_values

我不确定到底是什么情况-数据大小似乎起了作用。我能够像这里一样使用宽的固定长度数据类型来重现此数据但是得到了一个不同的,更高的猜测值varchar(它只使用了10%的固定猜测值,估计有100,000行)。@Solomon Rutzky指出,如果varchar(100)使用结尾空格填充,char则使用较低的估计值

IN列表扩展到OR,SQL Server使用指数回退,最多考虑4个谓词。因此219.707估计得出如下。

DECLARE @TableCardinality FLOAT = 4008334, 
        @DistinctColumnValueEstimate FLOAT = 34207

DECLARE @NotSelectivity float = 1 - (1/@DistinctColumnValueEstimate)

SELECT @TableCardinality * ( 1 - (
@NotSelectivity * 
SQRT(@NotSelectivity) * 
SQRT(SQRT(@NotSelectivity)) * 
SQRT(SQRT(SQRT(@NotSelectivity)))
))
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.