SQLSERVER 阻塞之 PFS 页到底是什么?
一:背景
1. 讲故事
在 SQLSERVER 的众多阻塞场景中,有不小的一部分是由于 PFS 页上的 闩锁 等待造成的,毕竟写页操作一定是要串行化的,在面对 闩锁(PAGELATCH_X) 等待问题上,一定要搞明白 PFS 页到底是什么? 这篇就来好好聊一聊。
二:PFS 详解
1. 什么是 PFS 页
我们知道数据库是由海量的 数据页 组成,表记录会写入到 数据页 上,那海量的数据页如何管理呢? SQLSERVER 想到了一个办法,从海量的数据页中按一定规则择取一些作为 管理页 使用,比如:
- GAM 跟踪区分配情况
- SGAM 跟踪共享区分配情况
- PFS 跟踪数据页的空间使用情况
这里我简述一下吧,GAM数据页中一个 bit 跟踪一个 64k 的空间(一个区),所以一个 8k 的GAM页 可以跟踪大约 (8 * 8192) * 64k = 4G 的空间,而 PFS 数据页用一个 byte 跟踪一个 8k 的数据页,理论上可以管理 8192 * 8k = 64M 的数据。
接下来的问题是这 1byte 是如何标记一个数据页的使用情况呢? 简单的画个图吧。

从图中看,这一个跟踪 byte 差不多都给塞满了,有了这些基础之后,接下来用一个案例来演示一下。
2. 案例演示
创建一个 MyTestDB 数据库,新建一个 post 表,参考sql如下:
CREATE DATABASE MyTestDB
GO
USE MyTestDB
GO
CREATE TABLE post (id INT IDENTITY, content CHAR(1000))
创建好之后来观察下 MyTestDB 的 mdf 文件中的 PFS 数据页,可以使用 DBCC PAGE 命令,但这个命令需要获取 fileid, pageid 这两个参数,那如何提取呢?
- 如何提取 fileid
可以查询 sys.database_files 系统表提取。
SELECT file_id,name,physical_name FROM sys.database_files;

- 如何提取 pageid
刚才说过了,这种管理页都是有规律的,比如 PFS 页是 64M 一个,在 file_id=1 的文件中,第一个区的第1号数据页就是 PFS 页,即 MyTestDB.mdf 文件偏移 8192byte 的位置,这里稍微补充一下,mdf 文件的第一个区中的 8 个 page 都是有特殊用途的,画个简图如下:

接下来观察下这个 PFS 数据页,它的 type=11,后续我们还会对它不断的观察。
DBCC TRACEON(3604)
DBCC PAGE(MyTestDB,1,1,3)
------ output -----
Page @0x000001DB52882000
m_pageId = (1:1) m_headerVersion = 1 m_type = 11
m_typeFlagBits = 0x0 m_level = 0 m_flagBits = 0x0
m_objId (AllocUnitId.idObj) = 99 m_indexId (AllocUnitId.idInd) = 0 Metadata: AllocUnitId = 6488064
Metadata: PartitionId = 0 Metadata: IndexId = 0 Metadata: ObjectId = 99
m_prevPage = (0:0) m_nextPage = (0:0) pminlen = 0
m_slotCnt = 1 m_freeCnt = 2 m_freeData = 8188
m_reservedCnt = 0 m_lsn = (37:192:1) m_xactReserved = 0
m_xdesId = (0:0) m_ghostRecCnt = 0 m_tornBits = 651443756
DB Frag ID = 1
好了,继续测试吧,插入一条数据,观察 PFS 页对应的位数是否有变化? 比如空间使用率,参考sql如下:
INSERT INTO post(content) VALUES ('aaaaa')
DBCC TRACEON(3604)
DBCC IND(MyTestDB,post,-1)

从图中可以看到,插入的 aaaaa 记录写到了 240 号数据页,跟踪这个数据页的byte 理论上是在 PFS 页偏移 0n240 byte 的位置,那具体是哪一个位置的 byte 跟踪的呢?我们用 windbg 观察下 PFS 数据页的内存页地址即可,首先用 DBCC PAGE(MyTestDB,1,1,2) 找到 Page 页在内存的首地址。
Memory Dump @0x000000C8177F8000
000000C8177F8000: 010b0000 00000000 00000000 00000000 00000000 ....................
000000C8177F8014: 00000100 63000000 0200fc1f 01000000 01000000 ....c...............
000000C8177F8028: 25000000 e0000000 1c000000 00000000 00000000 %...................
000000C8177F803C: 2c3ed426 01000000 00000000 00000000 00000000 ,>.&................
000000C8177F8050: 00000000 00000000 00000000 00000000 00009c1f ....................
000000C8177F8064: 44444444 00004444 60647060 74706070 60607060 DDDD..DD`dp`tp`p``p`
000000C8177F8078: 60707060 40404040 40404040 61706070 60606070 `pp`@@@@@@@@ap`p```p
000000C8177F808C: 60646060 60706060 60706060 60606070 40404040 `d```p```p`````p@@@@
接下来用 WinDbg 附加 SqlServer,由于前 96byte 是数据页头,所以理论位置应该是 000000C8177F8000+0x60 + 0n240 的位置,截图如下:

哈哈,终于给找到了,那 0x41 是什么意思呢?可以用 windbg 的 .formats 命令观察一下。
0:118> .formats 41
Evaluate expression:
Hex: 00000000`00000041
Decimal: 65
Decimal (unsigned) : 65
Octal: 0000000000000000000101
Binary: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 01000001
对应 byte 中位解读,可以知道当前 240 号数据页的空间使用率为 1 ~50%,同时 数据页已被分配。
这里我们顺带观察下 PageID=80 号这个 IAM 页,看看 PFS 中如何表示它的,在内存中计算位置应该是 000000C8177F8000+0x60 + 0n80,输出如下:
0:118> dp 000000C8177F8000+0x60 + 0n80 L2
000000c8`177f80b0 70607070`60606070 60302070`60606070
0:118> .formats 70
Evaluate expression:
Hex: 00000000`00000070
Decimal: 112
Decimal (unsigned) : 112
Octal: 0000000000000000000160
Binary: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 01110000
再对应 byte 位解读,可以知道当前的 80号数据页 的四点信息:
- 当前数据页已分配
- 当前数据页在混合区
- 当前数据页为 IAM 页
- 当前为特殊管理页
接下来再插入 4条 数据,观察下 空间使用率 是否有变化 ?
INSERT INTO post(content) VALUES ('bbbbb')
INSERT INTO post(content) VALUES ('ccccc')
INSERT INTO post(content) VALUES ('ddddd')
INSERT INTO post(content) VALUES ('eeeee')
用 DBCC PAGE 观察内存页首地址之后,再次用 WinDBG 附加观察,输出如下:

0:126> .formats 42
Evaluate expression:
Hex: 00000000`00000042
Decimal: 66
Decimal (unsigned) : 66
Octal: 0000000000000000000102
Binary: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 01000010
从输出可以看到已经由原来的 41 变成了 42,其中的 010 刚好表示当前数据页为 51~80% 满,作为校验的话,可以直接观察 240号 页头上的 PFS (1:1) = 0x42 ALLOCATED 80_PCT_FULL 描述。
DBCC TRACEON(3604)
DBCC IND(MyTestDB,post,-1)
DBCC PAGE(MyTestDB,1,240,2)
----- output -----
PAGE: (1:240)
...
PAGE HEADER:
Page @0x000001DB58F92000
...
Allocation Status
GAM (1:2) = ALLOCATED SGAM (1:3) = NOT ALLOCATED PFS (1:1) = 0x42 ALLOCATED 80_PCT_FULL
DIFF (1:6) = CHANGED ML (1:7) = NOT MIN_LOGGED
...
三:总结
总的来说 PFS页 瓶颈主要来自于用户创建的 临时表 和 表变量,常常在高并发的场景下由于高频的创建和删除临时表以及对临时表的插入,SQLSERVER 不得不高频的修改 PFS页 造成这一块的瓶颈,如果大家在阻塞中看到大量的PageLatch_x 等待资源,记得一定要将 Tempdb 中的 ndf 划分为多个来分摊写入压力。
SQLSERVER 阻塞之 PFS 页到底是什么?的更多相关文章
- SQLServer基础之数据页类型:GAM,SGAM,PFS
简介 我们已经知道SQL Server IO最小的单位是页,连续的8个页是一个区.SQL Server需要一种方式来知道其所管辖的数据库中的空间使用情况,这就是GAM页和SGAM页. GAM页 GAM ...
- 深入理解Sqlserver文件存储之页和应用 (转)
我们每天都在使用数据库,我们部门使用最多的关系数据库有Sqlserver,Oracle,有没有想过这些数据库是怎么存放到操作系统的文件中的?有时候为了能够设计出最优的表结构,写出高性能的Sqlserv ...
- SQL Server 存储(4/8):理解Page Free Space (PFS) 页
我们已经讨论了GAM与SGAM页,数据页(Data Page) ,现在我们来看下页面自由空间页(Page Free Space (PFS) ). PFS在数据文件里是第2页(页号1,页号从0开始),接 ...
- SQL Server :理解Page Free Space (PFS) 页
原文:SQL Server :理解Page Free Space (PFS) 页 我们已经讨论了GAM与SGAM页,数据页(Data Page) ,现在我们来看下页面自由空间页(Page Free S ...
- sqlserver 存储过程中使用临时表到底会不会导致重编译
曾经在网络上看到过一种说法,SqlServer的存储过程中使用临时表,会导致重编译,以至于执行计划无法重用, 运行时候会导致重编译的这么一个说法,自己私底下去做测试的时候,根据profile的跟踪结果 ...
- SQLServer中的页如何影响数据库性能 (转)
无论是哪一个数据库,如果要对数据库的性能进行优化,那么必须要了解数据库内部的存储结构.否则的话,很多数据库的优化工作无法展开.对于对于数据库管理员来说,虽然学习数据库的内存存储结构比较单调,但是却是我 ...
- SQLSERVER数据库死锁与优化杂谈
死锁杂谈 当数据库死锁时,SqlServer会释放一个优先级较低的锁,让另一个事务运行:所以,即时去捕捉数据库死锁,是挺不容易的. 如果,数据库死锁比较长时间,那么死锁是可以被捕捉的. 可以用SqlS ...
- SQLSERVER聚集索引与非聚集索引的再次研究(上)
SQLSERVER聚集索引与非聚集索引的再次研究(上) 上篇主要说聚集索引 下篇的地址:SQLSERVER聚集索引与非聚集索引的再次研究(下) 由于本人还是SQLSERVER菜鸟一枚,加上一些实验的逻 ...
- 【转】怎样查出SQLServer的性能瓶颈
怎样查出SQLServer的性能瓶颈 --王成辉翻译整理,转贴请注明出自微软BI开拓者[url]www.windbi.com[/url]--原帖地址 如果你曾经做了很长时间的DBA,那么你会了解到SQ ...
- (转)解释一下SQLSERVER事务日志记录
本文转载自桦仔的博客http://www.cnblogs.com/lyhabc/archive/2013/07/16/3194220.html 解释一下SQLSERVER事务日志记录 大家知道在完整恢 ...
随机推荐
- Uniapp And Taro一些小测评
前情 最近公司准备新开发一个小程序项目,对于使用哪一款小程序框架有一些犹豫,我有过2年左右的uniapp项目开发经验,Taro在刚刚出来的时候有尝试过,经常莫名报错需要重启,在内心是有些偏向uniap ...
- Git 分支管理策略汇总
原文链接: Git 分支管理策略 最近,团队新入职了一些小伙伴,在开发过程中,他们问我 Git 分支是如何管理的,以及应该怎么提交代码? 我大概说了一些规则,但仔细想来,好像也并没有形成一个清晰规范的 ...
- Atlassian Confluence 远程代码执行漏洞(CVE-2022-26134)漏洞复现
目录 免责声明: Atlassian Confluence 远程代码执行漏洞(CVE-2022-26134)漏洞复现 漏洞概述: 影响版本: 漏洞复现: 利用POC: 利用过程: 修复建议: 参考 免 ...
- Go语言核心36讲04
我们已经知道,环境变量GOPATH指向的是一个或多个工作区,每个工作区中都会有以代码包为基本组织形式的源码文件. 这里的源码文件又分为三种,即:命令源码文件.库源码文件和测试源码文件,它们都有着不同的 ...
- KVC原理与数据筛选
作者:宋宏帅 1 前言 在技术论坛中看到一则很有意思的KVC案例: @interface Person : NSObject @property (nonatomic, copy) NSString ...
- python-名称空间与作用域
名称空间与作用域 (1)名称空间(namespaces) 用来存放名字的地方是对栈区的划分 有了名称空间,就可在栈区中放相同的名字 1.1.内置名称空间 存放的名字:是Python解释器内置的名字 & ...
- 【Spark】Day03-Spark SQL:DataFrame、DataSet、sql编程与转换、项目实战(区域热门商品)
一.概述 1.介绍 将Spark SQL转换成RDD,然后提交到集群执行[对比hive] 提供2个编程抽象:DataFrame&DataSet 可以使用SQL和DatasetAPI与Spark ...
- Java中的反射机制及反射的优缺点
1. 反射的概念 反射 机制指的是,程序在运行时能够获取自身的信息.在 java 中只要给定类的名字,就能够获取类的所有属性和方法. 反射是 Java 中很多高级特性的基础,比如 注解.动态代理 以及 ...
- 保存sklearn中模型的两种方法(pickle、joblib)
保存sklearn中模型的两种方法(pickle.joblib) from sklearn import svm from sklearn import datasets clf = svm.SVC( ...
- 简单易用的监控告警系统 | HertzBeat 在 Rainbond 上的使用分享
在现有的监控告警体系中 Prometheus + AlertManger + Grafana 一直是主流,但对于中小团队或个人来说,这种体系显的较为复杂.而 HertzBeat 能让中小团队或个人很快 ...