SQL如何决定添加新的数据页

SQL如何决定添加新的数据页,sql,sql-server,Sql,Sql Server,我试图理解当我们向表中插入一些新记录时,SQL是如何分配新的数据页的 我已通过以下方式在新数据库中创建了一个名为Employee的示例表- Create Database TestDb GO Use TestDb GO Create table Employee ( EmployeeName char(1000)) GO Insert into Employee values ('Employee1') DBCC IND('TestDb','dbo.Employee',-1) 运行DBC

我试图理解当我们向表中插入一些新记录时,SQL是如何分配新的数据页的

我已通过以下方式在新数据库中创建了一个名为Employee的示例表-

Create Database TestDb
GO
Use TestDb
GO
Create table Employee (
EmployeeName char(1000))   GO
Insert into Employee values ('Employee1')

DBCC IND('TestDb','dbo.Employee',-1)
运行DBCC后,我看到了2页。一个是IAM页面(PageType=10),另一个是保存实际数据的数据页面(PageType=1)

后来,我使用DBCC页面验证了数据页面的实际内容

DBCC TRACEON(3604)
DBCC PAGE('TestDb',1,298,3)
我看到了SQL是如何计算m_freecnt字节数的-

=1007字节记录大小+96字节头+2字节偏移量=1105字节

i、 e.8K页面=8192字节=8192-1105=7087字节可用。

现在我继续向这个表中添加记录,以便了解这个页面将容纳多少条记录,以及SQL将在何时分配一个新页面,同时牢记m_FreeCnt

(记录大小=1007,偏移字节=2)

新增第二条记录-

Insert into Employee values ('Employee2') GO
最后一次自由计数=7087,即7087-1007-2=6087=>m_FreeCnt=6078

Insert into Employee values ('Employee3') GO
最后一次自由计数=6078,即6078-1007-2=5069=>m_FreeCnt=5069

Insert into Employee values ('Employee4') GO
最后一次自由计数=5069,即5069-1007-2=4060=>m_FreeCnt=4060

Insert into Employee values ('Employee5') GO
上次自由计数=4060,即4060-1007-2=3051=>m_FreeCnt=3051

Insert into Employee values ('Employee6') GO
上次自由计数=3051,即3051-1007-2=2042=>m_FreeCnt=2042

Insert into Employee values ('Employee7') GO
最后一次自由计数=2042,即2042-1007-2=1033=>m_FreeCnt=1033

到目前为止一切正常。现在我们在这个数据页中还剩下1033个字节。当我添加第8条记录时,理想情况下它不应该创建另一个页面,因为可用字节数是1033字节,足以容纳第8条记录(1009字节足够)。但是,SQL确实创建了一个新的日期页来保存第8条记录

我插入了第8条记录并运行DBCC IND来检查-

Insert into Employee values ('Employee7') GO

DBCC IND('TestDb','dbo.Employee',-1)
现在它已经创建了一个PageNumber=300的新数据页

我不明白这部分。SQL是否保留了除[header(96)+数据部分+每页偏移量2字节]之外的一些字节

您可以尝试运行上述查询,并让我知道我是否遗漏了任何内容?或者我们是否不应该担心SQL的这些内存细节

谢谢。

这就是我发现的:

数据页(m_slotCnt)和 该页有3063字节可用于新记录(m_freeCnt)。如果 Microsoft SQL Server将在此页中插入新记录,它已 在偏移量5126(m_freeData)处插入。最后不能不提 页面使用率为51%-80%。如果Microsoft SQL Server将读取 来自页眉的信息应该知道3062字节 页面上的可用空间。但事实并非如此

记录3的长度为2504字节+行开销,但Microsoft SQL Server尚未在第150页上插入记录,因为 以下计算:

PFS报告了80%的空间使用率,这意味着从 完整的页面空间。页面的可用空间为1638字节,而 记录3的长度为2504字节,不适合该页 基于这些计算!此计算显示了PFS 扫描而不是专门扫描页眉。因为新的 记录不适合页面,将创建一个新记录作为 从事务日志中提取将使其可见

例如:

m_slotCnt   7
m_freeCnt   1033
m_freeData  7145
PFS (1:1)   0x63 MIXED_EXT ALLOCATED  95_PCT_FULL
8192*0.95=7782


8192-7782=409只需重新表述另一个答案就可以了:
当您的页面填充率达到81%时,该页面的PFS记录将前2位更改为
11
,这意味着“页面已满95%”,即使有足够的空间(超过5%的可用空间),SQL也不允许插入更多记录

有两种方法可以解决这个问题:
简单:创建聚集索引-然后您可以根据需要将fillfactor设置为100%。
困难:如果页面上的可用空间超过19%,则可以使用一条
insert
语句插入多条记录,以100%填充页面。
见示例:

CREATE TABLE TestTable(F0 SMALLINT);
GO
INSERT INTO TestTable (F0) 
SELECT TOP 699 1 FROM sys.messages;
GO
BEGIN TRAN 
  INSERT INTO TestTable (F0) 
  SELECT TOP 37 2 FROM sys.messages;
  SELECT 
    (SELECT COUNT(*) FROM sys.dm_db_database_page_allocations(DB_ID(), OBJECT_ID('TestTable'), NULL, NULL, 'DETAILED') WHERE Page_type = 1) AS Pages,
    (SELECT COUNT(*) FROM TestTable) AS "Count";
ROLLBACK
GO
BEGIN TRAN 
  INSERT INTO TestTable (F0) VALUES (3)
  GO 2
  SELECT 
    (SELECT COUNT(*) FROM sys.dm_db_database_page_allocations(DB_ID(), OBJECT_ID('TestTable'), NULL, NULL, 'DETAILED') WHERE Page_type = 1) AS Pages,
    (SELECT COUNT(*) FROM TestTable) AS "Count";
ROLLBACK
GO
DROP TABLE TestTable
GO

达到699条记录后,您可以一次插入37条新记录,或者如果按记录逐个插入,则只能插入一条记录。

Hi Slava,创建聚集索引非常有效。我修改了模式,添加了一个主键列,然后使用了整个内存字节。没有内存浪费。非常感谢:)小心点。仅当聚集索引列连续时才使用100%填充因子,以避免任何页面拆分。
CREATE TABLE TestTable(F0 SMALLINT);
GO
INSERT INTO TestTable (F0) 
SELECT TOP 699 1 FROM sys.messages;
GO
BEGIN TRAN 
  INSERT INTO TestTable (F0) 
  SELECT TOP 37 2 FROM sys.messages;
  SELECT 
    (SELECT COUNT(*) FROM sys.dm_db_database_page_allocations(DB_ID(), OBJECT_ID('TestTable'), NULL, NULL, 'DETAILED') WHERE Page_type = 1) AS Pages,
    (SELECT COUNT(*) FROM TestTable) AS "Count";
ROLLBACK
GO
BEGIN TRAN 
  INSERT INTO TestTable (F0) VALUES (3)
  GO 2
  SELECT 
    (SELECT COUNT(*) FROM sys.dm_db_database_page_allocations(DB_ID(), OBJECT_ID('TestTable'), NULL, NULL, 'DETAILED') WHERE Page_type = 1) AS Pages,
    (SELECT COUNT(*) FROM TestTable) AS "Count";
ROLLBACK
GO
DROP TABLE TestTable
GO