Si se incluye una columna VARCHAR (MAX) en un índice, ¿el valor completo siempre se almacena en las páginas de índice?

12

Lo pregunto por curiosidad, inspirado por esta pregunta .

Sabemos que los VARCHAR(MAX)valores de más de 8000 bytes no se almacenan en filas, sino en páginas LOB separadas. Posteriormente, recuperar una fila con dicho valor requiere dos o más operaciones lógicas de E / S (esencialmente, una más de lo contrario sería teóricamente necesario).

Podemos agregar una VARCHAR(MAX)columna, como INCLUDEd, a un índice único, como se demuestra en la pregunta vinculada. Si esta columna tiene valores que exceden los 8000 bytes de longitud, ¿estos valores se almacenarán "en línea" en las páginas de índice o también se moverán a las páginas LOB?

mustaccio
fuente

Respuestas:

16

Los valores que superan los 8000 bytes no se pueden almacenar "en línea". Se almacenan en páginas LOB. Puede ver esto con sys.dm_db_index_physical_stats . Comience con una tabla simple:

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);

Ahora inserte algunas filas con valores que toman 8000 bytes para la VARCHAR(MAX)columna y revise el 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'); 

No hay páginas LOB en el índice:

╔═════════════╦════════════════════╦══════════════════════╦════════════╦══════════════╗
 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 
╚═════════════╩════════════════════╩══════════════════════╩════════════╩══════════════╝

Pero si agrego filas con valores que toman 8001 bytes:

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'); 

Ahora tengo 1 página LOB en el índice para cada fila que acabo de insertar:

╔═════════════╦════════════════════╦══════════════════════╦════════════╦══════════════╗
 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 
╚═════════════╩════════════════════╩══════════════════════╩════════════╩══════════════╝

También puede ver esto con SET STATISTICS IO ON;y la consulta correcta. Considere la siguiente consulta que solo mira las filas con 8000 bytes:

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

Resultados al ejecutar:

Cuenta de escaneo 1, lecturas lógicas 2560, lecturas físicas 0, lecturas de lectura anticipada 0, lecturas lógicas lob 0, lecturas físicas lob 0, lecturas de lectura lob 0.

Si, en cambio, consulto las filas con 8001 bytes:

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

Ahora veo lob lee:

Recuento de escaneo 1, lecturas lógicas 20, lecturas físicas 0, lecturas anticipadas 0, lecturas lógicas lob 5080, lecturas físicas lob 0, lecturas anticipadas lob 0.

Joe Obbish
fuente