Apakah SQL Server menggunakan pointer daripada menyimpan duplikat baris?

8

Saya menggunakan sp_spaceusedprosedur tersimpan bawaan sebelum dan sesudah melakukan operasi dalam perangkat lunak kami untuk melihat tabel mana yang memiliki sisipan baris dan bagaimana ukuran setiap tabel berubah.

Apa yang saya lihat adalah bahwa dari semua tabel yang mendapatkan baris ditulis untuk mereka, hanya sedikit yang menunjukkan bahwa tabel telah meningkat di samping. Yang lain yang menunjukkan baris ditambahkan tidak menunjukkan perubahan ukuran dari prosedur yang disimpan ini.

Satu-satunya kasus ini tidak benar adalah pada transaksi pertama setelah melakukan pemotongan pada semua tabel. Jadi bagi saya tampak bahwa alih-alih menyimpan data duplikat SQL Server menunjukkan baris dimasukkan tetapi harus hanya menyimpan pointer ke baris identik sebelumnya.

Adakah yang bisa mengkonfirmasi ini?

Dan Revell
sumber
Ditandai untuk dba.se
gbn

Jawaban:

13

Tidak, SQL Server tidak mendeteksi baris duplikat

SQL Server sedang mengisi halaman kosong atau sebagian kosong di dalam halaman yang dialokasikan.

Jadi jika saya memiliki baris yang sangat sempit (2 kolom katakan), saya dapat menambahkan beberapa ratus baris lagi di halaman yang sama tanpa menambah ruang yang digunakan.

Demo cepat dan kotor (tanpa baris duplikat, tetapi Anda dapat bermain dengan ini jika mau)

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
sumber
Terima kasih jawaban yang luar biasa. Seandainya saya bisa memilih lebih banyak. Mungkin Anda juga bisa menyarankan, jika Anda baik, bagaimana saya bisa mengetahui data aktual yang digunakan dalam halaman. Yang bisa saya lihat adalah: baris nama data cadangan index_size Biaya tidak terpakai 6 16 KB 8 KB 8 KB 0 KB Dari ini saya tidak bisa melihat berapa banyak halaman yang saya gunakan untuk 6 baris saya. Ia memberi tahu saya bahwa saya 0KB belum digunakan di halaman ini, meskipun saya tahu ini bukan masalahnya.
Saya sudah mencoba DBCC SHOWCONTIG tetapi ini tidak menunjukkan kolom besar yang disimpan di LOB seperti yang saya mengerti.
Kupikir aku akan berkomentar daripada membuat pertanyaan baru ... Bagaimana cara menyimpan tabel yang lebih luas bekerja? Apa yang terjadi jika misalnya saya memiliki tabel yang benar-benar lebar tetapi sebagian besar waktu 60% atau lebih dari kolom adalah nol? Saya berasumsi bahwa baris ini akan mengambil jumlah ruang yang sama untuk disimpan di halaman karena kolom tersebut BISA memiliki data? Dalam hal penyimpanan saja (apa saja bisa diambil terlalu literal tentu saja) lebih baik memiliki lebih banyak tabel yang sempit? Jika Anda akhirnya harus sering-sering menarik kolom "kosong" maka mungkin masuk akal untuk menyimpannya bersama dengan tabel utama?
bdwakefield
7

Apakah SQL Server menggunakan pointer daripada menyimpan duplikat baris?

Itu tergantung pada versi SQL Server dan opsi kompresi data:

  • Mulai dari SQL Server 2008, ada opsi kompresi di tingkat baris atau halaman.
  • Kompresi tingkat halaman menggunakan banyak algoritma / teknik untuk kompresi. Mengenai pertanyaan Anda (petunjuk untuk data duplikat), kompresi halaman menggunakan (juga) kompresi awalan dan kompresi kamus :

Kompresi Awalan [...] Nilai awalan berulang dalam kolom digantikan oleh referensi ke awalan yang sesuai [...]

Kompresi Kamus Setelah kompresi awalan selesai, kompresi kamus diterapkan. Kompresi kamus mencari nilai berulang di mana saja pada halaman, dan menyimpannya di area CI. Tidak seperti kompresi awalan, kompresi kamus tidak terbatas pada satu kolom. Kompresi kamus dapat menggantikan nilai berulang yang terjadi di mana saja pada halaman. Ilustrasi berikut menunjukkan halaman yang sama setelah kompresi kamus.

Jadi, untuk awalan dan kompresi kamus (kompresi halaman), SQL Server menggunakan pointer untuk menyimpan (sebagian atau seluruhnya) nilai duplikat (bukan baris duplikat) dalam kolom yang sama atau di dalam diff. kolom.

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

Hasil:

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

PageCount_Person2_Compressed
----------------------------
2
Bogdan Sahlean
sumber