Использует ли SQL Server указатели вместо хранения повторяющихся строк?

8

Я использую встроенную sp_spaceusedхранимую процедуру до и после выполнения операции в нашем программном обеспечении, чтобы увидеть, какие таблицы имеют вставки строк и как изменяется размер каждой таблицы.

Что я вижу, так это то, что из всех таблиц, в которые записываются строки, лишь небольшое количество показывает, что таблица увеличилась в сторону. Другие, которые показывают строки, были добавлены, не показывают изменения в размере от этой хранимой процедуры.

Единственный случай, когда это не так, - это первая транзакция после выполнения усечения для всех таблиц. Поэтому мне кажется, что вместо хранения дубликатов данных SQL Server показывает, что строки вставлены, но должны просто хранить указатели на предыдущие идентичные строки.

Кто-нибудь может подтвердить это, пожалуйста?

Дэн Ревелл
источник
Помечено для dba.se
gbn

Ответы:

13

Нет, SQL Server не обнаруживает повторяющиеся строки

SQL Server заполняет пустые или частично пустые страницы в выделенных страницах.

Поэтому, если у меня очень узкая строка (скажем, в 2 столбцах), я могу добавить еще несколько сотен строк на той же странице, не увеличивая используемое пространство.

Быстрое и грязное демо (без дублирующих строк, но вы можете поиграть с этим, если хотите)

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
ГБН
источник
Спасибо, вот такой замечательный ответ. Хотел бы я больше проголосовать. Возможно, вы могли бы также предложить, если вы будете так любезны, как я могу определить фактические данные, используемые на странице. Все, что я вижу, это: имя строки зарезервированные данные index_size неиспользованные расходы 6 16 КБ 8 КБ 8 КБ 0 КБ Из этого я не могу видеть, какую часть страницы я использую для своих 6 строк. Это говорит мне, что я не использовал 0KB на этой странице, хотя я знаю, что это не так.
Я пробовал DBCC SHOWCONTIG, но это не показывает большие столбцы, которые хранятся в LOB, как я понимаю.
Думаю, я бы прокомментировал, а не создал новый вопрос ... Как работает хранение более широких таблиц? Что произойдет, если, например, у меня действительно большая таблица, но в большинстве случаев 60% столбцов имеют нулевые значения? Я предполагаю, что эта строка займет столько же места для хранения на странице, так как эти столбцы МОГУТ иметь данные? С точки зрения только хранения (что-нибудь, конечно, можно воспринимать слишком буквально), лучше иметь больше узких таблиц? Если вам все равно придется часто вставлять «пустые» столбцы, то, возможно, имеет смысл сохранить это вместе с основной таблицей?
bdwakefield
7

Использует ли SQL Server указатели вместо хранения повторяющихся строк?

Это зависит от версии SQL Server и параметров сжатия данных:

  • Начиная с SQL Server 2008, существует опция сжатия на уровне строки или страницы.
  • Сжатие на уровне страницы использует много алгоритмов / методов для сжатия. Что касается вашего вопроса (указатели для дублированных данных), сжатие страниц использует (также) префиксное сжатие и словарное сжатие :

Сжатие префикса [...] Повторяющиеся значения префикса в столбце заменяются ссылкой на соответствующий префикс [...]

Сжатие словаря После завершения префиксного сжатия применяется сжатие словаря. Сжатие по словарю ищет повторяющиеся значения в любом месте страницы и сохраняет их в области CI. В отличие от префиксного сжатия, словарное сжатие не ограничивается одним столбцом. Сжатие словаря может заменить повторяющиеся значения, которые встречаются в любом месте на странице. На следующем рисунке показана та же страница после сжатия словаря.

Таким образом, для сжатия префиксов и словарей (сжатие страниц) SQL Server использует указатели для хранения (частично или полностью) дублированных значений (не дублированных строк) в одном столбце или в diff. колонны.

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

Результаты:

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

PageCount_Person2_Compressed
----------------------------
2
Богдан Сахлин
источник