热门标签 | HotTags
当前位置:  开发笔记 > 数据库 > 正文

使用IN子句,SQL偏移总行数变慢

如何解决《使用IN子句,SQL偏移总行数变慢》经验,为你挑选了2个好方法。

我正在使用以下基于另一个答案的SQL代码。但是,当包含大量in子句时,获取总数需要花费太长时间。如果删除总数,则查询将花费不到1秒的时间。有没有更有效的方法来获取总行数?我看到的答案基于2013 SQL查询。

DECLARE 
    @PageSize INT = 10, 
    @PageNum  INT = 1;

WITH TempResult AS(
    SELECT ID, Name
    FROM Table
     Where ID in ( 1 ,2 3, 4, 5, 6, 7, 8, 9 ,10)
), TempCount AS (
    SELECT COUNT(*) AS MaxRows FROM TempResult
)
SELECT *
FROM TempResult, 
 TempCount    <----- this is what is slow. Removing this and the query is super fast
ORDER BY TempResult.Name
    OFFSET (@PageNum-1)*@PageSize ROWS
    FETCH NEXT @PageSize ROWS ONLY

Thailo.. 5

据我所知,除了使用已经提到的#temp表方法之外,还有3种方法可以实现此目的。在下面的测试案例中,我使用了具有6CPU / 16GB RAM的SQL Server 2016 Developer实例,以及一个包含约2500万行的简单表。

方法1:交叉加入

DECLARE
  @PageSize INT = 10
, @PageNum  INT = 1;

WITH TempResult AS (SELECT
                          id
                        , shortDesc
                    FROM  dbo.TestName
                    WHERE id IN (1, 2, 3, 4, 5, 6, 7, 8, 9, 10))
SELECT
           *, MaxRows
FROM       TempResult
CROSS JOIN (SELECT COUNT(1) AS MaxRows FROM TempResult) AS TheCount
ORDER BY   TempResult.shortDesc OFFSET (@PageNum - 1) * @PageSize ROWS 
FETCH NEXT @PageSize ROWS ONLY;

测试结果1:

方法2:COUNT(*)OVER()

DECLARE
  @PageSize INT = 10
, @PageNum  INT = 1;

WITH TempResult AS (SELECT
                          id
                        , shortDesc
                    FROM  dbo.TestName
                    WHERE id IN (1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
)
SELECT
         *, MaxRows = COUNT(*) OVER()
FROM     TempResult
ORDER BY TempResult.shortDesc OFFSET (@PageNum - 1) * @PageSize ROWS
FETCH NEXT @PageSize ROWS ONLY;

测试结果2:

方法3:第二次CTE

测试结果3(使用的T-SQL与问题中的相同):

结论

最快的方法取决于您的数据结构(和总行数)以及服务器的大小/负载。以我为例,使用COUNT(*)OVER()被证明是最快的方法。为了找到最适合您的方案,您必须测试最适合您的方案。并且也不排除#table方法还没有;-)



1> Thailo..:

据我所知,除了使用已经提到的#temp表方法之外,还有3种方法可以实现此目的。在下面的测试案例中,我使用了具有6CPU / 16GB RAM的SQL Server 2016 Developer实例,以及一个包含约2500万行的简单表。

方法1:交叉加入

DECLARE
  @PageSize INT = 10
, @PageNum  INT = 1;

WITH TempResult AS (SELECT
                          id
                        , shortDesc
                    FROM  dbo.TestName
                    WHERE id IN (1, 2, 3, 4, 5, 6, 7, 8, 9, 10))
SELECT
           *, MaxRows
FROM       TempResult
CROSS JOIN (SELECT COUNT(1) AS MaxRows FROM TempResult) AS TheCount
ORDER BY   TempResult.shortDesc OFFSET (@PageNum - 1) * @PageSize ROWS 
FETCH NEXT @PageSize ROWS ONLY;

测试结果1:

方法2:COUNT(*)OVER()

DECLARE
  @PageSize INT = 10
, @PageNum  INT = 1;

WITH TempResult AS (SELECT
                          id
                        , shortDesc
                    FROM  dbo.TestName
                    WHERE id IN (1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
)
SELECT
         *, MaxRows = COUNT(*) OVER()
FROM     TempResult
ORDER BY TempResult.shortDesc OFFSET (@PageNum - 1) * @PageSize ROWS
FETCH NEXT @PageSize ROWS ONLY;

测试结果2:

方法3:第二次CTE

测试结果3(使用的T-SQL与问题中的相同):

结论

最快的方法取决于您的数据结构(和总行数)以及服务器的大小/负载。以我为例,使用COUNT(*)OVER()被证明是最快的方法。为了找到最适合您的方案,您必须测试最适合您的方案。并且也不排除#table方法还没有;-)



2> Dannnno..:

与性能相关的问题的第一步将是分析表/索引结构,并检查查询计划。您尚未提供该信息,所以我将自行整理,然后从那里开始。

我将假设您有一个堆,其中有约1000万行(对我来说是12,872,738):

DECLARE @MaxRowCount bigint = 10000000,
        @Offset      bigint = 0;

DROP TABLE IF EXISTS #ExampleTable;
CREATE TABLE #ExampleTable
(
  ID   bigint      NOT NULL,
  Name varchar(50) COLLATE DATABASE_DEFAULT NOT NULL
);

WHILE @Offset <@MaxRowCount
BEGIN
  INSERT INTO #ExampleTable
  ( ID, Name )
    SELECT ROW_NUMBER() OVER ( ORDER BY ( SELECT NULL )),
           ROW_NUMBER() OVER ( ORDER BY ( SELECT NULL ))
      FROM master.dbo.spt_values SV
        CROSS APPLY master.dbo.spt_values SV2;
  SET @Offset = @Offset + ROWCOUNT_BIG();
END;

如果我运行over over提供的查询#ExampleTable,则大约需要4秒钟,并给出以下查询计划:

无论如何,这都不是一个很好的查询计划,但这并不可怕。使用实时查询统计数据运行时,显示基数估计最多相差一个,这很好。

让我们给出IN列表中的大量项目(1-5000中有5000个项目)。编制计划花了4秒钟:

在查询处理器停止处理之前,我最多可以获取15000个项目的编号,而查询计划没有任何变化(编译过程总共需要6秒钟)。在我的计算机上运行两个查询大约需要5秒钟。

对于分析工作负载或数据仓库来说,这可能很好,但是对于像OLTP这样的查询,我们肯定超出了我们的理想时间限制。

让我们看一些替代方案。我们可能可以将其中一些组合在一起。

    我们可以将IN列表缓存在临时表或表变量中。

    我们可以使用窗口函数来计算计数

    我们可以将CTE缓存在临时表或表变量中

    如果在足够高的SQL Server版本上,请使用批处理模式

    更改表上的索引以使其更快。

工作流程注意事项

如果这是用于OLTP工作流程,那么无论我们有多少用户,我们都需要快速的东西。因此,我们希望最大程度地减少重新编译,并且希望在任何可能的地方进行索引查找。如果这是分析或仓储,则重新编译和扫描可能很好。

如果我们需要OLTP,则缓存选项可能不在表格中。临时表将始终强制重新编译,而依赖良好估计的查询中的表变量要求您强制重新编译。替代方法是让应用程序的其他部分维护具有分页计数或过滤器(或两者都有)的持久表,然后对此进行联接。

如果同一用户将查看许多页面,那么即使在OLTP中缓存掉一部分页面仍然值得,但是请确保您衡量了许多并发用户的影响。

不管工作流程如何,更新索引都可以(除非您的工作流程确实会使索引维护陷入困境)。

无论工作流程如何,批处理模式都是您的朋友。

无论工作流程如何,窗口函数(尤其是具有索引和/或批处理模式的窗口函数)可能都会更好。

批处理模式和默认基数估计器

通过传统的基数估计器和行模式执行,我们几乎总是得到差的基数估计(以及由此产生的计划)。强制默认基数估计值有助于第一个,而批处理模式则有助于第二个。

如果您无法更新数据库以使用新的基数估计器批发,则需要为特定查询启用它。为此,可以使用以下查询提示:OPTION( USE HINT( 'FORCE_DEFAULT_CARDINALITY_ESTIMATION' ) )获取第一个。第二,向CCI添加一个LEFT OUTER JOIN dbo.EmptyCciForRowstoreBatchmode ON 1 = 0联接(不需要返回数据):-这使SQL Server可以选择批处理模式优化。这些建议假定使用了足够新的SQL Server版本。

CCI无关紧要;为了保持一致性,我们希望保留一个空白,如下所示:

CREATE TABLE dbo.EmptyCciForRowstoreBatchmode
(
  __zzDoNotUse int NULL,
  INDEX CCI CLUSTERED COLUMNSTORE
);

我不修改表就可以得到的最好计划是同时使用它们。使用与以前相同的数据,运行时间不到1秒。

WITH TempResult AS
(
  SELECT ID,
         Name,
         COUNT( * ) OVER ( ) MaxRows
    FROM #ExampleTable
    WHERE ID IN ( <> )
)
  SELECT TempResult.ID,
         TempResult.Name,
         TempResult.MaxRows
    FROM TempResult
      LEFT OUTER JOIN dbo.EmptyCciForRowstoreBatchmode ON 1 = 0
    ORDER BY TempResult.Name OFFSET ( @PageNum - 1 ) * @PageSize ROWS FETCH NEXT @PageSize ROWS ONLY
    OPTION( USE HINT( 'FORCE_DEFAULT_CARDINALITY_ESTIMATION' ) );


推荐阅读
  • MySQL缓存机制深度解析
    本文详细探讨了MySQL的缓存机制,包括主从复制、读写分离以及缓存同步策略等内容。通过理解这些概念和技术,读者可以更好地优化数据库性能。 ... [详细]
  • Windows服务与数据库交互问题解析
    本文探讨了在Windows 10(64位)环境下开发的Windows服务,旨在定期向本地MS SQL Server (v.11)插入记录。尽管服务已成功安装并运行,但记录并未正确插入。我们将详细分析可能的原因及解决方案。 ... [详细]
  • 1:有如下一段程序:packagea.b.c;publicclassTest{privatestaticinti0;publicintgetNext(){return ... [详细]
  • 深入理解Cookie与Session会话管理
    本文详细介绍了如何通过HTTP响应和请求处理浏览器的Cookie信息,以及如何创建、设置和管理Cookie。同时探讨了会话跟踪技术中的Session机制,解释其原理及应用场景。 ... [详细]
  • 深入理解 SQL 视图、存储过程与事务
    本文详细介绍了SQL中的视图、存储过程和事务的概念及应用。视图为用户提供了一种灵活的数据查询方式,存储过程则封装了复杂的SQL逻辑,而事务确保了数据库操作的完整性和一致性。 ... [详细]
  • 本文详细介绍了Java编程语言中的核心概念和常见面试问题,包括集合类、数据结构、线程处理、Java虚拟机(JVM)、HTTP协议以及Git操作等方面的内容。通过深入分析每个主题,帮助读者更好地理解Java的关键特性和最佳实践。 ... [详细]
  • 在当前众多持久层框架中,MyBatis(前身为iBatis)凭借其轻量级、易用性和对SQL的直接支持,成为许多开发者的首选。本文将详细探讨MyBatis的核心概念、设计理念及其优势。 ... [详细]
  • 本文详细介绍了 MySQL 的查询处理流程,包括从客户端连接到服务器、查询缓存检查、语句解析、查询优化及执行等步骤。同时,深入探讨了 MySQL 中的乐观锁机制及其在并发控制中的应用。 ... [详细]
  • 网络运维工程师负责确保企业IT基础设施的稳定运行,保障业务连续性和数据安全。他们需要具备多种技能,包括搭建和维护网络环境、监控系统性能、处理突发事件等。本文将探讨网络运维工程师的职业前景及其平均薪酬水平。 ... [详细]
  • MySQL 数据库迁移指南:从本地到远程及磁盘间迁移
    本文详细介绍了如何在不同场景下进行 MySQL 数据库的迁移,包括从一个硬盘迁移到另一个硬盘、从一台计算机迁移到另一台计算机,以及解决迁移过程中可能遇到的问题。 ... [详细]
  • PHP 5.5.0rc1 发布:深入解析 Zend OPcache
    2013年5月9日,PHP官方发布了PHP 5.5.0rc1和PHP 5.4.15正式版,这两个版本均支持64位环境。本文将详细介绍Zend OPcache的功能及其在Windows环境下的配置与测试。 ... [详细]
  • 百度服务再次遭遇技术问题,疑似DNS解析故障
    近日晚间,百度多项在线服务出现加载异常,包括移动端搜索在内的多个功能受到影响。初步迹象表明,问题可能与DNS服务器解析有关。 ... [详细]
  • 深入解析 Apache Shiro 安全框架架构
    本文详细介绍了 Apache Shiro,一个强大且灵活的开源安全框架。Shiro 专注于简化身份验证、授权、会话管理和加密等复杂的安全操作,使开发者能够更轻松地保护应用程序。其核心目标是提供易于使用和理解的API,同时确保高度的安全性和灵活性。 ... [详细]
  • 本文详细分析了Hive在启动过程中遇到的权限拒绝错误,并提供了多种解决方案,包括调整文件权限、用户组设置以及环境变量配置等。 ... [详细]
  • 本文探讨了如何优化和正确配置Kafka Streams应用程序以确保准确的状态存储查询。通过调整配置参数和代码逻辑,可以有效解决数据不一致的问题。 ... [详细]
author-avatar
不变de诺言2502890365
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有