SQLSERVER 阻塞之 PFS 页到底是什么?

sqlserver,阻塞,pfs,到底,什么 · 浏览次数 : 1278

小编点评

**内容简介** 该内容描述了如何在 SQL Server 中观察和解决 Page latch 问题。页面 latch 问题主要来自于用户创建的临时表和表变量,经常在高并发的场景下由于高频的创建和删除临时表以及对临时表的插入,SQL Server 不得不高频的修改 PFS 页 造成这一块的瓶颈。 **解决方案** * 可以通过使用 DBCC PAGE 观察内存页首地址之后,再次用 WinDBG 附加观察,观察 240号 页头的 PFS (1:1) = 0x42 ALLOCATED 80_PCT_FULL 描述。 * 可以将 Tempdb 中的 ndf 划分为多个来分摊写入压力。 * 可以设置 Page latch 的 Timeout 属性,以指定页面 latch 的等待时间。 * 可以使用 DBCC 的 TRACON 和 IND 方法观察页面 latch 的详细过程。 * 可以使用 DBCC 的 PAGE 方法观察内存页首地址之后,再次用 WinDBG 附加观察,观察 240号 页头的 PFS (1:1) = 0x42 ALLOCATED 80_PCT_FULL 描述。 **结论** 通过使用上述方法,可以解决 SQL Server 中 Page latch 问题,提升其性能。

正文

一:背景

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 阻塞之 PFS 页到底是什么?

一:背景 1. 讲故事 在 SQLSERVER 的众多阻塞场景中,有不小的一部分是由于 PFS 页上的 闩锁 等待造成的,毕竟写页操作一定是要串行化的,在面对 闩锁(PAGELATCH_X) 等待问题上,一定要搞明白 PFS 页到底是什么? 这篇就来好好聊一聊。 二:PFS 详解 1. 什么是 PF

SQLServer如何监控阻塞会话

一、查询阻塞和被阻塞的会话 SELECT r.session_id AS [Blocked Session ID], r.blocking_session_id AS [Blocking Session ID], r.wait_type, r.wait_time, r.wait_resource,

SQLSERVER 语句交错引发的死锁研究

一:背景 1. 讲故事 相信大家在使用 SQLSERVER 的过程中经常会遇到 阻塞 和 死锁,尤其是 死锁,比如下面的输出: (1 row affected) Msg 1205, Level 13, State 51, Line 5 Transaction (Process ID 62) was

SQLSERVER 的 nolock 到底是怎样的无锁?

一:背景 1. 讲故事 相信绝大部分用 SQLSERVER 作为底层存储的程序员都知道 nolock 关键词,即使当时不知道也会在踩过若干阻塞坑之后果断的加上 nolock,但这玩意有什么注意事项呢?这就需要了解它的底层原理了。 二:nolock 的原理 1. sql 阻塞还原 为了方便讲述,先创建

SQLServer统计监控SQL执行计划突变的方法

使用动态管理视图(DMVs)来检测SQL执行计划的突变,你需要关注那些能够提供查询执行统计和计划信息的视图。以下是一些可以用于此目的的DMVs以及相应的查询示例: sys.dm_exec_query_stats:这个视图提供了关于SQL Server中查询执行的统计信息,包括CPU时间、总工作时间、

[转帖]SqlServer 突破CPU 20核限制

SqlServer安装时企业版会有两种选项:Microsoft SQL Server Enterprise (64-bit),Microsoft SQL Server Enterprise: Core-based Licensing (64-bit)。前者为Enterprise Server+CAL

[转帖]sqlserver 软件授权

https://cdn.modb.pro/db/516085 授权模式 SQL Server 产品有两种基本的授权(License)模式。 ● “每处理器”或“每内核”模式 “每处理器”(Per Processor)授权模式只计算物理处理器的数量,与物理处理器的内核数量无关。这种授权模式一直沿用到S

[转帖]SQLSERVER DBCC命令大全

https://cdn.modb.pro/db/460025 DBCC DROPCLEANBUFFERS:从缓冲池中删除所有缓存,清除缓冲区 在进行测试时,使用这个命令可以从SQLSERVER的数据缓存data cache(buffer)清除所有的测试数据,以保证测试的公正性。 需要注意的是这个命令

SQLServer 隔离级别的简单学习

SQLServer 隔离级别的简单学习 背景 上周北京一个项目出现了卡顿的现象。 周末开发测试加紧制作测试发布了补丁,但是并没有好转。 上周四时跟研发訾总简单沟通过, 怀疑是隔离级别有关系。但是不敢确认。 因为现场是SQLServer数据库。前期出现过一些问题。 同部门的杨老师也一直问我要不要SQL

【转帖】sqlserver 在高并发的select,update,insert的时候出现死锁的解决办法

最近在使用过程中使用SqlServer的时候发现在高并发情况下,频繁更新和频繁查询引发死锁。通常我们知道如果两个事务同时对一个表进行插入或修改数据,会发生在请求对表的X锁时,已经被对方持有了。由于得不到锁,后面的Commit无法执行,这样双方开始死锁。但是select语句和update语句同时执行,