cro*_*sek 3 sql-server execution-plan tempdb
为一个查询的两次执行捕获了两个实际的查询计划:
计划“快速”花费了大约 1 秒,大约 90% 的时间都会发生。
计划“慢”花了大约 16 秒。
由于图形计划看起来与我相同,因此我转储了 XML 版本并执行了差异以查看文本差异以确保。
快侧有 2 个“SpillToTempDb”警告,慢侧有 4 个警告。看着 2 个额外的慢端:
One SpillToTempDb warning on "Parallelism (Repartition Streams) Cost 1%":
<SpillToTempDb SpillLevel="0" />
On SpillToTempDb warning on "Parallelism (Distribute Streams) Cost 1%":
<SpillToTempDb SpillLevel="0" />
Run Code Online (Sandbox Code Playgroud)
慢速和快速情况下的成本是相同的 (1%)。这是否意味着可以忽略警告?有没有办法显示“实际”时间或成本。那会好很多!对于溢出操作,实际行数是相同的。
除了执行 xml 执行计划的手动文本差异以查找警告中的差异之外,我怎么知道运行时间增加 1500% 的实际原因是什么?
除了“RunTimeCountersPerThread”行之外的差异:
Left file: C:\Users\chrisr\Desktop\fast.sqlplan Right file: C:\Users\chrisr\Desktop\slow.sqlplan
10 <ThreadStat Branches="10" UsedThreads="85">
10 <ThreadStat Branches="10" UsedThreads="73">
------------------------------------------------------------------------
------------------------------------------------------------------------
19 <MemoryGrantInfo SerialRequiredMemory="1536" SerialDesiredMemory="10816" RequiredMemory="124224" DesiredMemory="133536" RequestedMemory="133536" GrantWaitTime="0" GrantedMemory="133536" MaxUsedMemory="105440" />
19 <MemoryGrantInfo SerialRequiredMemory="1536" SerialDesiredMemory="10816" RequiredMemory="124224" DesiredMemory="133536" RequestedMemory="133536" GrantWaitTime="0" GrantedMemory="133536" MaxUsedMemory="107840" />
------------------------------------------------------------------------
276 <Warnings>
277 <SpillToTempDb SpillLevel="0" />
278 </Warnings>
------------------------------------------------------------------------------------------------------------------------------------------------
630 <Warnings>
631 <SpillToTempDb SpillLevel="0" />
632 </Warnings>
------------------------------------------------------------------------
Run Code Online (Sandbox Code Playgroud)
对于慢速情况,tempdb 之前/之后(选择 * sys.fn_virtualfilestats(db_id('tempdb'),null))(仅显示几个 100 毫秒的延迟):
我应该查看 RunTimeCountersPerThread 行中的差异吗?看来这只是线程之间的顺序不同。
可能的提示(2013/04/26):查询执行多个内部合并连接,我在 Paul White 的博客上找到了这个:
交换泄漏
发表者:Paul White NZ(查看个人资料) 发表于:2011 年 3 月 7 日星期一上午 3:29
...这些几乎总是由顺序保留模式下的一系列交换操作引起的,由一个或多个需要排序输入的操作符分隔,例如合并连接、流聚合、分段...
在 SQL Server 2008 中,有一个 Profiler 事件来显示何时通过将交换数据包溢出到磁盘来解决这种类型的交换“死锁”。
主要的解决方法是避免这些计划形状(根据我的经验,尤其是在高 DOP 下并行合并连接)。有时使用提示强制执行散列或循环连接就足够了,但这是高级调整,确实需要有关数据的详细知识。
慢速和快速情况下的成本是相同的 (1%)。这是否意味着可以忽略警告?有没有办法显示“实际”时间或成本。那会好很多!对于溢出操作,实际行数是相同的。
显示的成本始终是优化器对迭代器的估计成本,根据其内部模型计算。此模型不反映您服务器的特定性能特征;它是一种抽象,在大多数系统上的大多数查询中,大部分时间都会产生合理的计划形状。无法显示每个迭代器的“实际”成本/执行时间。
除了执行 xml 执行计划的手动文本差异以查找警告中的差异之外,我怎么知道运行时间增加 1500% 的实际原因是什么?
通常,你不能。溢出警告(排序、散列、交换)是 2012 年执行计划中的新内容,但它们只是表明您应该调查并在可能的情况下消除某些内容。特定溢出的影响是需要衡量的——例如,不可能说特定类型的溢出总是会导致 x% 的性能下降。
对于慢速情况,tempdb before/after (select * sys.fn_virtualfilestats(db_id('tempdb'),null))(只显示几个 100ms 的延迟)
溢出到 tempdb 并返回当然是不可取的,但总体影响很难评估。对于排序和散列溢出,影响主要是由于 I/O 和访问模式,这可能是小块同步 I/O,例如排序溢出。大约 100 毫秒的延迟,您不需要太多同步 I/O 来引入显着延迟。进程的性质和 I/O 模式意味着 tempdb 溢出仍然是一个非常低延迟的存储系统(如 fusion-io)的问题。
对于交换溢出,有一个额外的延迟。查询内死锁必须由常规死锁监视器检测,默认情况下它每 5 秒只唤醒一次(如果最近发现死锁,则更频繁)。
然后,解析器必须选择一个或多个受害者,并将缓冲区交换到 tempdb,直到解决死锁。所需的假脱机数量和死锁的复杂性将在很大程度上决定这需要多长时间。
归根结底,保留顺序对于并行性来说是一件非常糟糕的事情。理想情况下,我们希望多个并发线程在没有相互依赖的数据流上运行。保留排序顺序会引入依赖关系,因此不同并行分支中的生产者和消费者线程可能会陷入死锁,等待保留顺序的迭代器接收行以决定接下来对哪个输入进行排序。
死锁的确切性质取决于运行时的数据分布和每个线程的排序顺序,因此通常很难调试。因此,我建议避免在并行计划中使用保序迭代器,尤其是在高 DOP 时。在我所做的一些演讲中,我确实解释了一个非常简单的顺序保留并行死锁示例,但真实的示例总是更复杂,尽管根本原因是相同的。
如果这些概念不熟悉,遵循以下示例可能会有所帮助,该示例来自Goetz Graefe的(有点史诗般的)1993 年论文《大型数据库查询评估技术》:
如果使用与范围分区不同的分区策略,则不能保证在所有情况下对后续分区进行排序都是无死锁的。如果(i)多个消费者提供多个生产者,并且(ii)每个生产者产生一个排序的流并且每个消费者合并多个排序的流,并且(iii)使用一些基于键的分区规则而不是范围分区,则将发生死锁,即,散列分区,(iv)启用流量控制,以及(v)数据分布特别不幸。
图 37 显示了具有两个生产者和两个消费者进程的场景,即,生产者操作符和消费者操作符都以并行度为 2 执行。图 37 中的圆圈表示进程,箭头表示数据路径。假设左排序产生流 1, 3, 5, 7, ..., 999, 1002, 1004, 1006, 1008, ..., 2000 而右排序产生流 2, 4, 6, 8, .. ., 1000, 1001, 1003, 1005, 1007, ..., 1999。
消费者进程中的合并操作必须从每个生产者进程接收第一项,然后才能创建他们的第一个输出项并从其输入缓冲区中删除其他项。但是,在将第一个项目发送给另一个消费者之前,生产者需要每个生产 500 个项目(并将它们插入一个消费者的输入缓冲区,一个消费者所有 500 个)。数据交换缓冲区在一个时间点需要容纳 1000 个项目,图 37 的每一侧各 500 个。如果启用了流控,并且交换缓冲区(流控制 slack)少于 500 个项目,则会发生死锁。
在这种情况下可能发生死锁的原因是,生产者进程需要按照从其输入子计划(图 37 中的排序)获得的顺序发送数据,而消费者进程需要按照合并的要求按排序顺序接收数据。因此,有两个方面都需要对数据通过进程边界的顺序进行绝对控制。如果这两个要求不兼容,则需要一个无界缓冲区来确保免于死锁。