¿SQL Server utiliza punteros en lugar de almacenar filas duplicadas?

8

Estoy usando el sp_spaceusedprocedimiento almacenado incorporado antes y después de realizar una operación en nuestro software para ver qué tablas tienen inserciones de fila y cómo cambia el tamaño de cada tabla.

Lo que estoy viendo es que de todas las tablas que tienen filas escritas, solo un puñado muestra que la tabla ha aumentado de lado. Los otros que muestran filas que se agregaron no muestran cambios en el tamaño de este procedimiento almacenado.

El único caso en que esto no es cierto es en la primera transacción después de realizar un truncamiento en todas las tablas. Entonces, para mí, parece que en lugar de almacenar datos duplicados, SQL Server muestra que se insertan filas, pero solo debe almacenar punteros a filas idénticas anteriores.

Puede alguien confirmar esto por favor?

Dan Revell
fuente
Marcado para dba.se
gbn

Respuestas:

13

No, SQL Server no detecta filas duplicadas

SQL Server está llenando páginas vacías o parcialmente vacías dentro de las páginas asignadas.

Entonces, si tengo una fila muy estrecha (por ejemplo, 2 columnas), puedo agregar unos cientos de filas más en la misma página sin aumentar el espacio utilizado.

Demostración rápida y sucia (sin filas duplicadas, pero puede jugar con esto si lo desea)

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
fuente
Gracias, esa es una respuesta tan maravillosa. Ojalá pudiera votar más. Quizás también podría sugerir, si fuera tan amable, cómo podría calcular los datos reales utilizados en la página. Todo lo que puedo ver es: nombre filas datos reservados índice_tamaño no utilizado Cargos 6 16 KB 8 KB 8 KB 0 KB De esto no puedo ver cuánto de la página estoy usando para mis 6 filas. Me dice que tengo 0 KB sin usar en esta página, aunque sé que este no es el caso.
He intentado DBCC SHOWCONTIG pero esto no muestra columnas grandes que están almacenadas en el LOB tal como lo entiendo.
Pensé que haría un comentario en lugar de crear una nueva pregunta ... ¿Cómo funciona el almacenamiento de tablas más amplias? ¿Qué sucede si, por ejemplo, tengo una tabla que es realmente amplia pero la mayoría de las veces el 60% de las columnas son nulas? Supongo que esta fila tomará la misma cantidad de espacio para almacenar en la página ya que esas columnas ¿ PODRÍAN tener datos? En términos de almacenamiento solamente (cualquier cosa puede tomarse demasiado literal, por supuesto), ¿es mejor tener más tablas que sean estrechas? Si terminas necesitando tirar de las columnas "vacías" con frecuencia de todos modos, ¿entonces probablemente tendría sentido mantener eso junto con la tabla principal?
bdwakefield
7

¿SQL Server utiliza punteros en lugar de almacenar filas duplicadas?

Depende de la versión de SQL Server y las opciones de compresión de datos:

  • A partir de SQL Server 2008, hay una opción de compresión a nivel de fila o página.
  • La compresión a nivel de página utiliza muchos algoritmos / técnicas para la compresión. Con respecto a su pregunta (punteros para datos duplicados), la compresión de página usa (también) compresión de prefijo y compresión de diccionario :

Compresión de prefijo [...] Los valores de prefijo repetidos en la columna se reemplazan por una referencia al prefijo correspondiente [...]

Compresión del diccionario Después de completar la compresión del prefijo, se aplica la compresión del diccionario. La compresión de diccionario busca valores repetidos en cualquier parte de la página y los almacena en el área de CI. A diferencia de la compresión de prefijos, la compresión de diccionario no está restringida a una columna. La compresión de diccionario puede reemplazar los valores repetidos que ocurren en cualquier lugar de una página. La siguiente ilustración muestra la misma página después de la compresión del diccionario.

Entonces, para la compresión de prefijos y diccionarios (compresión de páginas), SQL Server usa punteros para almacenar (duplicados total o parcialmente) valores duplicados (no filas duplicadas) dentro de la misma columna o dentro de diff. columnas

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
fuente