关闭 x
IT技术网
    技 采 号
    ITJS.cn - 技术改变世界
    • 实用工具
    • 菜鸟教程
    IT采购网 中国存储网 科技号 CIO智库

    IT技术网

    IT采购网
    • 首页
    • 行业资讯
    • 系统运维
      • 操作系统
        • Windows
        • Linux
        • Mac OS
      • 数据库
        • MySQL
        • Oracle
        • SQL Server
      • 网站建设
    • 人工智能
    • 半导体芯片
    • 笔记本电脑
    • 智能手机
    • 智能汽车
    • 编程语言
    IT技术网 - ITJS.CN
    首页 » SQL Server »SQL Server执行SQL语句时内存占用特点(1)

    SQL Server执行SQL语句时内存占用特点(1)

    2015-11-28 00:00:00 出处:ITJS
    分享

    众所周知,SQL Server执行SQL语句的性能判定标准主要是IO读取数大小。本文在不违反这一原则情况下,同时来分析一下部分SQL语句执行时,SQL Server内存的变化情况。

    首先简述一下SQL Server内存占用的特点。SQL Server所占用的内存除程序(即SQL Server引擎)外,主要包括缓存的数据(Buffer)和执行计划(Cache)。SQL Server以8KB大小的页为单位存储数据。这个和SQL Server数据在磁盘上的存储页大小相同。当SQL Server执行SQL 语句时,假如需要的数据已经在其内存中,则直接从内存缓冲区读取并进行必要的运算然后输出执行结果。假如数据还未在内存中,则首先将数据从磁盘上读入内存Buffer中。而我们通常评价SQL性能指标中的IO逻辑读取数对应的正是从内存缓冲区读取的页数,而IO物理读取数则对应数据从磁盘读取的页数。

    注:以下的试验在多人共享的开发测试服务器上也可以进行,因为实际上可以分别看到某个表所占用的内存情况。但为了方便,笔者在做此试验时,在一个单独的、确认没有其它并发任务的数据库上进行,因此所看到的内存变化正是每一次所执行的SQL语句引起的。

    我们首先来看一个简单的实例。创建下表:

    Create Table P_User 

    1.2、执行高选择性选取

    另外,在没有索引的情况下,假如将上例修改为:

    Select Top 1 * From P_Order 或者Select * From P_Order Where MobileNo=28502

    可以看到,系统同样要读取全部的数据页到内存。

    假如使用Select Top 1 * From P_Order Where MobileNo=28502这样的选取方式,有可能会出现只读取部分数据页到内存的情况。但由于在没有索引情况下,数据实际上是无序存放在堆上,所以结果很不稳定,也有可能发生读取所有的数据页到内存。

    测试2:建立聚集索引情况下,执行SQL语句

    2.1、执行全表选取或者低选择性选取

    修改表结构,在MobileNo字段上建立聚集索引。然后再次执行刚才的SQL语句。得到的执行计划变为聚集索引扫描。IO统计消息为:

    (1000 row(s) affected)

    表'P_User'。扫描计数1,逻辑读取6 次,物理读取1 次,预读4 次,lob 逻辑读取0 次,lob 物理读取0 次,lob 预读0 次。

    这里的逻辑读取变为6次。

    内存情况如下:

    图2

    内存中的变化是增加了一个非叶级的聚集索引页,而叶级的聚集索引则会和数据放在一起。

    另外,可以查看该表索引的级别:

    SELECT database_id,object_id,index_id,index_level,page_count,record_count 

    4.1、执行全表选取或者低选择性选取

    Select * From P_Order A 

    测试6:执行Hash Join

    6.1、执行全表选取或者低选择性选取

    对于两表联接,假如两张表都没有索引,不写明联接提示的情况下,SQL Server默认使用hash join。而对于两表联接,假如两张表都有聚集索引,则SQL Server默认使用Merge Join。

    执行SQL:

    Select * From P_Order A 

    众所周知,SQL Server执行SQL语句的性能判定标准主要是IO读取数大小。本文在不违反这一原则情况下,同时来分析一下部分SQL语句执行时,SQL Server内存的变化情况。

    首先简述一下SQL Server内存占用的特点。SQL Server所占用的内存除程序(即SQL Server引擎)外,主要包括缓存的数据(Buffer)和执行计划(Cache)。SQL Server以8KB大小的页为单位存储数据。这个和SQL Server数据在磁盘上的存储页大小相同。当SQL Server执行SQL 语句时,假如需要的数据已经在其内存中,则直接从内存缓冲区读取并进行必要的运算然后输出执行结果。假如数据还未在内存中,则首先将数据从磁盘上读入内存Buffer中。而我们通常评价SQL性能指标中的IO逻辑读取数对应的正是从内存缓冲区读取的页数,而IO物理读取数则对应数据从磁盘读取的页数。

    注:以下的试验在多人共享的开发测试服务器上也可以进行,因为实际上可以分别看到某个表所占用的内存情况。但为了方便,笔者在做此试验时,在一个单独的、确认没有其它并发任务的数据库上进行,因此所看到的内存变化正是每一次所执行的SQL语句引起的。

    我们首先来看一个简单的实例。创建下表:

    Create Table P_User 
    ( UserMobileStatus int NOT NULL,
    MobileNo int NOT NULL,
    LastOpTime DateTime Not NULL
    )

    然后为该表插入一定的数据:

    Declare @i int 
    Set @i=28000
    WHILE @i<29000
    BEGIN
    Insert Into P_User
    Select @i % 2,@i,GetUTCDate()
    Set @i=@i+1
    END

    然后我们在查询分析器中首先执行:

    Set Statistics IO ON

    并按下Ctrl+M以显示实际的执行计划。

    此时,可以开始进行我们的试验了。为了准确观察每一次SQL语句变化情况,在执行第一条SQL语句以前,我们首先清空SQL Server所占用的数据内存:

    CHECKPOINT 
    GO
    DBCC DROPCLEANBUFFERS

    这将清空SQL Server所占用的数据缓冲区(此语句在生产服务器上慎用,因为将导致一段时间内后续的SQL语句执行变慢)。

    测试1:在没有索引的表上执行SQL语句

    1.1、执行全表选取或者低选择性选取

    Select * From P_User

    从SQL执行计划可以看到,由于此时表中没有任何索引,因此将产生Table Scan。而IO统计结果如下:

    (1000 row(s) affected)

    表'P_User'。扫描计数1,逻辑读取4次,物理读取4次,预读0次,lob逻辑读取0次,lob物理读取0次,lob预读0次。

    我们看一下数据库内存中的情况。

    首先查询到我们所操作database的database_id:

    Select database_id From sys.databases Where name='TestGDB'

    然后使用该database_id从表中查看内存情况:

    SELECT * FROM sys.dm_os_buffer_descriptors bd 
    WHERE database_id=5
    order by allocation_unit_id,page_id

    得到结果如下:

    图1

    得到的结果中可以看到,除了必要的管理页(一个PFS_Page和一个IAM_Page)外,内存中总共出现了4个Data_Page页。这和刚才IO统计中看到的结果:逻辑读为4,物理读为4相同。由于是全表读取,表明P_User表全部数据所占用的数据页数也正是4,将这4个数据页的row_count数加起来也可以验证其总数据行=1000。

    在上例中,假如不清空数据缓冲区,再执行一遍SQL,可以看到内存毫无变化,而逻辑读也不变,只是物理读变为0,因为已经不需要再从磁盘读入数据。

    1.2、执行高选择性选取

    另外,在没有索引的情况下,假如将上例修改为:

    Select Top 1 * From P_Order 或者Select * From P_Order Where MobileNo=28502

    可以看到,系统同样要读取全部的数据页到内存。

    假如使用Select Top 1 * From P_Order Where MobileNo=28502这样的选取方式,有可能会出现只读取部分数据页到内存的情况。但由于在没有索引情况下,数据实际上是无序存放在堆上,所以结果很不稳定,也有可能发生读取所有的数据页到内存。

    测试2:建立聚集索引情况下,执行SQL语句

    2.1、执行全表选取或者低选择性选取

    修改表结构,在MobileNo字段上建立聚集索引。然后再次执行刚才的SQL语句。得到的执行计划变为聚集索引扫描。IO统计消息为:

    (1000 row(s) affected)

    表'P_User'。扫描计数1,逻辑读取6 次,物理读取1 次,预读4 次,lob 逻辑读取0 次,lob 物理读取0 次,lob 预读0 次。

    这里的逻辑读取变为6次。

    内存情况如下:

    图2

    内存中的变化是增加了一个非叶级的聚集索引页,而叶级的聚集索引则会和数据放在一起。

    另外,可以查看该表索引的级别:

    SELECT database_id,object_id,index_id,index_level,page_count,record_count 
    FROM sys.dm_db_index_physical_stats
    (DB_ID(N'TestGDB'), OBJECT_ID(N'dbo.P_User'), NULL, NULL , 'DETAILED');

    从结果可以看到该表的聚集索引总共分2级。

    图3

    因而逻辑读增加了2——(由于发生Clustered Index Scan,除了根级别的聚集索引页占用1次外,从根级别聚集索引定位到叶级别的聚集索引也将额外占用1次逻辑读)。

    另外一个变化是只发生了一次物理读,即读取根级别的聚集索引页,另外4个数据页则通过预读方式而不是物理读从磁盘装入内存Buffer。这使得有聚集索引的情况下,执行SQL所直接花费的代价实际上更小。

    2.2、执行高选择性选取

    在建立聚集索引情况下,对性能有益的变化是:

    对于Select Top 1 * From P_Order 或者Select * From P_Order Where MobileNo=28702这样的语句,在有聚集索引情况下,只会将最终记录所在的页读入内存。

    测试3:建立非聚集索引情况下,执行SQL语句

    3.1、执行全表选取或者低选择性选取

    假如将表中同一字段的聚集索引换成非聚集索引,则可以看到如下特点:

    执行全表扫描将和没有任何索引的情况相似,将读取所有的数据页到内存。此时,SQL Server的查询引擎实际上无法使用非聚集索引。

    3.2、执行高选择性选取

    将只读取最终数据所在的页到内存。通过查询计划可以看到,SQL Server在非聚集索引上使用INDEX SEEK,然后通过lookup 得到数据实际所在行(索引覆盖情况下例外,因为不需要定位到实际数据行)。

    测试4:执行Nested Loop Join

    在进行测试前,我们先准备另外一张表和数据。

    Create Table P_Order 
    ( UserStatus int NOT NULL,
    MobileNo int NOT NULL,
    Sid int Not NULL,
    LastSubTime DateTime
    )

    插入数据:

    Declare @i int 
    Set @i=20000
    WHILE @i<30000
    BEGIN
    Insert Into P_Order
    Select @i % 2,@i,@i-19999,GetUTCDate()
    set @i=@i+1
    END

    可以看到,在执行全表扫描情况下,该表10000条数据总共占用38个内存数据页。

    4.1、执行全表选取或者低选择性选取

    Select * From P_Order A 
    Inner Loop JOIN P_User B ON A.MobileNo=B.MobileNo

    对于此种高选择性选择,默认情况下SQL Server不会执行Loop Join。因此,使用了强制性的联接提示。

    在两个表都没有任何索引的情况下,可以看到:

    两个表所有的数据页都将被加载到内存。逻辑读取代价高达6万多次——对于P_Order表中的每一条记录,都将在P_User表中进行遍历。

    在其中一个表有聚集索引情况下,尽管逻辑读取相比刚才的6万多次已经大大下降,但仍然达到2万次。而且联接的次序对查询性能影响很大。因为其实际执行的是将SQL语句中前面的表作为联接的外部输入,而后面的表作为联接的内部输入。

    在两个表都有聚集索引情况下,相比较而言,逻辑读仍然达到数千次(取决于最终输出的数据大小),但相比较已经大大改善。而且表中的数据只有最终需要输出的部分才会被读入内存Buffer中。

    4.2、执行高选择性选取

    执行如下的SQL语句:

    Select * From P_Order A 
    Inner merge JOIN P_User B ON A.MobileNo=B.MobileNo
    Where A.MobileNo=28913

    在两个表都没有任何索引情况下,两张表都将执行全表扫描。要读入所有的数据页到内存。总体逻辑读取决于两表的数据页数。

    在一个表有聚集索引或者非聚集索引情况下,该表将执行Index Seek,另一个表将出现全表扫描。内存数据缓冲区中,将有一张表只读入最终数据所在的数据页、一张表读入全部数据页。逻辑读数取决于表在联接中的秩序、以及无索引表的数据页数。

    在两个表都有聚集索引情况下,逻辑读最小,每个表只有2到3次。而且只有实际需要输出的数据才会被读入内存页。两个表都有非聚集索引情况下,消耗的逻辑读和内存资源近似。

    测试5:执行Merge Join

    5.1、执行全表选取或者低选择性选取

    执行SQL:

    Select * From P_Order A 
    Inner merge JOIN P_User B ON A.MobileNo=B.MobileNo

    假如两张表都没有任何索引,则两张表都要进行全表扫描。所有的数据都要读入内存页。

    逻辑读数近似等于两张表的数据页总和。SQL Server处理过程中将使用到临时表。

    只有一张表有聚集索引的情形类似,SQL Server处理过程中将使用到临时表。并且读入所有的数据页到内存。

    假如两张表都有聚集索引,尽管两表的数据都会被读入内存页,但逻辑读数已经大大减少,等于其中一张表总数据内存页数加上最终输出的数据页数。而且SQL Server处理过程中将不需要再使用临时表。

    5.2、执行高选择性选取

    对于这样的高选择性SQL语句,SQL Server 将提示无法生成执行计划。

    Select * From P_Order A 
    Inner merge JOIN P_User B ON A.MobileNo=B.MobileNo
    Where A.MobileNo=28913

    但可以执行:

    Select * From P_Order A 
    Inner merge JOIN P_User B ON A.MobileNo=B.MobileNo
    Where A.MobileNo<=28001 (注:最终结果只有2条)

    这样的属于低选择性语句,但最终结果也很少的语句。如前面所述,这种情况下,采用netsted loop联接效率可能更高。

    测试6:执行Hash Join

    6.1、执行全表选取或者低选择性选取

    对于两表联接,假如两张表都没有索引,不写明联接提示的情况下,SQL Server默认使用hash join。而对于两表联接,假如两张表都有聚集索引,则SQL Server默认使用Merge Join。

    执行SQL:

    Select * From P_Order A 
    Inner hash JOIN P_User B ON A.MobileNo=B.MobileNo

    在使用hash join情况下,无论两张表有无索引,都将读取所有的数据页到内存,SQL Server将使用临时表进行处理。逻辑读数近似等于两张表的数据页总和。

    6.2、执行高选择性选取

    和merge join执行高选择性选取情况类似,也无法直接执行:

    Select * From P_Order A 
    Inner merge JOIN P_User B ON A.MobileNo=B.MobileNo
    Where A.MobileNo=28913

    但可以执行这样的结果很少的低选择性脚本:

    Select * From P_Order A 
    Inner merge JOIN P_User B ON A.MobileNo=B.MobileNo
    Where A.MobileNo<=28001 (注:最终结果只有2条)

    但此情况下,采用netsted loop联接效率更高。

    测试总结

    本次测试的主要意义在于,通过分析具体的内存变化结合执行计划、IO读取等信息,可以更清楚地了解SQL Server执行SQL 语句过程。

    另外,也验证了一些通过分析SQL 语句的IO读取、执行计划曾经得到的经验:

    (1) 在执行单表查询时,假如是高选择查询,要建立非聚集索引或者聚集索引(推荐非聚集索引,是独立于数据存放的)。假如是低选择性查询,则需要建立聚集索引。

    (2) 在执行联接查询时,假如最终输出结果很少,则适宜使用nested loop join;假如输出结果较多,则通过建立聚集索引,而以merge join方式查询能得到好的性能。对于性能较低的hash join,最好通过转换成merge join或者nested loop join方式提高查询性能。

    上一篇返回首页 下一篇

    声明: 此文观点不代表本站立场;转载务必保留本文链接;版权疑问请联系我们。

    别人在看

    正版 Windows 11产品密钥怎么查找/查看?

    还有3个月,微软将停止 Windows 10 的更新

    Windows 10 终止支持后,企业为何要立即升级?

    Windows 10 将于 2025年10 月终止技术支持,建议迁移到 Windows 11

    Windows 12 发布推迟,微软正全力筹备Windows 11 25H2更新

    Linux 退出 mail的命令是什么

    Linux 提醒 No space left on device,但我的空间看起来还有不少空余呢

    hiberfil.sys文件可以删除吗?了解该文件并手把手教你删除C盘的hiberfil.sys文件

    Window 10和 Windows 11哪个好?答案是:看你自己的需求

    盗版软件成公司里的“隐形炸弹”?老板们的“法务噩梦” 有救了!

    IT头条

    公安部:我国在售汽车搭载的“智驾”系统都不具备“自动驾驶”功能

    02:03

    液冷服务器概念股走强,博汇、润泽等液冷概念股票大涨

    01:17

    亚太地区的 AI 驱动型医疗保健:2025 年及以后的下一步是什么?

    16:30

    智能手机市场风云:iPhone领跑销量榜,华为缺席引争议

    15:43

    大数据算法和“老师傅”经验叠加 智慧化收储粮食尽显“科技范”

    15:17

    技术热点

    商业智能成CIO优先关注点 技术落地方显成效(1)

    用linux安装MySQL时产生问题破解

    JAVA中关于Map的九大问题

    windows 7旗舰版无法使用远程登录如何开启telnet服务

    Android View 事件分发机制详解

    MySQL用户变量的用法

      友情链接:
    • IT采购网
    • 科技号
    • 中国存储网
    • 存储网
    • 半导体联盟
    • 医疗软件网
    • 软件中国
    • ITbrand
    • 采购中国
    • CIO智库
    • 考研题库
    • 法务网
    • AI工具网
    • 电子芯片网
    • 安全库
    • 隐私保护
    • 版权申明
    • 联系我们
    IT技术网 版权所有 © 2020-2025,京ICP备14047533号-20,Power by OK设计网

    在上方输入关键词后,回车键 开始搜索。Esc键 取消该搜索窗口。