¿Por qué no se utiliza el índice selectivo secundario cuando la cláusula where se filtra en `value ()`?


13

Preparar:

create table dbo.T
(
  ID int identity primary key,
  XMLDoc xml not null
);

insert into dbo.T(XMLDoc)
select (
       select N.Number
       for xml path(''), type
       )
from (
     select top(10000) row_number() over(order by (select null)) as Number
     from sys.columns as c1, sys.columns as c2
     ) as N;

XML de muestra para cada fila:

<Number>314</Number>

El trabajo para la consulta es contar el número de filas Tcon un valor especificado de <Number>.

Hay dos formas obvias de hacer esto:

select count(*)
from dbo.T as T
where T.XMLDoc.value('/Number[1]', 'int') = 314;

select count(*)
from dbo.T as T
where T.XMLDoc.exist('/Number[. eq 314]') = 1;

Resulta que value()y exists()requiere dos definiciones de ruta diferentes para que funcione el índice XML selectivo.

create selective xml index SIX_T on dbo.T(XMLDoc) for
(
  pathSQL = '/Number' as sql int singleton,
  pathXQUERY = '/Number' as xquery 'xs:double' singleton
);

La sqlversión es para value()y la xqueryversión es para exist().

Puede pensar que un índice como ese le daría un plan con una buena búsqueda, pero los índices XML selectivos se implementan como una tabla del sistema con la clave principal de Tla clave principal de la clave agrupada de la tabla del sistema. Las rutas especificadas son columnas dispersas en esa tabla. Si desea un índice de los valores reales de las rutas definidas, debe crear índices secundarios selectivos, uno para cada expresión de ruta.

create xml index SIX_T_pathSQL on dbo.T(XMLDoc)
  using xml index SIX_T for (pathSQL);

create xml index SIX_T_pathXQUERY on dbo.T(XMLDoc)
  using xml index SIX_T for (pathXQUERY);

El plan de consulta exist()realiza una búsqueda en el índice XML secundario seguido de una búsqueda clave en la tabla del sistema para el índice XML selectivo (no sé por qué es necesario) y finalmente realiza una búsqueda Tpara asegurarse de que realmente hay filas allí. La última parte es necesaria porque no hay restricción de clave externa entre la tabla del sistema y T.

ingrese la descripción de la imagen aquí

El plan para la value()consulta no es tan bueno. Realiza una exploración de índice agrupado Tcon una unión de bucles anidados contra una búsqueda en la tabla interna para obtener el valor de la columna dispersa y finalmente filtra el valor.

ingrese la descripción de la imagen aquí

Si un índice selectivo se debe usar o no se decide antes de la optimización, pero si un índice selectivo secundario se debe usar o no, es una decisión basada en el costo del optimizador.

¿Por qué no se utiliza el índice selectivo secundario cuando se filtra la cláusula where value()?

Actualizar:

Las consultas son semánticamente diferentes. Si agrega una fila con el valor

<Number>313</Number>
<Number>314</Number>` 

la exist()versión contaría 2 filas y la values()consulta contaría 1 fila. Pero con las definiciones de índice tal como se especifican aquí, el uso de la singletondirectiva SQL Server evitará que agregue una fila con múltiples <Number>elementos.

Sin embargo, eso no nos permite usar la values()función sin especificar [1]para garantizar al compilador que solo obtendremos un único valor. Esa [1]es la razón por la que tenemos un Top N Sort en el value()plan.

Parece que me estoy acercando a una respuesta aquí ...

Respuestas:


11

La declaración de singletonen la expresión de ruta del índice exige que no se puedan agregar varios <Number>elementos, pero el compilador XQuery no lo tiene en cuenta al interpretar la expresión en la value()función. Debe especificar [1]para hacer feliz a SQL Server. Usar XML escrito con un esquema tampoco ayuda con eso. Y debido a eso, SQL Server crea una consulta que usa algo que podría llamarse un patrón "aplicar".

La forma más fácil de demostrar es usar tablas regulares en lugar de XML simulando la consulta que realmente estamos ejecutando Ty la tabla interna.

Aquí está la configuración de la tabla interna como una tabla real.

create table dbo.xml_sxi_table
(
  pk1 int not null,
  row_id int,
  path_1_id varbinary(900),
  pathSQL_1_sql_value int,
  pathXQUERY_2_value float
);

go

create clustered index SIX_T on xml_sxi_table(pk1, row_id);
create nonclustered index SIX_pathSQL on xml_sxi_table(pathSQL_1_sql_value) where path_1_id is not null;
create nonclustered index SIX_T_pathXQUERY on xml_sxi_table(pathXQUERY_2_value) where path_1_id is not null;

go

insert into dbo.xml_sxi_table(pk1, row_id, path_1_id, pathSQL_1_sql_value, pathXQUERY_2_value)
select T.ID, 1, T.ID, T.ID, T.ID
from dbo.T;

Con ambas tablas en su lugar, puede ejecutar el equivalente de la exist()consulta.

select count(*)
from dbo.T
where exists (
             select *
             from dbo.xml_sxi_table as S
             where S.pk1 = T.ID and
                   S.pathXQUERY_2_value = 314 and
                   S.path_1_id is not null
             );

El equivalente de la value()consulta se vería así.

select count(*)
from dbo.T
where (
      select top(1) S.pathSQL_1_sql_value
      from dbo.xml_sxi_table as S
      where S.pk1 = T.ID and
            S.path_1_id is not null
      order by S.path_1_id
      ) = 314;

El top(1)y order by S.path_1_ides el culpable y [1]la culpa es de la expresión Xpath.

No creo que sea posible que Microsoft arregle esto con la estructura actual de la tabla interna, incluso si se le permitió omitir [1]la values()función. Probablemente tendrían que crear varias tablas internas para cada expresión de ruta con restricciones únicas para garantizar al optimizador que solo puede haber un <number>elemento para cada fila. No estoy seguro de que eso sea suficiente para que el optimizador "salga del patrón de aplicación".

Para ustedes que piensan que esto es divertido e interesante, y como todavía lo están leyendo, probablemente lo sean.

Algunas consultas para ver la estructura de la tabla interna.

select T.name, 
       T.internal_type_desc, 
       object_name(T.parent_id) as parent_table_name
from sys.internal_tables as T
where T.parent_id = object_id('T');

select C.name as column_name, 
       C.column_id,
       T.name as type_name,
       C.max_length,
       C.is_sparse,
       C.is_nullable
from sys.columns as C
  inner join sys.types as T
    on C.user_type_id = T.user_type_id
where C.object_id in (
                     select T.object_id 
                     from sys.internal_tables as T 
                     where T.parent_id = object_id('T')
                     )
order by C.column_id;

select I.name as index_name,
       I.type_desc,
       I.is_unique,
       I.filter_definition,
       IC.key_ordinal,
       C.name as column_name, 
       C.column_id,
       T.name as type_name,
       C.max_length,
       I.is_unique,
       I.is_unique_constraint
from sys.indexes as I
  inner join sys.index_columns as IC
    on I.object_id = IC.object_id and
       I.index_id = IC.index_id
  inner join sys.columns as C
    on IC.column_id = C.column_id and
       IC.object_id = C.object_id
  inner join sys.types as T
    on C.user_type_id = T.user_type_id
where I.object_id in (
                     select T.object_id 
                     from sys.internal_tables as T 
                     where T.parent_id = object_id('T')
                     );
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.