Condição de filtro não aplicada corretamente ao índice de armazenamento de colunas em cluster

10

Usando o exemplo abaixo, os predicados são os mesmos, no entanto, a instrução superior (corretamente) retorna 0 linhas, a instrução inferior retorna 1 - mesmo que os predicados NÃO correspondam:

declare @barcode nchar(22)=N'RECB012ZUKI449M1VBJZ'  
declare @tableId int = null
declare @total decimal(10, 2) = 5.17

SELECT 1
FROM
    [dbo].[transaction] WITH (INDEX([IX_Transaction_TransactionID_PaymentStatus_DeviceID_DateTime_All]))
WHERE
    Barcode = @barcode
    AND StatusID = 1
    AND TableID = @tableID
    AND @total <= Total

SELECT 1
FROM
    [dbo].[transaction] 
WHERE
    Barcode = @barcode
    AND StatusID = 1
    AND TableID = @tableID
    AND @total <= Total

Por que isso pode estar acontecendo?

Mais informações:

  • O índice não clusterizado na instrução superior NÃO é filtrado
  • CheckDB retorna 0 questões
  • Versão do servidor: Microsoft SQL Azure (RTM) - 12.0.2000.8 Dec 19 2018 08:43:17 Copyright (C) 2018 Microsoft Corporation

Cole o link do plano:

https://www.brentozar.com/pastetheplan/?id=S1w_rU68E

Mais informações:

Foram executados, o dbcc checktable ([transaction]) with all_errormsgs, extended_logical_checks, data_purityque indica que não há problemas.

Posso reproduzir de maneira confiável o problema nessa tabela ao restaurar um backup desse banco de dados.

Uberzen1
fonte
Comentários não são para discussão prolongada; esta conversa foi movida para o bate-papo .
Jack diz que tente topanswers.xyz 7/03/19

Respostas:

7

Este bug não requer a remoção ou renomeação de colunas.

Você também verá o mesmo comportamento para o statusId = 100qual nunca esteve presente em nenhuma versão da coluna.

Exigências

  • Um columnstore armazenado em cluster
  • Índice de árvore b não clusterizada
  • Um plano que executa uma pesquisa no columnstore com
    • Linhas de destino na loja delta
    • Um predicado não-SARG enviado
    • Uma comparação com NULL usando um teste de igualdade

Exemplo

DROP TABLE IF EXISTS dbo.Example;
GO
CREATE TABLE dbo.Example
(
    c1 integer NOT NULL,
    c2 integer NULL,

    INDEX CCS CLUSTERED COLUMNSTORE,
    INDEX IX NONCLUSTERED (c1)
);
GO
INSERT dbo.Example
    (c1, c2)
VALUES
    (1, NULL);
GO
DECLARE @c2 integer = NULL;

-- Returns one row but should not
SELECT
    E.* 
FROM dbo.Example AS E 
    WITH (INDEX(IX))
WHERE
    E.c2 = @c2;

Qualquer um dos seguintes procedimentos evitará o erro:

  • Movendo linhas para fora do repositório delta usando qualquer método, incluindo a reorganização com a opção compactar grupos de linhas especificada
  • Escrevendo o predicado para rejeitar explicitamente = NULL
  • Habilitando o sinalizador de rastreamento não documentado 9130 para evitar empurrar o predicado para a pesquisa

db <> demo de violino .


Este bug foi corrigido no CU15 para SQL Server 2017 (e CU7 para SQL Server 2016 SP2):

CORRECÇÃO: A consulta em uma tabela com o índice columnstore clusterizado e o índice rowstore não clusterizado pode retornar resultados incorretos no SQL Server 2016 e 2017

Paul White 9
fonte
8

Este é um erro no SQL Server. Se uma coluna é excluída de uma tabela com um índice columnstore clusterizado e, em seguida, uma nova coluna é adicionada com o mesmo nome, ela parece estar usando a coluna excluída antiga para o predicado. Aqui está o MVCE:

Esse script começa com 10000linhas com statusIdof 1e statusId2of 5- depois solta a statusIDcoluna e renomeia statusId2para statusId. Portanto, no final, todas as linhas devem ter um statusIdde 5.

Mas a consulta a seguir atinge o índice não agrupado ...

select *
from example
where statusId = 1
    and total <= @filter
    and barcode = @barcode
    and id2 = @id2

... e retorna 2linhas (com o selecionado statusIddiferente do implícito na WHEREcláusula) ...

+-------+---------+------+-------+----------+
|  id   | barcode | id2  | total | statusId |
+-------+---------+------+-------+----------+
|     5 |    5    | NULL |  5.00 |        5 |
| 10005 |    5    | NULL |  5.00 |        5 |
+-------+---------+------+-------+----------+

... enquanto este acessa o columnstore e retorna corretamente 0

select count(*) 
from example 
where statusId = 1

MVCE

/*Create table with clustered columnstore and non clustered rowstore*/
CREATE TABLE example
(
id        INT IDENTITY(1, 1),
barcode   CHAR(22),
id2       INT,
total     DECIMAL(10,2),
statusId  TINYINT,
statusId2 TINYINT,
INDEX cci_example CLUSTERED COLUMNSTORE,
INDEX ix_example (barcode, total)
);

/* Insert 10000 rows all with (statusId,statusId2) = (1,5) */
INSERT example
       (barcode,
        id2,
        total,
        statusId,
        statusId2)
SELECT TOP (10000) barcode = row_number() OVER (ORDER BY @@spid),
                   id2 = NULL,
                   total = row_number() OVER (ORDER BY @@spid),
                   statusId = 1,
                   statusId2 = 5
FROM   sys.all_columns c1, sys.all_columns c2;

ALTER TABLE example
  DROP COLUMN statusid
/* Now have 10000 rows with statusId2 = 5 */


EXEC sys.sp_rename
  @objname = N'dbo.example.statusId2',
  @newname = 'statusId',
  @objtype = 'COLUMN';
/* Now have 10000 rows with StatusID = 5 */

INSERT example
       (barcode,
        id2,
        total,
        statusId)
SELECT TOP (10000) barcode = row_number() OVER (ORDER BY @@spid),
                   id2 = NULL,
                   total = row_number() OVER (ORDER BY @@spid),
                   statusId = 5
FROM   sys.all_columns c1, sys.all_columns c2;
/* Now have 20000 rows with StatusID = 5 */


DECLARE @filter  DECIMAL = 5,
        @barcode CHAR(22) = '5',
        @id2     INT = NULL; 

/*This returns 2 rows from the NCI*/
SELECT *
FROM   example WITH (INDEX = ix_example)
WHERE  statusId = 1
       AND total <= @filter
       AND barcode = @barcode
       AND id2 = @id2;

/*This counts 0 rows from the Columnstore*/
SELECT COUNT(*)
FROM   example
WHERE  statusId = 1;

Também levantei um problema no portal de comentários do Azure :

E para qualquer pessoa que encontre isso, a reconstrução do Índice de armazenamento de colunas em cluster corrige o problema:

alter index cci_example on example rebuild

A reconstrução da CCI corrige apenas os dados existentes. Se novos registros forem adicionados, o problema surgirá novamente nesses registros; atualmente, a única correção conhecida para a tabela é recriá-la completamente.

Uberzen1
fonte
11
Não é apenas o problema que está usando o antigo para o predicado. A outra coisa estranha é que ele quebra completamente o predicado residual em diferentes colunas and id2 = @id2deve garantir zero linhas de qualquer maneira como @id2é null, mas você ainda obter o 2
Martin Smith
RE: Sua edição 2 faz REORGANIZE WITH (COMPRESS_ALL_ROW_GROUPS = ON);o trabalho? Isso limpará o deltastore - o problema ainda ocorre para novas linhas adicionadas posteriormente a isso?
Martin Smith
Não, parece ser exatamente o mesmo resultado, infelizmente?
Uberzen1
-4

Com base nos planos, parece que o índice Columnstore foi criado com SET ANSI_NULLS OFF. Tabelas e índices mantêm a configuração como era quando o índice foi criado. Você pode verificar criando um índice duplicado do Columnstore enquanto assegura que ANSI_NULLS está LIGADO, descartando o original ou desativando-o.

Mas, a menos que você tenha descoberto um bug do SQL Server, essa é a única maneira de os resultados acontecerem.

Rindo Vergil
fonte
2
Tem certeza de que 1) índices não filtrados podem manter as configurações ANSI_NULLS separadas da tabela base e 2) que a configuração ANSI_NULLS da sessão pode realmente causar discrepâncias quando a tabela é criada com ANSI_NULLS OFF?
Forrest
Eu pensei isso, mas quando eu escrevo a definição do CCI, ele não tem opções definidas, e se eu criá-lo com SET ANSI_NULLS ON antes da definição do índice, o resultado é o mesmo?
Uberzen1