一:背景

1.讲故事

最近给一位朋友做 SQL 慢语句 优化,花了些时间调优,遗憾的是 SQLSERVER 非源码公开,玩起来不是那么顺利,不过从这次经历中我觉得明年的一个重大任务就是好好研究一下它,争取在 SQLSERVER 性能优化上做一些成绩,哈哈! 个人觉得要想深入研究 SQLSERVER,得从它的存储引擎说起,说到存储引擎又得从核心的 数据页 说起,毕竟 mdf 就是由 数据页 拼出来的,当然理解的不对大家可以指出来。

二:理解数据页

1. 什么是数据页

一般来说,对大块资源或者数据进行高效管理都会按照一定粒度来划分的,比如说 Windows 对内存的管理就是按照 内存页 (4k) 来进行划分,言外之意就是 SQLSERVER 对 mdf 的管理也是按照 数据页 (8k) 来划分的,画个图大概就是这样的。

那如何来验证这个结论呢?刚才也说了数据都在数据页上,我们弄点数据然后在指定的数据页上提取出来就好了,这里用的是 SQLServer 2019


CREATE DATABASE MyTestDB
GO
USE MyTestDB;
GO
IF OBJECT_ID('person') IS NOT NULL
DROP TABLE person;
CREATE TABLE person
(
id INT IDENTITY,
name CHAR(5)
);
GO
INSERT INTO dbo.person( name ) VALUES ('john');
INSERT INTO dbo.person( name ) VALUES ('mary');

2. 寻找数据所在的数据页

刚才图中也说了 mdf 是由无数个 数据页 拼出来的,那如何找到 person 表所在的数据页呢?其实微软提供了一个 dbcc ind 命令可以帮我们洞察出来,同时记得开始 3604 标记,让输出显示在控制台上,而不是默认的错误日志中,这个命令具体怎么用,大家可以查看官方文档。


DBCC TRACEON(3604)
DBCC IND(MyTestDB,person, -1)

从输出看有两条记录,第一个是 PagePID=41 是 IAM 数据页,而PagePID=280 就是我们 person 表记录所在的数据页编号,也就是说 person 表的记录在 mdf 文件偏移为 0n280 * 0n8192 的位置,用 WinDbg 算一下就是 0x00230000


0:090> ? 0n280 * 0n8192
Evaluate expression: 2293760 = 00000000`00230000

那是不是呢?可以用 WinHex 验证一下,为了不出现进程占用,先把 MyTestDB 下线了,最后记得再上线即可。


ALTER DATABASE MyTestDB SET OFFLINE ALTER DATABASE MyTestDB SET ONLINE

从 WinHex 上看,果然是在偏移为 0x00230000 这个数据页上。

3. 如何从内存中看到数据页

刚才我们看到的数据页只是物理硬盘上的,但数据页和数据页之间的逻辑关系肯定是在内存中用一定的数据结构来承载的,接下来看下这个 数据页 映射到 SQLSERVER 进程内存的哪里呢?微软提供了 DBCC PAGE 命令可以查看指定 数据页 的详细信息。


DBCC TRACEON(3604)
DBCC PAGE(MyTestDB,1,280,2)

输出结果如下:


DBCC 执行完毕。如果 DBCC 输出了错误信息,请与系统管理员联系。 PAGE: (1:280) BUFFER: BUF @0x000002B41104F480 bpage = 0x000002B3F0632000 bPmmpage = 0x0000000000000000 bsort_r_nextbP = 0x000002B41104F3D0
bsort_r_prevbP = 0x0000000000000000 bhash = 0x0000000000000000 bpageno = (1:280)
bpart = 1 ckptGen = 0x0000000000000000 bDirtyRefCount = 0
bstat = 0x9 breferences = 0 berrcode = 0
bUse1 = 12454 bstat2 = 0x0 blog = 0x15ab215a
bsampleCount = 0 bIoCount = 0 resPoolId = 0
bcputicks = 0 bReadMicroSec = 182 bDirtyContext = 0x0000000000000000
bDbPageBroker = 0x0000000000000000 bdbid = 10 bpru = 0x000002B3FA708040 PAGE HEADER: Page @0x000002B3F0632000 m_pageId = (1:280) m_headerVersion = 1 m_type = 1
m_typeFlagBits = 0x0 m_level = 0 m_flagBits = 0x8200
m_objId (AllocUnitId.idObj) = 179 m_indexId (AllocUnitId.idInd) = 256
Metadata: AllocUnitId = 72057594049658880
Metadata: PartitionId = 72057594043170816 Metadata: IndexId = 0
Metadata: ObjectId = 581577110 m_prevPage = (0:0) m_nextPage = (0:0)
pminlen = 13 m_slotCnt = 2 m_freeCnt = 8060
m_freeData = 128 m_reservedCnt = 0 m_lsn = (37:584:3)
m_xactReserved = 0 m_xdesId = (0:0) m_ghostRecCnt = 0
m_tornBits = -116446693 DB Frag ID = 1 Allocation Status GAM (1:2) = ALLOCATED SGAM (1:3) = NOT ALLOCATED PFS (1:1) = 0x41 ALLOCATED 50_PCT_FULL
DIFF (1:6) = CHANGED ML (1:7) = NOT MIN_LOGGED DATA: Memory Dump @0x000000F840DF8000 000000F840DF8000: 01010000 00820001 00000000 00000d00 00000000 ....................
000000F840DF8014: 00000200 b3000000 7c1f8000 18010000 01000000 ........|...........
000000F840DF8028: 25000000 48020000 03000000 00000000 00000000 %...H...............
000000F840DF803C: 1b2a0ff9 00000000 00000000 00000000 00000000 .*..................
000000F840DF8050: 00000000 00000000 00000000 00000000 10000d00 ....................
000000F840DF8064: 01000000 6a6f686e 20020000 10000d00 02000000 ....john ...........
000000F840DF8078: 6d617279 20020000 00002121 21212121 21212121 mary .....!!!!!!!!!!
000000F840DF808C: 21212121 21212121 21212121 21212121 21212121 !!!!!!!!!!!!!!!!!!!!
000000F840DF80A0: 21212121 21212121 21212121 21212121 21212121 !!!!!!!!!!!!!!!!!!!!
...
000000F840DF9FF4: 21212121 21212121 70006000 !!!!!!!!p.`. OFFSET TABLE: Row - Offset
1 (0x1) - 112 (0x70)
0 (0x0) - 96 (0x60) DBCC 执行完毕。如果 DBCC 输出了错误信息,请与系统管理员联系。 Completion time: 2022-12-30T17:48:20.5466040+08:00

从上面的 Memory Dump 区节中可以看到,数据在进程内存的 000000F840DF8064 ~ 000000F840DF8078 范围内,这里要吐槽的是内存地址按照 大端布局 的,看起来很不习惯,可以用 windbg 用 小端布局 来显示。


0:116> dp 000000F840DF8064
000000f8`40df8064 6e686f6a`00000001 000d0010`00000220
000000f8`40df8074 7972616d`00000002 21210000`00000220
000000f8`40df8084 21212121`21212121 21212121`21212121
000000f8`40df8094 21212121`21212121 21212121`21212121
000000f8`40df80a4 21212121`21212121 21212121`21212121
000000f8`40df80b4 21212121`21212121 21212121`21212121
000000f8`40df80c4 21212121`21212121 21212121`21212121
000000f8`40df80d4 21212121`21212121 21212121`21212121

4. sql 请求源码研究

喜欢玩 windbg 的朋友肯定想对 sqlserver 进行汇编级洞察,比如研究下 sql 请求在 sqlserver 里面的执行流是什么样的? 其实很简单,我们可以这样处理,使用 ba 对 john 的内存地址下一个硬件断点,即 ba r4 000000f840df8064+0x4 ,然后在 SSMS 上执行一条 SELECT * FROM person 语句,因为要提取 john 自然就会命中。


0:104> ba r4 000000f840df8064+0x4
0:104> g
Breakpoint 0 hit
sqlmin!BTreeMgr::GetHPageIdWithKey+0x4a:
00007ff8`d4ea121a 48894c2478 mov qword ptr [rsp+78h],rcx ss:000000f8`45278028=0000024800000025
0:102> k
# Child-SP RetAddr Call Site
00 000000f8`45277fb0 00007ff8`d4ea0b59 sqlmin!BTreeMgr::GetHPageIdWithKey+0x4a
01 000000f8`45278450 00007ff8`d4ea08b7 sqlmin!IndexPageManager::GetPageWithKey+0x119
02 000000f8`45278d20 00007ff8`d4eb22d1 sqlmin!GetRowForKeyValue+0x203
03 000000f8`45279880 00007ff8`d4eb2a39 sqlmin!IndexDataSetSession::GetRowByKeyValue+0x141
04 000000f8`45279a70 00007ff8`d4eb279b sqlmin!IndexDataSetSession::FetchRowByKeyValueInternal+0x230
05 000000f8`45279ed0 00007ff8`d4eb2883 sqlmin!RowsetNewSS::FetchRowByKeyValueInternal+0x437
06 000000f8`4527a000 00007ff8`d4eaadab sqlmin!RowsetNewSS::FetchRowByKeyValue+0x96
07 000000f8`4527a050 00007ff8`d4f93d60 sqlmin!CMEDScan::StartSearch+0x4f8
08 000000f8`4527a170 00007ff8`d4f93f3a sqlmin!CMEDCatYukonObject::GetTemporalCurrentTableId+0x10e
09 000000f8`4527a380 00007ff8`d801f0d1 sqlmin!CMEDProxyRelation::GetTemporalCurrentTableId+0x7a
0a 000000f8`4527a3c0 00007ff8`d801dfb8 sqllang!CAlgTableMetadata::FPartialBind+0xb58
0b 000000f8`4527a580 00007ff8`d80394b3 sqllang!CAlgTableMetadata::Bind+0x317
0c 000000f8`4527a620 00007ff8`d800415d sqllang!CRelOp_Get::BindTree+0x78f
0d 000000f8`4527a890 00007ff8`d80418a1 sqllang!COptExpr::BindTree+0x85
0e 000000f8`4527a8c0 00007ff8`d800415d sqllang!CRelOp_FromList::BindTree+0x31
0f 000000f8`4527a920 00007ff8`d802c2a3 sqllang!COptExpr::BindTree+0x85
10 000000f8`4527a950 00007ff8`d800415d sqllang!CRelOp_QuerySpec::BindTree+0x2e8
11 000000f8`4527aa60 00007ff8`d80042dd sqllang!COptExpr::BindTree+0x85
12 000000f8`4527aa90 00007ff8`d800415d sqllang!CRelOp_SelectQuery::BindTree+0x489
13 000000f8`4527ab80 00007ff8`d8003f23 sqllang!COptExpr::BindTree+0x85
14 000000f8`4527abb0 00007ff8`d8004e47 sqllang!CRelOp_Query::FAlgebrizeQuery+0x4bd
15 000000f8`4527ae30 00007ff8`d7ff5576 sqllang!CProchdr::FNormQuery+0x8f
16 000000f8`4527ae70 00007ff8`d7ff4a79 sqllang!CProchdr::FNormalizeStep+0x5bd
17 000000f8`4527b4b0 00007ff8`d7ff5124 sqllang!CSQLSource::FCompile+0xea5
18 000000f8`4527e1b0 00007ff8`d7e659c3 sqllang!CSQLSource::FCompWrapper+0xcb
19 000000f8`4527e280 00007ff8`d7e6387a sqllang!CSQLSource::Transform+0x721
1a 000000f8`4527e3e0 00007ff8`d7e6e67b sqllang!CSQLSource::Execute+0x4fa
1b 000000f8`4527e8c0 00007ff8`d7e6d815 sqllang!process_request+0xca6
1c 000000f8`4527efc0 00007ff8`d7e6d5ef sqllang!process_commands_internal+0x4b7
1d 000000f8`4527f0f0 00007ff8`d4096523 sqllang!process_messages+0x1d6
1e 000000f8`4527f2d0 00007ff8`d4096e6d sqldk!SOS_Task::Param::Execute+0x232
1f 000000f8`4527f8d0 00007ff8`d4096c75 sqldk!SOS_Scheduler::RunTask+0xa5
20 000000f8`4527f940 00007ff8`d40bb160 sqldk!SOS_Scheduler::ProcessTasks+0x39d
21 000000f8`4527fa60 00007ff8`d40baa5b sqldk!SchedulerManager::WorkerEntryPoint+0x2a1
22 000000f8`4527fb30 00007ff8`d40bafa4 sqldk!SystemThreadDispatcher::ProcessWorker+0x3ed
23 000000f8`4527fe30 00007ff8`f6d86fd4 sqldk!SchedulerManager::ThreadEntryPoint+0x3b5
24 000000f8`4527ff20 00007ff8`f865cec1 KERNEL32!BaseThreadInitThunk+0x14
25 000000f8`4527ff50 00000000`00000000 ntdll!RtlUserThreadStart+0x21

从线程栈上看,有 SQLSERVER 核心的 Scheduler ,Task 以及 命令分析器,查询优化器,查询执行器 等各种核心元素,后续再慢慢研究吧。

三: 总结

深入的理解数据,索引在数据页上的布局,可以从根本上帮助我们理解如何减少请求在数据页上的流转,减少逻辑读,从而提升 sql 的查询性能。

一次SQL调优 聊一聊 SQLSERVER 数据页的更多相关文章

  1. SQL调优

    # 问题的提出 在应用系统开发初期,由于开发数据库数据比较少,对于查询SQL语句,复杂视图的的编写等体会不出SQL语句各种写法的性能优劣,但是如果将应用 系统提交实际应用后,随着数据库中数据的增加,系 ...

  2. SQL调优常用方法

    在使用DBMS时经常对系统的性能有非常高的要求:不能占用过多的系统内存和 CPU资源.要尽可能快的完成的数据库操作.要有尽可能高的系统吞吐量.如果系统开发出来不能满足要求的所有性能指标,则必须对系统进 ...

  3. 读《程序员的SQL金典》[4]--SQL调优

    一.SQL注入 如果程序中采用sql拼接的方式书写代码,那么很可能存在SQL注入漏洞.避免的方式有两种: 1. 对于用户输入过滤敏感字母: 2. 参数化SQL(推荐). 二.索引 ①索引分类 聚簇索引 ...

  4. Oracle SQL 调优健康检查脚本

    Oracle SQL 调优健康检查脚本 我们关注数据库系统的性能,进行数据库调优的主要工作就是进行SQL的优化.良好的数据架构设计.配合应用系统中间件和写一手漂亮的SQL,是未来系统上线后不出现致命性 ...

  5. Spark性能调优之解决数据倾斜

    Spark性能调优之解决数据倾斜 数据倾斜七种解决方案 shuffle的过程最容易引起数据倾斜 1.使用Hive ETL预处理数据    • 方案适用场景:如果导致数据倾斜的是Hive表.如果该Hiv ...

  6. Oracle中SQL调优(SQL TUNING)之最权威获取SQL执行计划大全

    该文档为根据相关资料整理.总结而成,主要讲解Oracle数据库中,获取SQL语句执行计划的最权威.最正确的方法.步骤,此外,还详细说明了每种方法中可选项的意义及使用方法,以方便大家和自己日常工作中查阅 ...

  7. Oracle SQL调优记录

    目录 一.前言 二.注意点 三.Oracle执行计划 四.调优记录 @ 一.前言 本博客只记录工作中的一次oracle sql调优记录,因为数据量过多导致的查询缓慢,一方面是因为业务太过繁杂,关联了太 ...

  8. 《高性能SQL调优精要与案例解析》一书谈主流关系库SQL调优(SQL TUNING或SQL优化)核心机制之——索引(index)

    继<高性能SQL调优精要与案例解析>一书谈SQL调优(SQL TUNING或SQL优化),我们今天就谈谈各主流关系库中,占据SQL调优技术和工作半壁江山的.最重要的核心机制之一——索引(i ...

  9. 《高性能SQL调优精要与案例解析》一书谈SQL调优(SQL TUNING或SQL优化)学习

    <高性能SQL调优精要与案例解析>一书上市发售以来,很多热心读者就该书内容及一些具体问题提出了疑问,因读者众多外加本人日常工作的繁忙 ,在这里就SQL调优学习进行讨论并对热点问题统一作答. ...

  10. 初次使用SQL调优建议工具--SQL Tuning Advisor

    在10g中,Oracle推出了自己的SQL优化辅助工具: SQL优化器(SQL Tuning Advisor :STA),它是新的DBMS_SQLTUNE包. 使用STA一定要保证优化器是CBO模式下 ...

随机推荐

  1. 如何用AR Engine环境Mesh能力实现虚实遮挡

    在AR应用中,用户最不希望看到不真实的穿模现象发生,如虚拟形象部分身体陷入墙壁之中,或者未碰到墙壁却已无法移动,这种不真实的交互十分影响用户体验.那如何才能让避免虚拟物体的穿模问题呢?使用AR Eng ...

  2. 2.签名&初始化&提交

      Git设置签名 签名的作用是区分不同操作者的身份,用户的签名信息在每一个版本的提交信息中能够看到, 以此确认本次提交是谁做的,git首次安装必须设置用户签名,否则无法提交代码 这里设置的用户签名和 ...

  3. 洛谷P5759题解

    本文摘自本人洛谷博客,原文章地址:https://www.luogu.com.cn/blog/cjtb666anran/solution-p5759 \[这道题重在理解题意 \] 选手编号依次为: \ ...

  4. PCA降维的原理及实现

    PCA可以将数据从原来的向量空间映射到新的空间中.由于每次选择的都是方差最大的方向,所以往往经过前几个维度的划分后,之后的数据排列都非常紧密了, 我们可以舍弃这些维度从而实现降维 原理 内积 两个向量 ...

  5. 2022NISACTF--WEB

    easyssrf 打开题目,显示的是 尝试输入, 发现输入flag有东西 读取文件 访问下一个网站 读取文件 不能以file开头 直接伪协议,base64解码 checkIn 奇怪的unicode编码 ...

  6. 【Java复健指南09】项目练习全解--房屋出租系统

    一个基于文本界面的综合练习,主要用于串联和回忆知识点,比较简单 各个界面的设计样式 主菜单 =============房屋出租系统菜单============ 1 新 增 房 源 2 查 找 房 屋 ...

  7. Codeforces Round #811 (Div. 3)D. Color with Occurrences

    题目大意:给出一个文章t和n个字符串s1,s2...sn: 问能否用这n个字符串将整个文章覆盖: 思路:贪心(最小区间覆盖) 记录每个字符串能够覆盖的所有位置(起点,终点,编号) 排序后贪心的求出是否 ...

  8. MindStudio模型训练场景精度比对全流程和结果分析

    摘要:MindStudio是一套基于华为昇腾AI处理器开发的AI全栈开发平台 本文分享自华为云社区<MindStudio模型训练场景精度比对全流程和结果分析>,作者:yd_24730208 ...

  9. WSL下的Ubuntu 18.04LTS配置软件源和系统更新

    WSL下的Ubuntu 18.04LTS配置软件源和系统更新 设置系统语言为中文 # 安装中文支持 sudo apt-get install -y language-pack-zh-hans # 设置 ...

  10. Android开发之应用更新或软件下载

    Android开发之应用更新或软件下载 本文章学习前提:okHttp3或以上,EventBus或其它事件总线工具,四大组件的Activity和Service,安卓通知基础知识 新建项目文件 目录结构如 ...