O SQL Server usa ponteiros em vez de armazenar linhas duplicadas?

8

Estou usando o sp_spaceusedprocedimento armazenado interno antes e depois de executar uma operação em nosso software para ver quais tabelas têm inserções de linha e como o tamanho de cada tabela é alterado.

O que estou vendo é que, de todas as tabelas que recebem linhas escritas para elas, apenas um punhado mostra que a tabela aumentou de lado. Os outros que mostram linhas foram adicionadas não mostram alterações no tamanho deste procedimento armazenado.

O único caso em que isso não é verdade é na primeira transação após executar um truncado em todas as tabelas. Então, para mim, parece que, em vez de armazenar dados duplicados, o SQL Server está mostrando linhas inseridas, mas deve estar apenas armazenando ponteiros para linhas idênticas anteriores.

Alguém pode confirmar isso por favor?

Dan Revell
fonte
Sinalizado para dba.se
gbn

Respostas:

13

Não, o SQL Server não detecta linhas duplicadas

O SQL Server está preenchendo páginas vazias ou parcialmente vazias nas páginas alocadas.

Portanto, se eu tiver uma linha muito estreita (digamos 2 colunas), posso adicionar mais algumas centenas de linhas na mesma página sem aumentar o espaço usado.

Demonstração rápida e suja (sem linhas duplicadas, mas você pode jogar com isso, se quiser)

IF OBJECT_ID('dbo.Demo') IS NOT NULL
    DROP TABLE dbo.Demo;
GO
CREATE TABLE dbo.Demo (DemoID int NOT NULL IDENTITY(1,1), Demo char(1) NOT NULL)
GO
SELECT 'zero rows, zero space', SUM(ps.reserved_page_count)/128.0 AS ReservedMB, SUM(ps.used_page_count)/128.0 AS UsedMB
FROM sys.dm_db_partition_stats ps
WHERE ps.object_id = OBJECT_ID('dbo.Demo')
GO

INSERT dbo.Demo VALUES ('a');
GO
SELECT 'one row. Peanuts', SUM(ps.reserved_page_count)/128.0 AS ReservedMB, SUM(ps.used_page_count)/128.0 AS UsedMB
FROM sys.dm_db_partition_stats ps
WHERE ps.object_id = OBJECT_ID('dbo.Demo')
GO

INSERT dbo.Demo VALUES ('b');
GO 100
SELECT '101 rows. All on one page', SUM(ps.reserved_page_count)/128.0 AS ReservedMB, SUM(ps.used_page_count)/128.0 AS UsedMB
FROM sys.dm_db_partition_stats ps
WHERE ps.object_id = OBJECT_ID('dbo.Demo')
GO

INSERT dbo.Demo VALUES ('b');
GO 1899
SELECT '2000 rows. More than one page', SUM(ps.reserved_page_count)/128.0 AS ReservedMB, SUM(ps.used_page_count)/128.0 AS UsedMB
FROM sys.dm_db_partition_stats ps
WHERE ps.object_id = OBJECT_ID('dbo.Demo')
GO

TRUNCATE TABLE dbo.Demo
GO
SELECT 'zero rows, zero space. TRUNCATE deallocates pages', SUM(ps.reserved_page_count)/128.0 AS ReservedMB, SUM(ps.used_page_count)/128.0 AS UsedMB
FROM sys.dm_db_partition_stats ps
WHERE ps.object_id = OBJECT_ID('dbo.Demo')
GO

INSERT dbo.Demo VALUES ('c');
GO 500
SELECT '500 rows. Some space used', SUM(ps.reserved_page_count)/128.0 AS ReservedMB, SUM(ps.used_page_count)/128.0 AS UsedMB
FROM sys.dm_db_partition_stats ps
WHERE ps.object_id = OBJECT_ID('dbo.Demo')
GO

DELETE dbo.Demo
GO
SELECT 'zero rows after delete. Space still allocated', SUM(ps.reserved_page_count)/128.0 AS ReservedMB, SUM(ps.used_page_count)/128.0 AS UsedMB
FROM sys.dm_db_partition_stats ps
WHERE ps.object_id = OBJECT_ID('dbo.Demo')
GO

IF OBJECT_ID('dbo.Demo') IS NOT NULL
    DROP TABLE dbo.Demo;
GO
gbn
fonte
Obrigado, é uma resposta tão maravilhosa. Gostaria de poder votar mais. Talvez você também possa sugerir, se você for tão gentil, como eu posso descobrir os dados reais usados ​​na página. Tudo o que posso ver é: nome das linhas dados reservados index_size não utilizados Encargos 6 16 KB 8 KB 8 KB 0 KB Com isso, não consigo ver quanto da página estou usando nas minhas 6 linhas. Está me dizendo que eu não usei 0 KB nesta página, mesmo sabendo que esse não é o caso.
Eu tentei o DBCC SHOWCONTIG, mas isso não mostra colunas grandes que são armazenadas no LOB como eu a entendo.
Imaginei que gostaria de comentar, em vez de criar uma nova pergunta ... Como funciona o armazenamento de tabelas mais amplas? O que acontece se, por exemplo, eu tenho uma tabela realmente larga, mas na maioria das vezes 60% das colunas são nulas? Presumo que esta linha ocupe a mesma quantidade de espaço para armazenar na página, pois essas colunas PODEM ter dados? Apenas em termos de armazenamento (é claro que algo pode ser tomado como literal), é melhor ter mais tabelas estreitas? Se você precisar extrair as colunas "vazias" com frequência de qualquer maneira, provavelmente faria sentido manter isso junto com a tabela principal?
precisa saber é o seguinte
7

O SQL Server usa ponteiros em vez de armazenar linhas duplicadas?

Depende da versão do SQL Server e das opções de compactação de dados:

  • A partir do SQL Server 2008, há uma opção de compactação no nível da linha ou da página.
  • A compactação no nível da página usa muitos algoritmos / técnicas para compactação. Em relação à sua pergunta (ponteiros para dados duplicados), a compactação de página usa (também) compactação de prefixo e compactação de dicionário :

Compactação do prefixo Os valores repetidos do prefixo na coluna são substituídos por uma referência ao prefixo correspondente.

Compactação de dicionário Após a compactação do prefixo, a compactação de dicionário é aplicada. A compactação do dicionário procura valores repetidos em qualquer lugar da página e os armazena na área de IC. Ao contrário da compactação de prefixo, a compactação de dicionário não está restrita a uma coluna. A compactação do dicionário pode substituir valores repetidos que ocorrem em qualquer lugar da página. A ilustração a seguir mostra a mesma página após a compactação do dicionário.

Portanto, para compactação de prefixo e dicionário (compactação de página), o SQL Server usa ponteiros para armazenar (parcialmente ou totalmente) valores duplicados (não linhas duplicadas) na mesma coluna ou no diff. colunas.

CREATE DATABASE TestComp;
GO

USE TestComp;
GO

CREATE TABLE Person1 (
    PersonID INT IDENTITY PRIMARY KEY,
    FirstName NVARCHAR(100) NOT NULL,
    LastName NVARCHAR(100) NOT NULL
);
GO

DECLARE 
    @f NVARCHAR(100) = REPLICATE('A',100), 
    @l NVARCHAR(100) = REPLICATE('B',100);

INSERT Person1 (FirstName, LastName)
VALUES (@f, @l);
GO 1000

CREATE TABLE Person2 (
    PersonID INT IDENTITY PRIMARY KEY,
    FirstName NVARCHAR(100) NOT NULL,
    LastName NVARCHAR(100) NOT NULL
);
GO

ALTER TABLE Person2
REBUILD
WITH (DATA_COMPRESSION=PAGE);
GO

DECLARE 
    @f NVARCHAR(100) = REPLICATE('A',100), 
    @l NVARCHAR(100) = REPLICATE('B',100);

INSERT Person2 (FirstName, LastName)
VALUES (@f, @l);
GO 1000

SELECT  f.page_count AS PageCount_Person1_Uncompressed
FROM    sys.dm_db_index_physical_stats(DB_ID(), OBJECT_ID('Person1'), 1, DEFAULT, DEFAULT) f
SELECT  f.page_count AS PageCount_Person2_Compressed
FROM    sys.dm_db_index_physical_stats(DB_ID(), OBJECT_ID('Person2'), 1, DEFAULT, DEFAULT) f
GO

Resultados:

PageCount_Person1_Uncompressed
------------------------------
53

PageCount_Person2_Compressed
----------------------------
2
Bogdan Sahlean
fonte