Jika kolom VARCHAR (MAX) dimasukkan dalam indeks, apakah seluruh nilai selalu disimpan di halaman indeks?

12

Saya menanyakan ini karena penasaran, terinspirasi oleh pertanyaan ini .

Kita tahu bahwa VARCHAR(MAX)nilai lebih dari 8000 byte tidak disimpan dalam baris, tetapi di halaman LOB terpisah. Selanjutnya mengambil baris dengan nilai seperti itu membutuhkan dua atau lebih operasi IO logis (pada dasarnya, satu lebih dari yang secara teoritis diperlukan).

Kami dapat menambahkan VARCHAR(MAX)kolom, seperti INCLUDEd, ke indeks unik, seperti yang ditunjukkan dalam pertanyaan terkait. Jika kolom ini memiliki nilai yang panjangnya melebihi 8000 byte, apakah nilai tersebut masih disimpan "inline" di halaman daun indeks, atau apakah mereka juga akan dipindahkan ke halaman LOB?

mustaccio
sumber

Jawaban:

16

Nilai yang melebihi 8000 byte tidak dapat disimpan "inline". Mereka disimpan di halaman LOB. Anda dapat melihat ini dengan sys.dm_db_index_physical_stats . Mulai dengan tabel sederhana:

USE tempdb;

DROP TABLE IF EXISTS #LOB_FOR_ME;

CREATE TABLE #LOB_FOR_ME (
ID BIGINT,
MAX_VERNON_WAS_HERE VARCHAR(MAX) 
);

CREATE INDEX IX ON #LOB_FOR_ME (ID) INCLUDE (MAX_VERNON_WAS_HERE);

Sekarang masukkan beberapa baris dengan nilai yang mengambil 8000 byte untuk VARCHAR(MAX)kolom dan periksa DMF:

USE tempdb;

INSERT INTO #LOB_FOR_ME
SELECT 1, REPLICATE('Z', 8000)
FROM master..spt_values;

SELECT index_level, index_type_desc, alloc_unit_type_desc, page_count, record_count
FROM sys.dm_db_index_physical_stats(DB_ID(), OBJECT_ID('#LOB_FOR_ME'), 2, NULL , 'DETAILED'); 

Tidak ada halaman LOB dalam indeks:

╔═════════════╦════════════════════╦══════════════════════╦════════════╦══════════════╗
 index_level   index_type_desc    alloc_unit_type_desc  page_count  record_count 
╠═════════════╬════════════════════╬══════════════════════╬════════════╬══════════════╣
           0  NONCLUSTERED INDEX  IN_ROW_DATA                 2540          2540 
           1  NONCLUSTERED INDEX  IN_ROW_DATA                   18          2540 
           2  NONCLUSTERED INDEX  IN_ROW_DATA                    1            18 
╚═════════════╩════════════════════╩══════════════════════╩════════════╩══════════════╝

Tetapi jika saya menambahkan baris dengan nilai yang mengambil 8001 byte:

USE tempdb;

INSERT INTO #LOB_FOR_ME
SELECT 2, REPLICATE(CAST('Z' AS VARCHAR(MAX)), 8001)
FROM master..spt_values;

SELECT index_level, index_type_desc, alloc_unit_type_desc, page_count, record_count
FROM sys.dm_db_index_physical_stats(DB_ID(), OBJECT_ID('#LOB_FOR_ME'), 2, NULL , 'DETAILED'); 

Sekarang saya memiliki 1 halaman LOB dalam indeks untuk setiap baris yang baru saja saya masukkan:

╔═════════════╦════════════════════╦══════════════════════╦════════════╦══════════════╗
 index_level   index_type_desc    alloc_unit_type_desc  page_count  record_count 
╠═════════════╬════════════════════╬══════════════════════╬════════════╬══════════════╣
           0  NONCLUSTERED INDEX  IN_ROW_DATA                 2556          5080 
           1  NONCLUSTERED INDEX  IN_ROW_DATA                   18          2556 
           2  NONCLUSTERED INDEX  IN_ROW_DATA                    1            18 
           0  NONCLUSTERED INDEX  LOB_DATA                    2540          2540 
╚═════════════╩════════════════════╩══════════════════════╩════════════╩══════════════╝

Anda juga dapat melihat ini dengan SET STATISTICS IO ON;dan permintaan yang tepat. Pertimbangkan kueri berikut yang hanya melihat baris dengan 8000 byte:

SELECT SUM(LEN(MAX_VERNON_WAS_HERE))
FROM #LOB_FOR_ME
WHERE ID = 1;

Hasil setelah dieksekusi:

Pindai hitungan 1, pembacaan logis 2560, pembacaan fisik 0, pembacaan read-ahead 0, pembacaan logis lob 0, pembacaan fisik lob 0, pembacaan lob baca-depan 0.

Jika saya sebagai gantinya meminta baris dengan 8001 byte:

SELECT SUM(LEN(MAX_VERNON_WAS_HERE))
FROM #LOB_FOR_ME
WHERE ID = 2;

Sekarang saya melihat lob berbunyi:

Pindai hitungan 1, bacaan logis 20, bacaan fisik 0, bacaan baca-depan 0, bacaan lob logis 5080, bacaan fisik lob 0, bacaan lob baca-depan 0.

Joe Obbish
sumber