【问题标题】:Slow paging with SQL query (stored procedure) in SQL Server在 SQL Server 中使用 SQL 查询(存储过程)进行缓慢分页
【发布时间】:2019-02-10 19:32:51
【问题描述】:

我有以下基于各种参数在动态存储过程中生成的 SQL 语句:

SELECT [MetadataId], [DocumentType], InvoiceNumber FROM 
( 
  SELECT [MetadataId], [DocumentType], InvoiceNumber, ROW_NUMBER() 
  OVER (ORDER BY [MetadataId] Asc) 
  AS [Row_ID] 
  FROM [Metadata] 
  WHERE ([DocumentType] = 'Invoice')
) Wrapper 
WHERE Row_ID BETWEEN 999980 AND 1000000

Row_ID 会根据我的网格的当前页面而变化。

当我最初从第 1 页导航到第 2、3、4、5 页等时,上面的查询效果很好,但如果我立即从第 1 页导航到第 50,000 页,我就不能这么说了我的测试数据库中的最后一页,其中包含 100 万张随机生成的发票,其中我的页面大小为 20。

加载大约需要 29/30 秒,我的 SQL Server 实例使用的 RAM 从大约 400MB 变为 1.61GB。

初始延迟过后,立即转到第 49999、49998、49997 等页面,或者在第 1 页和第 50000 页之间来回导航也是即时的。

我只能假设整个数据集以某种方式加载到内存中。

补充说明:

  1. MetadataId 设置为主键。
  2. DocumentType、InvoiceNumber 等其他可搜索的列也已编入索引,但不是唯一的。
  3. 出于各种原因,我需要继续使用动态存储过程,但主要原因是,虽然字段要求因客户而异,但我们的应用程序使用的结果保持不变。
  4. 使用 SQL Server 2014 Developers 版本进行测试。

所以我的问题是:

  1. 谁能向我解释实际发生了什么?所有数据都加载到内存中了吗?

  2. 有没有办法改进这个?请注意,我需要 Row_ID 由 'ROW_NUMBER() OVER' 生成,因为我的 SQL 语句的 WHERE 子句部分可能会根据用户搜索的参数而发生很大变化。

谢谢。

UPDATE-1

这是执行计划:

【问题讨论】:

  • 你看过执行计划,看看发生了什么吗? Row_ID 不是索引还是主键?
  • 当然听起来整个表都已加载到内存中。我怀疑它必须按可能没有被索引的行排序并一直到最后。
  • @RickS,Row_ID 由 Row_Number 函数生成,该函数根据用户在应用程序中选择的内容而有所不同。因此,除非所有可能的排序列都被索引,否则您可能会遇到麻烦。
  • @Thierry,您查看过列存储索引吗?虽然它存在问题,但它可能会解决您的问题,因为基本上您需要在每个(可排序的)列上创建一个索引。否则,请考虑每个可排序列上的非聚集索引并压缩它们以进行存储。
  • @Thierry 和执行计划确实显示出了什么问题。您必须扫描整个表才能生成行号,以便稍后过滤它们。 UnhandledExcepSean 的链接显示了如何解决这个问题。 OFFSET/FETCH 将删除行计算并带来更好的性能。如果您保留当前页面的最后一个 ID 并要求接下来的 N 行大于它,您可以获得更好的性能

标签: sql sql-server tsql stored-procedures sql-server-2012


【解决方案1】:

动态sql不错,希望你用的是sp_executesql。

Row_ID BETWEEN 1 AND 20, then 21 to 40 then 41 to 60, the results are instant

因为执行第一个查询时,计划是缓存,后续查询在 row_id 1 和 20 之间重用,然后是 21 到 40,然后是 41 到 60。

Row_ID BETWEEN 999981 AND 1000000, it takes 28/29 secs for to load

我猜查询优化器会为这些范围创建新计划,因此第一次执行需要更长的时间然后计划被缓存。

下一次 row_id 999980 到 999960 执行得更快,因为它重用了计划。

我认为它有参数嗅探问题。

我认为您的查询有优化空间,但不能不看就说。

OFFSET / FETCH 可能会改进您的查询,因为它会减少一个 Select 语句,但不确定它是否完全依赖于主查询。

这么多计划还不够。Clustered Index Scan 并不总是坏事。

【讨论】:

    【解决方案2】:

    我相信查询中使用的列应该有一个非聚集复合索引,这样查询中的所有表列将被排序在一起并指向 metadata_Id pk;或使用查询的 where 子句上的过滤索引创建的视图。预计存储的磁盘空间消耗。我担心使用 row_id 的 order by 子句不是选择查询列的一部分,而是重命名的列..认为应该有错误。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-06-27
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-12-05
      • 1970-01-01
      相关资源
      最近更新 更多