`
javatome
  • 浏览: 845186 次
  • 性别: Icon_minigender_1
  • 来自: 北京
文章分类
社区版块
存档分类
最新评论

TOP 1比不加TOP慢的疑惑

阅读更多

问题描述:

有一个查询如下,去掉 TOP 1 的时候,很快就出来结果了,但加上 TOP 1 的时候,一般要 2~3 秒才出数据,何解?

SELECT TOP 1

    A. INVNO

FROM A, B

WHERE A. Item = B. ItemNumber

    AND B. OwnerCompanyCode IS NOT NULL

 

问题原因分析:

在使用 TOP 1 的时候, SQL Server 会尽力先找出这条 TOP 1 的记录,这就导致它采用了与不加 TOP 时不一致的扫描算法, SQL Server 查询优化器始终认为,应该可以比较快的找到匹配的第 1 条记录,所以一般是使用嵌套循环的联接,则不加 TOP 1 时, SQL Server 会根据结构和数据的统计信息决策出联接策略。 嵌套循环一般适用于联系的两个表,一个表的数据较大,而另一个表的数据较小的情况 ,如果查询匹配的值出现在扫描的前端,则在取 TOP 1 的情况下,是符合嵌套循环联系的使用条件的,但当匹配的数据出现在扫描的后端,或者是基本上没有匹配的数据时,则嵌套循环要扫描完成两个大表,这显然是不适宜的,也正是因为这种情况,导致了 TOP 1 比不加 TOP 1 的效率慢很多

 

关于此问题的模拟环境:

USE tempdb

GO

 

SET NOCOUNT ON

--======================================

-- 创建测试环境

--======================================

RAISERROR ( ' 创建测试环境 ' , 10, 1) WITH NOWAIT

-- Table A

CREATE TABLE [dbo]. A(

    [TranNumber] [int] IDENTITY ( 1, 1) NOT NULL,

    [INVNO] [char]( 8) NOT NULL,

    [ITEM] [char]( 15) NULL DEFAULT ( '' ),

    PRIMARY KEY ( [TranNumber])

)

 

CREATE INDEX [indexONinvno] ON [dbo]. A( [INVNO])

CREATE INDEX [indexOnitem] ON [dbo]. A ( [ITEM])

CREATE INDEX [indexONiteminnvo] ON [dbo]. A( [INVNO], [ITEM])

GO

 

-- Table B

CREATE TABLE [dbo]. B(

    [ItemNumber] [char]( 15) NOT NULL DEFAULT ( '' ),

    [CompanyCode] [char] ( 4) NOT NULL,

    [OwnerCompanyCode] [char]( 4) NULL,

    PRIMARY KEY ( [ItemNumber], [CompanyCode])

)

 

CREATE INDEX [ItemNumber] ON [dbo]. B( [ItemNumber])

CREATE INDEX [CompanyCode] ON [dbo]. B( [CompanyCode])

CREATE INDEX [OwnerCompanyCode] ON [dbo]. B( [OwnerCompanyCode])

GO

 

--======================================

-- 生成测试数据

--======================================

RAISERROR ( ' 生成测试数据 ' , 10, 1) WITH NOWAIT

INSERT [dbo]. A( [INVNO], [ITEM])

SELECT LEFT( NEWID (), 8), RIGHT( NEWID (), 15)

FROM syscolumns A, syscolumns B

 

INSERT [dbo]. B( [ItemNumber], [CompanyCode], [OwnerCompanyCode])

SELECT RIGHT( NEWID (), 15), LEFT( NEWID (), 4), LEFT( NEWID (), 4)

FROM syscolumns A, syscolumns B

GO

 

速度测试脚本:

--======================================

-- 进行查询测试

--======================================

RAISERROR ( ' 进行查询测试 ' , 10, 1) WITH NOWAIT

DECLARE @dt DATETIME , @id int , @loop int

DECLARE @ TABLE (

    id int IDENTITY ,

    [TOP 1] int ,

    [WITHOUT TOP] int )

 

SET @loop = 0

WHILE @loop < 10

BEGIN

    SET @loop = @loop + 1

    RAISERROR ( 'test %d' , 10, 1, @loop) WITH NOWAIT

    SET @dt = GETDATE ()

        SELECT TOP 1

            A. INVNO

        FROM A, B

        WHERE A. Item = B. ItemNumber

            AND B. OwnerCompanyCode IS NOT NULL

    INSERT @([TOP 1]) VALUES ( DATEDIFF ( ms, @dt, GETDATE ()))

    SELECT @id = SCOPE_IDENTITY (), @dt = GETDATE ()

        SELECT --TOP 1

            A. INVNO

        FROM A, B

        WHERE A. Item = B. ItemNumber

            AND B. OwnerCompanyCode IS NOT NULL

    UPDATE @ SET [WITHOUT TOP] = DATEDIFF ( ms, @dt, GETDATE ())

    WHERE id = @id

END

SELECT * FROM @

UNION ALL

SELECT NULL, SUM ( [TOP 1]), SUM ( [WITHOUT TOP]) FROM @

GO

 

测试数据的变更脚本:

DECLARE @value char ( 15), @value1 char ( 15)

SELECT

    @value = LEFT( NEWID (), 15),

    @value1 = LEFT( NEWID (), 15)

 

UPDATE A

SET Item = @value

FROM A

    INNER JOIN(

        SELECT TOP 1

            [TranNumber]

        FROM (

            SELECT TOP 20 PERCENT

                [TranNumber]

            FROM A

            ORDER BY [TranNumber]

        ) AA

        ORDER BY [TranNumber] DESC

    ) B

        ON A. [TranNumber] = B. [TranNumber]

 

UPDATE B

SET ItemNumber = @value

FROM B

    INNER JOIN(

        SELECT TOP 1

            [ItemNumber], [CompanyCode]

        FROM (

            SELECT TOP 20 PERCENT

                [ItemNumber], [CompanyCode]

            FROM B

            ORDER BY [ItemNumber], [CompanyCode]

        ) BB

        ORDER BY [ItemNumber] DESC , [CompanyCode] DESC

    ) B1

        ON B. [ItemNumber] = B1. [ItemNumber]

            AND B. [CompanyCode] = B1. [CompanyCode]

GO

 

测试说明:

1.   在刚建立好测试环境的时候,是没有任何匹配项的,这时候, TOP 1 会扫描两个表的所有数据,运行“速度测试脚本 ”可以看到此时有无 TOP 1 的效率差异: TOP 1 明显比不加 TOP

2.   修改“测试数据的变更脚本 ”中,红色的 20 让匹配的数据出现在扫描的顶端、中间和尾端,分别使用 速度测试脚本 ”测试,可以看到,匹配的值靠近扫描的前端的时候, TOP 1 比不加 TOP 快,随着匹配数据很后端的推移,这种效率差异会越来越小,到后面就变成 TOP 1 比不加 TOP 1 慢。

注意: 每次变更数据,并且完成“速度测试脚本 ”测试后,需要修改“测试数据的变更脚本 ”中,红色的 @ value @value1 ,让刚才设置匹配的数据再变回为不匹配

 

附:联接的几种方式

1.     嵌套循环联接

嵌套循环联接也称为 嵌套迭代 ,它将一个联接输入用作外部输入表(显示为图形执行计划中的顶端输入),将另一个联接输入用作内部(底端)输入表。外部循环逐行处理外部输入表。内部循环会针对每个外部行执行,在内部输入表中搜索匹配行。

最简单的情况是,搜索时扫描整个表或索引;这称为 单纯嵌套循环联接 。如果搜索时使用索引,则称为 索引嵌套循环联接 。如果将索引生成为查询计划的一部分(并在查询完成后立即将索引破坏),则称为 临时索引嵌套循环联接 。查询优化器考虑了所有这些不同情况。

如果外部输入较小而内部输入较大且预先创建了索引,则嵌套循环联接尤其有效。在许多小事务中(如那些只影响较小的一组行的事务),索引嵌套循环联接优于合并联接和哈希联接。但在大型查询中,嵌套循环联接通常不是最佳选择。

 

2.     合并联接

合并联接要求两个输入都在合并列上排序,而合并列由联接谓词的等效 (ON) 子句定义。通常,查询优化器扫描索引(如果在适当的一组列上存在索引),或在合并联接的下面放一个排序运算符。在极少数情况下,虽然可能有多个等效子句,但只用其中一些可用的等效子句获得合并列。

由于每个输入都已排序,因此 Merge Join 运算符将从每个输入获取一行并将其进行比较。例如,对于内联接操作,如果行相等则返回。如果行不相等,则废弃值较小的行并从该输入获得另一行。这一过程将重复进行,直到处理完所有的行为止。

合并联接操作可以是常规操作,也可以是多对多操作。多对多合并联接使用临时表存储行。如果每个输入中有重复值,则在处理其中一个输入中的每个重复项时,另一个输入必须重绕到重复项的开始位置。

如果存在驻留谓词,则所有满足合并谓词的行都将对该驻留谓词取值,而只返回那些满足该驻留谓词的行。

合并联接本身的速度很快,但如果需要排序操作,选择合并联接就会非常费时。然而,如果数据量很大且能够从现有 B 树索引中获得预排序的所需数据,则合并联接通常是最快的可用联接算法。

 

3.     哈希联接

哈希联接有两种输入:生成输入和探测输入。查询优化器指派这些角色,使两个输入中较小的那个作为生成输入。

哈希联接用于多种设置匹配操作:内部联接;左外部联接、右外部联接和完全外部联接;左半联接和右半联接;交集;联合和差异。此外,哈希联接的某种变形可以进行重复删除和分组,例如 SUM(salary) GROUP BY department 。这些修改对生成和探测角色只使用一个输入。

以下几节介绍了不同类型的哈希联接:内存中的哈希联接、 Grace 哈希联接和递归哈希联接。

内存中的哈希联接

哈 希联接先扫描或计算整个生成输入,然后在内存中生成哈希表。根据计算得出的哈希键的哈希值,将每行插入哈希存储桶。如果整个生成输入小于可用内存,则可以 将所有行都插入哈希表中。生成阶段之后是探测阶段。一次一行地对整个探测输入进行扫描或计算,并为每个探测行计算哈希键的值,扫描相应的哈希存储桶并生成 匹配项。

Grace 哈希联接

如果生成输入大于内存,哈希联接将分为几步进行。这称为 “Grace 哈希联接 。 每一步都分为生成阶段和探测阶段。首先,消耗整个生成和探测输入并将其分区(使用哈希键上的哈希函数)为多个文件。对哈希键使用哈希函数可以保证任意两个 联接记录一定位于相同的文件对中。因此,联接两个大输入的任务简化为相同任务的多个较小的实例。然后将哈希联接应用于每对分区文件。

递归哈希联接

如果生成输入非常大,以至于标准外部合并的输入需要多个合并级别,则需要多个分区步骤和多个分区级别。如果只有某些分区较大,则只需对那些分区使用附加的分区步骤。为了使所有分区步骤尽可能快,将使用大的异步 I/O 操作以便单个线程就能使多个磁盘驱动器繁忙工作。

border: 1pt solid #dedfef; padding: 0cm
1
3
分享到:
评论

相关推荐

    TOP264vg规格书_中文

    1. 高效率:TOP264vg规格书能够提供极高的能效,在265 VAC输入时空载功耗低于100 mW,在230 VAC输入时输入功率为1 W时,待机输出功率高达750 mW。 2. 多模式PWM控制技术:TOP264vg规格书采用多模式PWM控制技术,...

    Linux 系统top源码包

    1. **数据采集**:源码会展示`top`是如何通过系统调用来获取实时的系统状态信息,如`/proc`文件系统中的信息。 2. **进程调度**:`top`如何对进程进行排序,以及如何根据不同的参数(如CPU使用率、内存使用量等)...

    linux top命令源代码

    1. **进程信息获取**:`top`命令通过`/proc`文件系统获取系统和进程的信息。`/proc`是一个虚拟文件系统,它提供了运行时进程的状态信息。源码会解析这些文件以获取CPU使用率、内存使用情况、进程ID等。 2. **CPU和...

    MapReduce编程之求Top3(TopN)

    (45, 1), (3, 1), (78, 1), (456, 1), (70, 1), (1, 1), (999, 1) ``` 2. **Shuffle阶段**: - Map阶段的输出被分割并按照键进行排序,确保相同键的值被分发到同一个Reducer。在这个例子中,所有相同的数字会...

    TIPTOP T100导出EXCEL加超链接.docx

    文档是解决鼎捷TIPTOP GP以及T100 POI导出EXCEL的时候需要在字段里面加超链接。我这个案例是用于加网页链接,后期要是EXCEL页面内跳转也可以参考。

    top 源代码 top 源代码top 源代码

    top 源代码top 源代码top 源代码top 源代码top 源代码top 源代码

    tiptop TOPGP-5.3数据字典.chm

    鼎捷ERP tiptop TOPGP5.3数据库字典

    top853编程驱动器驱动盘

    1. 驱动安装程序:这是将TOP853编程驱动器软件安装到用户计算机上的文件,通常为.exe或.msi格式。 2. 用户手册:详细解释如何使用TOP853编程驱动器,包括连接设备、配置参数、编程步骤等。 3. 驱动更新程序:用于...

    Solaris系统上top工具top-3.6.1-sol10-x86-local

    1、按之上的连接把top-3.6-sol9-sparc-local.gz存在你的机器上,并上传到服务器.我把它传到了/tmp/yang这个目录。 sun480-1#[/tmp/yang]ls -l 总数 432 -rw-r--r-- 1 root other 215455 6月 21 15:08 top-3.6-sol9...

    TIPTOP ERP不完全实施开发手册.chm

    TIPTOP ERP不完全实施开发手册,里面有常用linux命令,TIPTOP、JAVA、jsp、oracle常用命令

    TIPTOP與其他系統之WebService接口資料

    1. 接口设计:TIPTOP系统可能会为外部系统提供一系列API,这些API通过Web Service的形式公开,允许外部系统按照定义的接口调用TIPTOP的功能。 2. 安全性:为了确保数据安全,接口通常会实施身份验证和授权机制,如...

    top源代码 top-3.8beta1.tar.gz

    《深入解析top命令:探索top-3.8beta1源代码》 在Linux系统中,top命令是一个实时显示系统状态的动态视图工具,它能够提供关于系统资源使用情况的实时监控,包括CPU使用率、内存占用、进程状态等关键信息。当我们...

    c#调用topsdk、调用topapi最新C#调用淘宝sdk_demo

    1. **安装TopSDK**:你需要将TopSDK的NuGet包导入到你的C#项目中。在Visual Studio中,可以通过NuGet包管理器找到并安装"Alibaba.Taobao.SDK.Core"。 2. **初始化配置**:在代码中,你需要创建一个`...

    PCB中TOP PASTE和TOP SOLDER的区别

    其次,在SMT封装中,TOP LAYER和TOP PASTE是同样大小的,而TOP SOLDER则比它们稍微大一圈,因为TOP SOLDER是为了匹配焊盘,是自动产生的。在PCB板设计时,阻焊层上默认会有SOLDER层,因此焊接面的焊盘部分是上了银白...

    Linux下使用python调用top命令获得CPU利用率

    这个就不解释了,不懂的朋友查询下top的帮助文档。这里要实现的是通过python调用top命令,并获得cpu的利用率信息。 用过popen的朋友很快就能想到类似如下的代码(这个是我第一次写的代码,*_*): 复制代码 代码如下...

    tophat变换matlab代码

    1. 原始图像文件,可能是.jpg、.png或.mat格式,用于演示Tophat变换前的图像状态。 2. 处理后的图像文件,同样可能是.jpg、.png或.mat格式,展示了Tophat变换的效果。 3. MATLAB代码文件,可能是.m文件,实现了Top...

    托普Top编程器软件8.82

    【标题】"托普Top编程器软件8.82"是指一种专用于编程微控制器的软件工具,由托普公司开发。这个版本是8.82,表明它是该软件的一个更新迭代,通常会包含错误修复、性能提升以及可能的新功能。 【描述】提到的“支持...

    TOP851编程器软件

    【标题】"TOP851编程器软件"指的是专门用于编程和烧录TOP851型号编程器的软件工具。这种编程器常被用于电子工程领域,尤其是对微控制器(MCU)进行编程,比如8位或16位的单片机。在描述中提到,该软件适用于较旧的...

    TOP 851烧录软件

    尽管如此,对于某些老款设备的维护和修复,或者对经典硬件的爱好者来说,TOP 851这类软件仍然是不可或缺的资源。 在提供的文件列表中,“top851v53.exe”可能是TOP 851软件的安装程序,版本为5.3。这表明用户可以...

    OWASP-TOP10-2021 最新中文版V1.0.pdf

    该报告涵盖了 Web 应用程序安全的十个最常见的风险,包括失效的访问控制、加密机制失效、注入、不安全设计、安全配置错误、自带缺陷和过时的组件、身份识别和身份验证错误、软件和数据完整性故障、安全日志和监控...

Global site tag (gtag.js) - Google Analytics