sql-server - 为什么 Index-seek 是成本最高的算子,而不是这个计划中的 Filter?
问题描述
我有这个计划需要大约 18 秒才能运行(由 SentryOne 计划资源管理器生成):
使用Index-seek
了一个非聚集索引,带有一些搜索谓词。而Filter
运算符有一些其他谓词,包括LIKE
条件。
这是查询(种类):
SELECT TOP(20) *
FROM Table
WHERE Table.Col1 = ''
AND Table.Col2 LIKE '%' + @searchString + '%'
ORDER BY Table.Col3
第一个(不相关的)问题:
如何阅读执行计划?
当我读到它时,“SQL”从最左边的 operator 开始Top
,然后它会在右边调用它的子运算符(节点)以获得 1 个单行并继续前进,直到它达到它的目标(20 行)。如果没有Top
,那么它将继续运行,直到它的子节点说没有更多了。并且子节点会调用子节点的子节点。因此在上述计划Filter
中调用了Index-seek
290 万次以获取 290 万行。我希望我是对的?
然后我对查询做了一个小改动:
SELECT TOP(20) *
FROM Table
WHERE Col1 = ''
AND Col2 LIKE @searchString + '%' -- search for string prefix only
ORDER BY Table.Col3
这次查询的运行时间 < 2 秒。实际执行与上面完全相同。我比较了 SSMS 中的 2 个。Index-seek
仍然是成本最高的运算符,但它返回的行数相同(290 万)。
第二个问题:
我对LIKE
条件所做的更改必须减少 16 秒的执行时间。那么为什么会Index-seek
被标记为最昂贵的呢?不应该是Filter
因为它是处理LIKE
搜索的人吗?如果我不做这个实验,我怎么知道LIKE
这里的条件是邪恶的?
第三个问题:
我如何知道 SQL 处理一项任务所花费的时间是否恰到好处?
我问这个问题是因为我是优化查询的新手。我第一次看到这个计划时,我不知道Index-seek
花费 18 秒的 300 万行是正确的还是有问题的。
解决方案
阅读执行计划
从右到左。SQL Server 创建一个执行计划,从右侧开始。(数据所在的一侧)
因此,在您的示例中,发生索引查找,将 N 行数据流式传输到左侧的下一个运算符,然后过滤器处理内存(页面缓冲区)中的所有行,依此类推。
要查看实际情况,您可以在 SSMS中打开实时查询统计信息。这样您就可以看到流经每个运算符的数据。很高兴看到查询引擎在运行。
成本
成本是相对的(基于估计的子树成本)。从磁盘(SSD 或 HDD)获取数据是 I/O。I/O 仍然是一项昂贵的操作。您会看到,在index seek
从磁盘检索数据之前,它已经在处理过滤器了。否则,您将进行全表扫描或索引扫描。把它想象成电话簿:如果你按姓氏查找一个人,你可以跳过大部分页面,因为你知道人名的第一个字母,电话簿是按姓名按字母顺序排列的。
您示例中的过滤器成本不高,因为所有行都在内存中,并且索引搜索可能已经过滤掉了 N 行。
适当的时间
时间是相对的。依赖于
- 硬件:SQL 服务器上的查询可能需要 16 秒,但在具有 128 GB 内存的机器上运行它,而所有数据都已经在缓冲池中,它可能需要 < 100 毫秒。
- 缓冲区缓存:所有数据,或部分数据已经可以缓存在内存中
- 执行计划缓存:是第一次执行(或
OPTION(RECOMPILE)
),那么 SQL Server 需要快速猜测出最便宜的执行计划。此查询的第二次执行可以重用该计划,但对于新传递的参数,它可能不是一个好的执行计划。 - 并行性:查询可以并行以减少整体执行时间,但会增加 CPU 时间。D取决于您的内核和配置以获得最大并行度和并行度成本阈值
- 索引碎片:当发生大量更新/删除时,索引可能会被碎片化。
- 统计信息:统计信息用于猜测将返回多少行,这种估计在很多情况下可能是错误的。(统计数据不是最新的,索引中的数据种类繁多,201 桶问题,...)
- 锁定和隔离级别:当您尝试读取数据时,不同的事务可能正在更改数据。锁定查询数据,增加执行时间。
如果您想要一个正确的比较数字,您可以使用SET STATISTICS IO ON
打开Statistics IO。如果您在运行查询后在 SSMS 中打开消息选项卡,您将看到类似的内容。
Table 'Customer'. Scan count 1, logical reads 4152, physical reads 1, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
逻辑读取是关键指标。它指示读取多少AK1 页以创建您想要的结果。
了解一个表上可以发生多少逻辑读取(在聚集索引上)。您可以在启用统计信息 IO 的情况下执行简单SELECT count(*) FROM table
操作,并将逻辑读取与您的查询进行比较。
当查询是 nonSARGABLE 时(在您的示例中LIKE '%' + @Param + '%'
),您可能有更多的逻辑读取,然后您的表中有页面。
如果您查看电话簿示例并且想要检索所有姓氏中包含“Do”的人,则需要扫描整本电话簿 = nonSARGABLE。如果将其更改为开头,则可以进行索引查找,因为您知道 Do 的第一次出现在哪里,而 Dp 的第一次出现在哪里。
推荐阅读
- excel - 不同计算机上图形的大小和位置 VBA EXCEL
- javascript - Vue 异步组件不适用于 Webpack 热模块替换
- django - 如何以 DRY 方式创建带有嵌套 url 的 rest api?
- python - 我正在使用简单的神经网络来解决 IRIS 数据集问题。NN 的输出是多标签多类而不是单标签
- reactjs - Sass @use - 多次导入同一个文件
- ios - 有什么方法可以在 macOS 中创建触控板事件
- visual-studio-code - VScode 在自动完成弹出窗口中显示可用项目的所有详细信息
- python - Geopandas:shapefile中现有图形中的颜色特定集群
- html - 调整大小时导航栏被切断
- powershell - 使用便携式 VSCode 和便携式 powershell