Ken*_*Ken 15 index sql-server storage sql-server-2017
我在生产数据库中有一个表,其大小为 525 GB,其中 383 GB 未使用:
我想回收其中的一些空间,但是,在弄乱生产数据库之前,我正在测试数据库中数据较少的相同表上测试一些策略。这个表有一个类似的问题:
关于表的一些信息:
服务器正在运行 SQL Server 2017 (RTM-GDR) (KB4505224) - 14.0.2027.2 (X64)。数据库正在使用SIMPLE
恢复模型。
我尝试过的一些事情:
ALTER INDEX ALL ON dbo.MyTable REBUILD
. 这产生了微不足道的影响。ALTER INDEX ALL ON dbo.MyTable REORGANIZE WITH(LOB_COMPACTION = ON)
. 这产生了微不足道的影响。将 LOB 列复制到另一个表,删除该列,重新创建该列,并将数据复制回来(如这篇文章所述:释放未使用的空间 SQL Server 表)。这减少了未使用的空间,但似乎只是将其转换为已用空间:
使用 bcp 实用程序导出表、截断它并重新加载它(如这篇文章所述:如何为表释放未使用的空间)。这也减少了未使用的空间并增加了与上图类似的程度。
DBCC CLEANTABLE('myDB', 'dbo.myTable')
没什么区别如果这些是我可以预期的结果,我不想在生产数据库上进行这些尝试,因此:
编辑:这是该表的磁盘使用情况报告和脚本:
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[MyTable](
[Column1] [int] NOT NULL,
[Column2] [int] NOT NULL,
[Column3] [int] NOT NULL,
[Column4] [bit] NOT NULL,
[Column5] [tinyint] NOT NULL,
[Column6] [datetime] NULL,
[Column7] [int] NOT NULL,
[Column8] [varchar](100) NULL,
[Column9] [varchar](256) NULL,
[Column10] [int] NULL,
[Column11] [image] NULL,
[Column12] [text] NULL,
[Column13] [varchar](100) NULL,
[Column14] [varchar](6) NULL,
[Column15] [int] NOT NULL,
[Column16] [bit] NOT NULL,
[Column17] [datetime] NULL,
[Column18] [varchar](50) NULL,
[Column19] [varchar](50) NULL,
[Column20] [varchar](60) NULL,
[Column21] [varchar](20) NULL,
[Column22] [varchar](120) NULL,
[Column23] [varchar](4) NULL,
[Column24] [varchar](75) NULL,
[Column25] [char](1) NULL,
[Column26] [varchar](50) NULL,
[Column27] [varchar](128) NULL,
[Column28] [varchar](50) NULL,
[Column29] [int] NULL,
[Column30] [text] NULL,
CONSTRAINT [PK] PRIMARY KEY CLUSTERED
(
[Column1] ASC,
[Column2] ASC,
[Column3] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]
GO
ALTER TABLE [dbo].[MyTable] ADD CONSTRAINT [DF_Column4] DEFAULT (0) FOR [Column4]
GO
ALTER TABLE [dbo].[MyTable] ADD CONSTRAINT [DF_Column5] DEFAULT (0) FOR [Column5]
GO
ALTER TABLE [dbo].[MyTable] ADD CONSTRAINT [DF_Column15] DEFAULT (0) FOR [Column15]
GO
ALTER TABLE [dbo].[MyTable] ADD CONSTRAINT [DF_Column16] DEFAULT (0) FOR [Column16]
GO
Run Code Online (Sandbox Code Playgroud)
以下是执行 Max Vernon 回答中的命令的结果:
?????????????????????????????????????????????????????????????????????????????????????????????????????
? TotalBytes ? FreeBytes ? TotalPages ? TotalEmptyPages ? PageBytesFreePercent ? UnusedPagesPercent ?
?????????????????????????????????????????????????????????????????????????????????????????????????????
? 9014280192? 8653594624? 1100376? 997178 ? 95.998700 ? 90.621500 ?
?????????????????????????????????????????????????????????????????????????????????????????????????????
Run Code Online (Sandbox Code Playgroud)
????????????????????????????????????????????????????????
? ObjectName ? ReservedPageCount ? UsedPageCount ?
????????????????????????????????????????????????????????
? dbo.MyTable ? 5109090 ? 2850245 ?
????????????????????????????????????????????????????????
Run Code Online (Sandbox Code Playgroud)
更新:
我按照 Max Vernon 的建议运行了以下命令:
DBCC UPDATEUSAGE (N'<database_name>', N'<table_name>');
Run Code Online (Sandbox Code Playgroud)
这是输出:
DBCC UPDATEUSAGE: Usage counts updated for table 'MyTable' (index 'PK_MyTable', partition 1):
USED pages (LOB Data): changed from (568025) to (1019641) pages.
RSVD pages (LOB Data): changed from (1019761) to (1019763) pages.
Run Code Online (Sandbox Code Playgroud)
这更新了表的磁盘使用情况:
以及整体磁盘使用情况:
因此,问题似乎在于 SQL Server 跟踪的磁盘使用情况与实际磁盘使用情况严重不同步。我会认为这个问题已经解决,但我很想知道为什么会发生这种情况!
Han*_*non 10
作为第一步,我会针对表运行DBCC UPDATEUSAGE,因为症状显示空间使用不一致。
DBCC UPDATEUSAGE 更正表或索引中每个分区的行、已用页、保留页、叶页和数据页计数。如果系统表中没有错误,DBCC UPDATEUSAGE 将不返回任何数据。如果发现并更正了错误并且未使用 WITH NO_INFOMSGS,则 DBCC UPDATEUSAGE 返回系统表中正在更新的行和列。
语法是:
DBCC UPDATEUSAGE (N'<database_name>', N'<table_name>');
在你运行之后,我会跑到EXEC sys.sp_spaceused
桌子上:
EXEC sys.sp_spaceused @objname = N'dbo.MyTable'
, @updateusage = 'false' --true or false
, @mode = 'ALL' --ALL, LOCAL_ONLY, REMOTE_ONLY
, @oneresultset = 1;
Run Code Online (Sandbox Code Playgroud)
上面的命令可以选择更新使用情况,但由于您DBCC UPDATEUSAGE
首先手动运行,只需将其设置为 false。DBCC UPDATEUSAGE
手动运行允许您查看是否有任何更正。
以下查询应显示表中空闲字节的百分比和表中空闲页的百分比。由于查询使用了未记录的功能,因此指望结果是不明智的,但与 , 的输出相比sys.sp_spaceused
,它在高层次上似乎是准确的。
如果空闲字节的百分比明显高于空闲页面的百分比,那么你有很多部分空的页面。
部分空白的页面可能源于多种原因,包括:
页面拆分,必须拆分页面以容纳新的插入到聚集索引中
由于列大小而无法用列填充页面。
查询使用未公开的sys.dm_db_database_page_allocations
动态管理功能:
;WITH dpa AS
(
SELECT dpa.*
, page_free_space_percent_corrected =
CASE COALESCE(dpa.page_type_desc, N'')
WHEN N'TEXT_MIX_PAGE' THEN 100 - COALESCE(dpa.page_free_space_percent, 100)
WHEN N'TEXT_TREE_PAGE' THEN 100 - COALESCE(dpa.page_free_space_percent, 100)
ELSE COALESCE(dpa.page_free_space_percent, 100)
END
FROM sys.dm_db_database_page_allocations(DB_ID(), OBJECT_ID('dbo.MyTable'), NULL, NULL, 'DETAILED') dpa
)
, src AS
(
SELECT TotalKB = COUNT_BIG(1) * 8192 / 1024
, FreeKB = SUM((dpa.page_free_space_percent_corrected / 100) * CONVERT(bigint, 8192)) / 1024
, TotalPages = COUNT_BIG(1)
, TotalEmptyPages = SUM(CASE WHEN dpa.page_free_space_percent_corrected = 100 THEN 1 ELSE 0 END) --completely empty pages
FROM dpa
)
SELECT *
, BytesFreePercent = (CONVERT(decimal(38,2), src.FreeKB) / src.TotalKB) * 100
, UnusedPagesPercent = (CONVERT(decimal(38,2), src.TotalEmptyPages) / src.TotalPages) * 100
FROM src
Run Code Online (Sandbox Code Playgroud)
输出看起来像:
?????????????????????????????????????????????????????? ????????????????????????????????????????????? ? 总KB ? 自由知识库?总页数?总空页数?BytesFreePercent ?未使用的页面百分比? ?????????????????????????????????????????????????????? ????????????????????????????????????????????? ? 208?96 ? 26 ? 12 ? 46.153800?46.153800? ?????????????????????????????????????????????????????? ?????????????????????????????????????????????
我写了一篇博客文章描述了这里的功能。
在您的场景中,由于您已经执行了ALTER TABLE ... REBUILD
,您应该看到 的数量非常少TotalEmptyPages
,但我猜您仍然会有大约 72% 的BytesFreePercent
.
我已经使用您的CREATE TABLE
脚本尝试重新创建您的场景。
这是我正在使用的MCVE:
DROP TABLE IF EXISTS dbo.MyTable;
CREATE TABLE [dbo].[MyTable](
[Column1] [int] NOT NULL IDENTITY(1,1),
[Column2] [int] NOT NULL,
[Column3] [int] NOT NULL,
[Column4] [bit] NOT NULL,
[Column5] [tinyint] NOT NULL,
[Column6] [datetime] NULL,
[Column7] [int] NOT NULL,
[Column8] [varchar](100) NULL,
[Column9] [varchar](256) NULL,
[Column10] [int] NULL,
[Column11] [image] NULL,
[Column12] [text] NULL,
[Column13] [varchar](100) NULL,
[Column14] [varchar](6) NULL,
[Column15] [int] NOT NULL,
[Column16] [bit] NOT NULL,
[Column17] [datetime] NULL,
[Column18] [varchar](50) NULL,
[Column19] [varchar](50) NULL,
[Column20] [varchar](60) NULL,
[Column21] [varchar](20) NULL,
[Column22] [varchar](120) NULL,
[Column23] [varchar](4) NULL,
[Column24] [varchar](75) NULL,
[Column25] [char](1) NULL,
[Column26] [varchar](50) NULL,
[Column27] [varchar](128) NULL,
[Column28] [varchar](50) NULL,
[Column29] [int] NULL,
[Column30] [text] NULL,
CONSTRAINT [PK] PRIMARY KEY CLUSTERED
(
[Column1] ASC,
[Column2] ASC,
[Column3] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]
ALTER TABLE [dbo].[MyTable] ADD CONSTRAINT [DF_Column4] DEFAULT (0) FOR [Column4]
ALTER TABLE [dbo].[MyTable] ADD CONSTRAINT [DF_Column5] DEFAULT (0) FOR [Column5]
ALTER TABLE [dbo].[MyTable] ADD CONSTRAINT [DF_Column15] DEFAULT (0) FOR [Column15]
ALTER TABLE [dbo].[MyTable] ADD CONSTRAINT [DF_Column16] DEFAULT (0) FOR [Column16]
GO
INSERT INTO dbo.MyTable (
Column2
, Column3
, Column4
, Column5
, Column6
, Column7
, Column8
, Column9
, Column10
, Column11
, Column12
, Column13
, Column14
, Column15
, Column16
, Column17
, Column18
, Column19
, Column20
, Column21
, Column22
, Column23
, Column24
, Column25
, Column26
, Column27
, Column28
, Column29
, Column30
)
VALUES (
0
, 0
, 0
, 0
, '2019-07-09 00:00:00'
, 1
, REPLICATE('A', 50)
, REPLICATE('B', 128)
, 0
, REPLICATE(CONVERT(varchar(max), 'a'), 1)
, REPLICATE(CONVERT(varchar(max), 'b'), 9000)
, REPLICATE('C', 50)
, REPLICATE('D', 3)
, 0
, 0
, '2019-07-10 00:00:00'
, REPLICATE('E', 25)
, REPLICATE('F', 25)
, REPLICATE('G', 30)
, REPLICATE('H', 10)
, REPLICATE('I', 120)
, REPLICATE('J', 4)
, REPLICATE('K', 75)
, 'L'
, REPLICATE('M', 50)
, REPLICATE('N', 128)
, REPLICATE('O', 50)
, 0
, REPLICATE(CONVERT(varchar(max), 'c'), 90000)
);
--GO 100
;WITH dpa AS
(
SELECT dpa.*
, page_free_space_percent_corrected =
CASE COALESCE(dpa.page_type_desc, N'')
WHEN N'TEXT_MIX_PAGE' THEN 100 - COALESCE(dpa.page_free_space_percent, 100)
WHEN N'TEXT_TREE_PAGE' THEN 100 - COALESCE(dpa.page_free_space_percent, 100)
ELSE COALESCE(dpa.page_free_space_percent, 100)
END
FROM sys.dm_db_database_page_allocations(DB_ID(), OBJECT_ID('dbo.MyTable'), NULL, NULL, 'DETAILED') dpa
)
, src AS
(
SELECT TotalKB = COUNT_BIG(1) * 8192 / 1024
, FreeKB = SUM((dpa.page_free_space_percent_corrected / 100) * CONVERT(bigint, 8192)) / 1024
, TotalPages = COUNT_BIG(1)
, TotalEmptyPages = SUM(CASE WHEN dpa.page_free_space_percent_corrected = 100 THEN 1 ELSE 0 END) --completely empty pages
FROM dpa
)
SELECT *
, BytesFreePercent = (CONVERT(decimal(38,2), src.FreeKB) / src.TotalKB) * 100
, UnusedPagesPercent = (CONVERT(decimal(38,2), src.TotalEmptyPages) / src.TotalPages) * 100
FROM src
Run Code Online (Sandbox Code Playgroud)
以下查询为分配给表的每个页面显示一行,并使用相同的未记录 DMV:
SELECT DatabaseName = d.name
, ObjectName = o.name
, IndexName = i.name
, PartitionID = dpa.partition_id
, dpa.allocation_unit_type_desc
, dpa.allocated_page_file_id
, dpa.allocated_page_page_id
, dpa.is_allocated
, dpa.page_free_space_percent --this seems unreliable
, page_free_space_percent_corrected =
CASE COALESCE(dpa.page_type_desc, N'')
WHEN N'TEXT_MIX_PAGE' THEN 100 - COALESCE(dpa.page_free_space_percent, 100)
WHEN N'TEXT_TREE_PAGE' THEN 100 - COALESCE(dpa.page_free_space_percent, 100)
ELSE COALESCE(dpa.page_free_space_percent, 100)
END
, dpa.page_type_desc
, dpa.is_page_compressed
, dpa.has_ghost_records
FROM sys.dm_db_database_page_allocations(DB_ID(), OBJECT_ID('dbo.MyTable'), NULL, NULL, 'DETAILED') dpa
LEFT JOIN sys.databases d ON dpa.database_id = d.database_id
LEFT JOIN sys.objects o ON dpa.object_id = o.object_id
LEFT JOIN sys.indexes i ON dpa.object_id = i.object_id AND dpa.index_id = i.index_id
WHERE dpa.database_id = DB_ID() --sanity check for sys.objects and sys.indexes
Run Code Online (Sandbox Code Playgroud)
如果您在测试环境中针对真实表运行它,输出将显示很多行,但它可能会让您看到问题出在哪里。
您可以运行以下脚本并将结果发布在您的问题中吗?我只是想确保我们在同一页面上。
SELECT ObjectName = s.name + N'.' + o.name
, ReservedPageCount = SUM(dps.reserved_page_count)
, UsePageCount = SUM(dps.used_page_count)
FROM sys.schemas s
INNER JOIN sys.objects o ON s.schema_id = o.schema_id
INNER JOIN sys.partitions p ON o.object_id = p.object_id
INNER JOIN sys.dm_db_partition_stats dps ON p.object_id = dps.object_id
WHERE s.name = N'dbo'
AND o.name = N'MyTable'
GROUP BY s.name + N'.' + o.name;
Run Code Online (Sandbox Code Playgroud)
归档时间: |
|
查看次数: |
1288 次 |
最近记录: |