Ordem de classificação especificada na chave primária, mas a classificação é executada em SELECT

15

Estou armazenando dados do sensor em uma tabela SensorValues . A tabela e a chave primária são as seguintes:

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

No entanto, quando seleciono o valor do sensor válido por um tempo específico, o plano de execução diz que está fazendo uma classificação. Por que é que?

Eu pensaria que, desde que eu armazene os valores classificados pela coluna Data, a classificação não ocorrerá. Ou é porque o índice não é classificado apenas pela coluna Data, ou seja, não pode assumir que o conjunto de resultados foi classificado?

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

Plano de execução

Edit: Posso fazer isso?

Como a tabela é classificada DeviceId, SensorId, Date e eu fazemos um SELECT especificando apenas um DeviceId e um SensorId , o conjunto de saída já deve ser classificado por Data DESC . Então, eu me pergunto se a seguinte pergunta renderia o mesmo resultado em todos os casos?

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

De acordo com @Catcall abaixo, a ordem de classificação não é a mesma que a ordem de armazenamento. Ou seja, não podemos assumir que os valores retornados já estejam em uma ordem classificada.

Edit: Eu tentei esta solução CROSS APPLY, sem sorte

@ Martin Smith sugeriu que eu tentaria aplicar o meu resultado fora das partições. Encontrei uma postagem no blog ( índices alinhados não agrupados na tabela particionada ) descrevendo esse problema semelhante e tentei a solução um pouco semelhante à sugerida por Smith. No entanto, sem sorte aqui, o tempo de execução está a par da minha solução 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
m__
fonte
A opção MAXDOP 1 não ajuda. Conforme especificado pelo @ Martin Smith abaixo, parece que o particionamento é o que está causando isso ...
m__

Respostas:

13

Para uma tabela não particionada, recebo o seguinte plano

Plano 1

Existe um único predicado de busca ativado Seek Keys[1]: Prefix: DeviceId, SensorId = (3819, 53), Start: Date < 1339225010.

Significa que o SQL Server pode executar uma busca de igualdade nas duas primeiras colunas e iniciar uma busca de intervalo iniciando 1339225010e ordenada FORWARD(conforme o índice é definido [Date] DESC)

O TOPoperador irá parar de solicitar mais linhas da busca após a primeira linha ser emitida.

Quando eu crio o esquema e a função de partição

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

E preencha a tabela com os seguintes dados

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

O plano no SQL Server 2008 é o seguinte.

Plano 2

O número real de linhas emitidas a partir da pesquisa é 500. O plano mostra procurar predicados

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

Indicando que está usando a abordagem de ignorar varredura descrita aqui

o otimizador de consulta é estendido para que uma operação de busca ou verificação com uma condição possa ser realizada no PartitionID (como a coluna principal lógica) e possivelmente em outras colunas de chave de índice e, em seguida, uma busca de segundo nível, com uma condição diferente, possa ser realizada em uma ou mais colunas adicionais, para cada valor distinto que atenda à qualificação para a operação de busca de primeiro nível.

Esse plano é um plano serial e, portanto, para a consulta específica, parece que, se o SQL Server garantisse o processamento das partições em ordem decrescente, dateo plano original TOPainda funcionaria e poderia interromper o processamento após a primeira linha correspondente. encontrado em vez de continuar e gerar as 499 correspondências restantes.

De fato, o plano para 2005 parece seguir essa abordagem

Planejar 2005

Eu não tenho certeza se ele é para a frente para obter o mesmo plano em 2008 ou talvez seria necessário um OUTER APPLYem sys.partition_range_valuessimular isso.

Martin Smith
fonte
9

Muitas pessoas acreditam que um índice em cluster garante uma ordem de classificação na saída. Mas não é isso que faz; garante uma ordem de armazenamento em disco.

Veja, por exemplo, esta postagem no blog e essa discussão mais longa .

Mike Sherrill 'Recorde Gato'
fonte
1
Bem, anteriormente, o OP também dizia: "Eu pensaria que, desde que armazene os valores classificados pela coluna Data, a classificação não ocorrerá [sic]". Portanto, pelo menos parte do problema é esse equívoco sobre o que um índice clusterizado faz. Eu acho que é bom esclarecer isso de qualquer maneira.
Mike Sherrill 'Cat Recall'
Talvez eu esteja apenas sendo teimoso (então, por favor, me perdoe ;-)). De qualquer forma, eu li o post de Hugo Kornelis e é bem direto. No entanto, em seu exemplo, ele está usando um índice em cluster e um não em cluster, o índice não em cluster é menor em tamanho e, portanto, está sendo usado no plano de execução. No meu caso, tenho apenas um índice em cluster, o servidor sql ainda pode retornar os valores na ordem errada (ele não possui um índice menor para usar e as verificações de tabela completa são muito lentas)?
m__
Eu mudei isso para uma nova pergunta (fora de tópico)
m__
5

Estou especulando que o SORT é necessário por causa do plano paralelo. Baseei isso em algum artigo obscuro e distante do blog: mas encontrei isso no MSDN, o que pode ou não justificar isso

Então, tente com o MAXDOP 1 e veja o que acontece ...

Também sugerido na postagem do blog de @sql kiwi no Simple Talk, em "Exchange Operator", eu acho. E "dependência DOP" aqui

gbn
fonte
Embora eu não tivesse me incomodado em configurar uma função de partição dateantes. Agora eu tenho e parece que particionar é o culpado de 2005 possivelmente se comportando melhor para essa consulta em particular.
Martin Smith
1

Basicamente, você está certo - já que a chave primária está na ordem "DeviceId, SensorId, Date", os dados na chave não são classificados por data, portanto, não podem ser usados. Se a sua chave estivesse em uma ordem diferente "Data, DeviceId, SensorId", os dados na chave seriam classificados por data, para que pudessem ser usados ​​...


fonte
Eu já havia tentado mudar a chave da maneira que você mencionou, então não sinta muito. De qualquer forma, tentarei criar o índice não agrupado nas 3 colunas e ver o que isso me dá. (a busca pelo índice ausente continua ... ;-))
m__ 12/12