一:背景

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 页到底是什么?的更多相关文章

  1. SQLServer基础之数据页类型:GAM,SGAM,PFS

    简介 我们已经知道SQL Server IO最小的单位是页,连续的8个页是一个区.SQL Server需要一种方式来知道其所管辖的数据库中的空间使用情况,这就是GAM页和SGAM页. GAM页 GAM ...

  2. 深入理解Sqlserver文件存储之页和应用 (转)

    我们每天都在使用数据库,我们部门使用最多的关系数据库有Sqlserver,Oracle,有没有想过这些数据库是怎么存放到操作系统的文件中的?有时候为了能够设计出最优的表结构,写出高性能的Sqlserv ...

  3. SQL Server 存储(4/8):理解Page Free Space (PFS) 页

    我们已经讨论了GAM与SGAM页,数据页(Data Page) ,现在我们来看下页面自由空间页(Page Free Space (PFS) ). PFS在数据文件里是第2页(页号1,页号从0开始),接 ...

  4. SQL Server :理解Page Free Space (PFS) 页

    原文:SQL Server :理解Page Free Space (PFS) 页 我们已经讨论了GAM与SGAM页,数据页(Data Page) ,现在我们来看下页面自由空间页(Page Free S ...

  5. sqlserver 存储过程中使用临时表到底会不会导致重编译

    曾经在网络上看到过一种说法,SqlServer的存储过程中使用临时表,会导致重编译,以至于执行计划无法重用, 运行时候会导致重编译的这么一个说法,自己私底下去做测试的时候,根据profile的跟踪结果 ...

  6. SQLServer中的页如何影响数据库性能 (转)

    无论是哪一个数据库,如果要对数据库的性能进行优化,那么必须要了解数据库内部的存储结构.否则的话,很多数据库的优化工作无法展开.对于对于数据库管理员来说,虽然学习数据库的内存存储结构比较单调,但是却是我 ...

  7. SQLSERVER数据库死锁与优化杂谈

    死锁杂谈 当数据库死锁时,SqlServer会释放一个优先级较低的锁,让另一个事务运行:所以,即时去捕捉数据库死锁,是挺不容易的. 如果,数据库死锁比较长时间,那么死锁是可以被捕捉的. 可以用SqlS ...

  8. SQLSERVER聚集索引与非聚集索引的再次研究(上)

    SQLSERVER聚集索引与非聚集索引的再次研究(上) 上篇主要说聚集索引 下篇的地址:SQLSERVER聚集索引与非聚集索引的再次研究(下) 由于本人还是SQLSERVER菜鸟一枚,加上一些实验的逻 ...

  9. 【转】怎样查出SQLServer的性能瓶颈

    怎样查出SQLServer的性能瓶颈 --王成辉翻译整理,转贴请注明出自微软BI开拓者[url]www.windbi.com[/url]--原帖地址 如果你曾经做了很长时间的DBA,那么你会了解到SQ ...

  10. (转)解释一下SQLSERVER事务日志记录

    本文转载自桦仔的博客http://www.cnblogs.com/lyhabc/archive/2013/07/16/3194220.html 解释一下SQLSERVER事务日志记录 大家知道在完整恢 ...

随机推荐

  1. 虚拟机VMware运行Ubuntu时无法和主机之间复制粘贴的问题

    解决虚拟机VMware运行Ubuntu时无法和主机之间复制粘贴的问题 执行以下命令并重启即可解决 sudo apt-get autoremove open-vm-tools sudo apt-get ...

  2. python 的time、datetime模块

    python 时间模块 import datetime ​ res = datetime.datetime.now() print(res) # 2022-08-07 16:47:07.120459 ...

  3. nginx安装及相关操作

    工作中经常用到nginx,今天写个自动部署nginx的脚本.nginx版本选用:1.20.2 1.创建nginx安装脚本(nginx.sh) [root@iZ2ze7uphtapcv51egcm7rZ ...

  4. redis位图(bitmap)常用命令的解析

    描述   bitmap是redis封装的用于针对位(bit)的操作,其特点是计算效率高,占用空间少,常被用来统计用户签到.登录等场景 常用命令及解析 常用命令 setbit key offset va ...

  5. Aspose.Words利用Word模板导出Word文档

    今天工作中遇到了导出Word文档的问题,但是在搜索Aspose.Words 导出Word文档时发现网上的方法都是有头没尾的,有的只有一小段实例,让人看着摸不着头脑.借着https://www.cnbl ...

  6. SpringBoot yml配置文件中,logging.level报错

    报错 *************************** APPLICATION FAILED TO START *************************** Description: ...

  7. 8、将两个字符串s1,s2进行比较,如果s1>s2,则输出一个正数。如果s1 = s2,输出零。如果s1 < s2, 输出一个负数,不用strcmp函数,输出的正数或者负数的绝对值应该是比较两字符串相应字符的ascii码的差值。

    /* 将两个字符串s1,s2进行比较,如果s1>s2,则输出一个正数.如果s1 = s2,输出零.如果s1 < s2, 输出一个负数,不用strcmp函数,输出的正数或者负数的绝对值应该是 ...

  8. python(牛客)试题解析3 - 困难

    导航 一.找到已经最大承重的背包内如何放入最大价值的物品的最优解 二.查找一个字符串中包含另外一个字符串(可打乱顺序)的次数三.计算正整数数组从头走到最后一个成员所需的最小步骤四.计算字符串非严格递增 ...

  9. C++编程笔记(多线程学习)

    目录 一.线程创建 二.线程的相关操作 2.1 join 2.2 detach 2.3 joinable 三.线程参数 3.1传参所引发的资源回收问题 3.2 将对象的成员函数作为入口函数 四.线程的 ...

  10. 4.7:Hive操作实验

    〇.概述 1.拓扑结构 2.目标 通过Hive实验熟悉Hive的基本操作 一.操作流程 1.启动环境 2.启动hive 输入 cd /home/user/bigdata/apache-hive-2.3 ...