Eu tenho uma tabela em um banco de dados de produção com um tamanho de 525 GB, dos quais 383 GB não são utilizados:
Gostaria de recuperar parte desse espaço, mas, antes de mexer com o banco de dados de produção, estou testando algumas estratégias em uma tabela idêntica em um banco de dados de teste com menos dados. Esta tabela tem um problema semelhante:
Algumas informações sobre a tabela:
- O fator de preenchimento está definido como 0
- Existem cerca de 30 colunas
- Uma das colunas é um LOB de tipo de imagem e está armazenando arquivos com tamanho variando de algumas KB a várias centenas de MB
- A tabela não possui nenhum índice hipotético associado a ela
O servidor está executando o SQL Server 2017 (RTM-GDR) (KB4505224) - 14.0.2027.2 (X64). O banco de dados está usando o SIMPLE
modelo de recuperação.
Algumas coisas que eu tentei:
- Reconstrução dos índices:
ALTER INDEX ALL ON dbo.MyTable REBUILD
. Isso teve um impacto insignificante. - A reorganização dos índices:
ALTER INDEX ALL ON dbo.MyTable REORGANIZE WITH(LOB_COMPACTION = ON)
. Isso teve um impacto insignificante. Copiou a coluna LOB para outra tabela, soltou a coluna, recriou a coluna e copiou os dados novamente (conforme descrito nesta postagem: Liberando Espaço Não Utilizado na Tabela do SQL Server ). Isso diminuiu o espaço não utilizado, mas parecia convertê-lo em espaço usado:
Utilizou o utilitário bcp para exportar, truncá-la e recarregá-la (conforme descrito nesta postagem: Como liberar o espaço não utilizado para uma tabela ). Isso também reduziu o espaço não utilizado e aumentou o espaço usado em uma extensão semelhante à da imagem acima.
- Embora não seja recomendado, tentei os comandos DBCC SHRINKFILE e DBCC SHRINKDATABASE, mas eles não tiveram nenhum impacto no espaço não utilizado.
- Correr
DBCC CLEANTABLE('myDB', 'dbo.myTable')
não fez diferença - Eu tentei todas as opções acima, mantendo os tipos de dados de imagem e texto e depois de alterá-los para varbinary (max) e varchar (max).
- Tentei importar os dados para uma nova tabela em um banco de dados novo, e isso também converteu apenas o espaço não utilizado em espaço usado. Descrevi os detalhes dessa tentativa neste post .
Eu não quero fazer essas tentativas no banco de dados de produção se estes são os resultados que posso esperar, então:
- Por que o espaço não utilizado está sendo convertido em espaço usado após algumas dessas tentativas? Sinto que não tenho uma boa compreensão do que está acontecendo sob o capô.
- Há mais alguma coisa que eu possa fazer para diminuir o espaço não utilizado sem aumentar o espaço usado?
EDIT: Aqui está o relatório de uso de disco e script para a tabela:
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
Aqui estão os resultados da execução dos comandos na resposta de Max Vernon:
╔════════════╦═══════════╦════════════╦═════════════════╦══════════════════════╦════════════════════╗
║ TotalBytes ║ FreeBytes ║ TotalPages ║ TotalEmptyPages ║ PageBytesFreePercent ║ UnusedPagesPercent ║
╠════════════╬═══════════╬════════════╬═════════════════╬══════════════════════╬════════════════════╣
║ 9014280192║ 8653594624║ 1100376║ 997178 ║ 95.998700 ║ 90.621500 ║
╚════════════╩═══════════╩════════════╩═════════════════╩══════════════════════╩════════════════════╝
╔═════════════╦═══════════════════╦════════════════════╗
║ ObjectName ║ ReservedPageCount ║ UsedPageCount ║
╠═════════════╬═══════════════════╬════════════════════╣
║ dbo.MyTable ║ 5109090 ║ 2850245 ║
╚═════════════╩═══════════════════╩════════════════════╝
ATUALIZAR:
Executei o seguinte, conforme sugerido por Max Vernon:
DBCC UPDATEUSAGE (N'<database_name>', N'<table_name>');
E aqui estava a saída:
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.
Isso atualizou o uso do disco para a tabela:
E o uso geral do disco:
Portanto, parece que o problema foi que o uso do disco, conforme rastreado pelo SQL Server, ficou descontrolado com o uso real do disco. Considerarei esse problema resolvido, mas eu estaria interessado em saber por que isso teria acontecido em primeiro lugar!
DBCC CHECKDB
? Você já pensou em se afastando dos tipos de dados obsoletos ,text
eimage
? Eles podem estar contribuindo para as estatísticas impróprias.Respostas:
Eu executaria o DBCC UPDATEUSAGE na tabela como uma primeira etapa, pois os sintomas mostram o uso inconsistente de espaço.
A sintaxe é:
DBCC UPDATEUSAGE (N'<database_name>', N'<table_name>');
Depois de executar isso, eu correria
EXEC sys.sp_spaceused
contra a mesa:O comando acima tem a opção de atualizar o uso, mas desde que você executou
DBCC UPDATEUSAGE
manualmente primeiro, deixe esse conjunto como false. A execuçãoDBCC UPDATEUSAGE
manual permite ver se alguma coisa foi corrigida.A consulta a seguir deve mostrar a porcentagem de bytes livres na tabela e a porcentagem de páginas livres na tabela. Como a consulta usa um recurso não documentado, não é aconselhável contar com os resultados, mas parece preciso quando comparado com a saída de
sys.sp_spaceused
alto nível.Se a porcentagem de bytes livres for significativamente maior que a porcentagem de páginas gratuitas, você terá muitas páginas parcialmente vazias.
As páginas parcialmente vazias podem resultar de várias causas, incluindo:
Divisões de página, em que a página deve ser dividida para acomodar novas inserções no índice em cluster
Incapacidade de preencher a página com colunas devido ao tamanho da coluna.
A consulta usa a
sys.dm_db_database_page_allocations
função de gerenciamento dinâmico não documentada :A saída se parece com:
Eu escrevi uma postagem no blog descrevendo a função aqui .
No seu cenário, desde que você executou
ALTER TABLE ... REBUILD
, você deve ver um número muito baixo paraTotalEmptyPages
, mas acho que você ainda terá cerca de 72% emBytesFreePercent
.Eu usei seu
CREATE TABLE
script para tentar recriar seu cenário.Este é o MCVE que estou usando:
A consulta a seguir mostra uma única linha para cada página alocada para a tabela e usa a mesma DMV não documentada:
A saída mostrará muitas linhas se você a executar na tabela real em seu ambiente de teste, mas poderá permitir que você veja onde está o problema.
Você pode executar o script a seguir e postar os resultados em sua pergunta? Só estou tentando garantir que estamos na mesma página.
fonte
DBCC UPDATEUSAGE
atualizou o espaço não utilizado e a contagem de páginas não utilizadas. Parece que o uso do disco e as informações da página relatadas pelo SQL Server estavam extremamente fora de sincronia - atualizei minha postagem com os detalhes. Estou curioso sobre como isso teria acontecido em primeiro lugar, mas pelo menos o problema foi encontrado. Obrigado por toda a sua ajuda, eu realmente aprecio isso!Você pode estar enfrentando uma fragmentação interna.
Qual é a fragmentação da página para esta tabela?
E a fragmentação para a linha é diferente das páginas fora da linha?
Você diz que possui arquivos com alguns KB.
O SQL Server armazena tudo em páginas de 8060 bytes. Ou seja, se você tiver uma linha (ou dados fora de linha) com 4040 bytes e a próxima for semelhante, ela não poderá caber na mesma página e você perderá metade do seu espaço. Tente alterar o tamanho da linha armazenando colunas de comprimento variável (comece com a imagem, por exemplo) em uma tabela diferente.
fonte
O banco de dados está no modo de recuperação completa? Nesse caso, quando você executa uma redução, ela registra todas as alterações e não diminui da maneira que você espera. Dependendo do horário de funcionamento, você pode fazer um backup, alternar para o modo de recuperação de envio em massa e depois executar a redução no arquivo de dados. Depois disso, você deseja executar scripts de índice para reparar / reconstruir e voltar à recuperação total. É isso que eu tentaria de qualquer maneira, mas novamente, isso depende do seu horário de operação para tudo isso.
fonte
A única vez em que não consegui reduzir um banco de dados e recuperar espaço é porque você não pode reduzir um banco de dados além do tamanho inicial do banco de dados quando ele foi criado. Por exemplo, se o seu banco de dados for uma cópia do banco de dados de produção e você o criou pela primeira vez em 525 GB, o servidor sql não permitirá reduzir o tamanho abaixo de 525 GB, independentemente da quantidade de dados excluídos do banco de dados. Mas se o banco de dados foi criado abaixo de 383 GB e depois cresceu para 525 GB, você não deve ter problemas para recuperar o espaço. Há muito tempo penso que essa é uma restrição estúpida e arbitrária da Microsoft.
Reduza o banco de dados apenas até o tamanho inicial definido após a criação do banco de dados
fonte
Já encontrei esse problema antes nas caixas de produção, o que você precisa fazer é recriar tabelas e índices para cada tabela (nessa ordem).
Aqui está a consulta que eu uso para manter as tabelas sob controle. Isso ajudará a determinar quais tabelas precisam ser reconstruídas e a criar as consultas SQL que você precisa executar. Essa consulta é limitada àqueles com mais de 1 MB de espaço não utilizado e uma proporção de 5% não utilizada, para que você reconstrua apenas o que realmente precisa focar:
fonte