El orden de clasificación especificado en la clave primaria, pero la clasificación se ejecuta en SELECT

15

Estoy almacenando datos del sensor en una tabla SensorValues . La tabla y la clave primaria son las siguientes:

CREATE TABLE [dbo].[SensorValues](
  [DeviceId] [int] NOT NULL,
  [SensorId] [int] NOT NULL,
  [SensorValue] [int] NOT NULL,
  [Date] [int] NOT NULL,
CONSTRAINT [PK_SensorValues] PRIMARY KEY CLUSTERED 
(
  [DeviceId] ASC,
  [SensorId] ASC,
  [Date] DESC
) WITH (
    FILLFACTOR=75,
    DATA_COMPRESSION = PAGE,
    PAD_INDEX = OFF,
    STATISTICS_NORECOMPUTE = OFF,
    SORT_IN_TEMPDB = OFF,
    IGNORE_DUP_KEY = OFF,
    ONLINE = OFF,
    ALLOW_ROW_LOCKS = ON,
    ALLOW_PAGE_LOCKS = ON)
  ON [MyPartitioningScheme]([Date])

Sin embargo, cuando selecciono el valor del sensor válido por un tiempo específico, el plan de ejecución me dice que está haciendo una especie. ¿Porqué es eso?

Pensé que, dado que almaceno los valores ordenados por la columna Fecha, la clasificación no se produciría. ¿O es porque el índice no está ordenado únicamente por la columna Fecha, es decir, no puede suponer que el conjunto de resultados está ordenado?

SELECT TOP 1 SensorValue
  FROM SensorValues
  WHERE SensorId = 53
    AND DeviceId = 3819
    AND Date < 1339225010
  ORDER BY Date DESC

Plan de ejecución

Editar: ¿Puedo hacer esto en su lugar?

Dado que la tabla está ordenada DeviceId, SensorId, Date y hago un SELECT especificando solo un DeviceId y un SensorId , el conjunto de salida ya debería estar ordenado por Date DESC . Entonces, me pregunto si la siguiente pregunta arrojaría el mismo resultado en todos los casos.

SELECT TOP 1 SensorValue
  FROM SensorValues
  WHERE SensorId = 53
    AND DeviceId = 3819
    AND Date < 1339225010

Según @Catcall a continuación, el orden de clasificación no es el mismo que el orden de almacenamiento. Es decir, no podemos suponer que los valores devueltos ya están en un orden ordenado.

Editar: he probado esta solución CROSS APPLY, no tuve suerte

@ Martin Smith sugirió que intentara APLICAR EXTERIORMENTE mi resultado contra las particiones. Encontré una publicación de blog ( índices alineados no agrupados en una tabla particionada ) que describe este problema similar y probé una solución algo similar a lo que Smith sugirió. Sin embargo, no tuve suerte aquí, el tiempo de ejecución está a la par con mi solución original.

WITH Boundaries(boundary_id)
AS
(
  SELECT boundary_id
  FROM sys.partition_functions pf
  JOIN sys.partition_range_values prf ON pf.function_id = prf.function_id
  WHERE pf.name = 'PF'
  AND prf.value <= 1339225010
  UNION ALL
  SELECT max(boundary_id) + 1
  FROM sys.partition_functions pf
  JOIN sys.partition_range_values prf ON pf.function_id = prf.function_id
  WHERE pf.name = 'PF'
  AND prf.value <= 1339225010
),
Top1(SensorValue)
AS
(
  SELECT TOP 1 d.SensorValue
  FROM Boundaries b
  CROSS APPLY
  (
    SELECT TOP 1 SensorValue
      FROM SensorValues
      WHERE  SensorId = 53
        AND DeviceId = 3819
        AND "Date" < 1339225010
        AND $Partition.PF(Date) = b.boundary_id
        ORDER BY Date DESC
  ) d
  ORDER BY d.Date DESC
)
SELECT SensorValue
FROM Top1
metro__
fuente
OPCIÓN MAXDOP 1 no ayuda. Según lo especificado por @Martin Smith a continuación, parece que el particionamiento es lo que lo está causando ...
m__

Respuestas:

13

Para una tabla no particionada obtengo el siguiente plan

Plan 1

Hay un solo predicado de búsqueda en Seek Keys[1]: Prefix: DeviceId, SensorId = (3819, 53), Start: Date < 1339225010.

Lo que significa que SQL Server puede realizar una búsqueda de igualdad en las dos primeras columnas y luego comenzar una búsqueda de rango a partir de 1339225010y ordenada FORWARD(como se define el índice con [Date] DESC)

El TOPoperador dejará de solicitar más filas de la búsqueda después de que se emita la primera fila.

Cuando creo el esquema de partición y la función

CREATE PARTITION FUNCTION PF (int)
AS RANGE LEFT FOR VALUES (1000, 1339225009 ,1339225010 , 1339225011);
GO
CREATE PARTITION SCHEME [MyPartitioningScheme]
AS PARTITION PF
ALL TO ([PRIMARY] );

Y complete la tabla con los siguientes datos

INSERT INTO [dbo].[SensorValues]    
/*500 rows matching date and SensorId, DeviceId predicate*/
SELECT TOP (500) 3819,53,1, ROW_NUMBER() OVER (ORDER BY (SELECT 0))           
FROM master..spt_values
UNION ALL
/*700 rows matching date but not SensorId, DeviceId predicate*/
SELECT TOP (700) 3819,52,1, ROW_NUMBER() OVER (ORDER BY (SELECT 0))           
FROM master..spt_values
UNION ALL 
/*1100 rows matching SensorId, DeviceId predicate but not date */
SELECT TOP (1100) 3819,53,1, ROW_NUMBER() OVER (ORDER BY (SELECT 0)) + 1339225011      
FROM master..spt_values

El plan en SQL Server 2008 tiene el siguiente aspecto.

Plan 2

El número real de filas emitidas por la búsqueda es 500. El plan muestra buscar predicados

Seek Keys[1]: Start: PtnId1000 <= 2, End: PtnId1000 >= 1, 
Seek Keys[2]: Prefix: DeviceId, SensorId = (3819, 53), Start: Date < 1339225010

Indicando que está utilizando el enfoque de omisión de exploración descrito aquí

el optimizador de consultas se amplía para que se pueda realizar una operación de búsqueda o exploración con una condición en PartitionID (como la columna inicial lógica) y posiblemente en otras columnas de clave de índice, y luego se puede realizar una búsqueda de segundo nivel, con una condición diferente. en una o más columnas adicionales, para cada valor distinto que cumpla con la calificación para la operación de búsqueda de primer nivel.

Este plan es un plan en serie y, por lo tanto, para la consulta específica que tiene, parece que si SQL Server se asegurara de que procesara las particiones en orden descendente, dateel plan original con el TOPtodavía funcionaría y podría detener el procesamiento después de que la primera fila coincidente fuera encontrado en lugar de continuar y generar los 499 partidos restantes.

De hecho, parece que el plan para 2005 toma ese enfoque

Plan en 2005

No estoy seguro de si es sencillo obtener el mismo plan en 2008 o tal vez necesitaría un OUTER APPLYencendido sys.partition_range_valuespara simularlo.

Martin Smith
fuente
9

Mucha gente cree que un índice agrupado garantiza un orden de clasificación en la salida. Pero eso no es lo que hace; garantiza una orden de almacenamiento en disco.

Vea, por ejemplo, esta publicación de blog y esta discusión más larga .

Mike Sherrill 'Retiro del gato'
fuente
1
Bueno, anteriormente, el OP también dijo: "Pensé que, dado que almaceno los valores ordenados por la columna Fecha, la clasificación no se producirá [sic]". Entonces, al menos parte del problema es esa idea errónea sobre lo que hace un índice agrupado. Creo que es bueno enderezar eso de todos modos.
Mike Sherrill 'Cat Recall'
Tal vez solo estoy siendo terco (así que perdóname ;-)). De todos modos, he leído la publicación de blog de Hugo Kornelis y es bastante sencillo. Sin embargo, en su ejemplo, está usando un índice agrupado y uno no agrupado, el índice no agrupado es de menor tamaño y, por lo tanto, se está utilizando en el plan de ejecución. En mi caso, solo tengo un índice agrupado, ¿el servidor SQL aún puede devolver los valores en un orden incorrecto (no tiene un índice más pequeño para usar y los escaneos completos de la tabla son demasiado lentos)?
m__
He trasladado esto a una nueva pregunta (fuera del tema)
m__
5

Estoy especulando que el SORT es necesario debido a un plan paralelo. Baso esto en un artículo de blog tenue y distante: pero encontré esto en MSDN que puede o no justificar esto

Entonces, intente con MAXDOP 1 y vea qué sucede ...

También insinuado en la publicación de blog de @sql kiwi en Simple Talk en "Operador de intercambio", creo. Y "dependencia DOP" aquí

gbn
fuente
Aunque no me había molestado en configurar una función de partición dateantes. Ahora tengo y parece que la partición es la culpable de que 2005 se comporte mejor para esta consulta en particular.
Martin Smith
1

Básicamente tiene razón: dado que la clave principal está en el orden "DeviceId, SensorId, Date", los datos en la clave no están ordenados por fecha, por lo que no se pueden usar. Si su clave estaba en un orden diferente "Fecha, DeviceId, SensorId", entonces los datos en la clave se ordenarían por fecha, por lo que podrían usarse ...


fuente
Ya había intentado cambiar la clave de la manera que mencionaste, así que no lo sientas. De todos modos, intentaré crear el índice no agrupado en las 3 columnas y veré qué me da. (la búsqueda del índice faltante continúa ... ;-))
m__