La condition de filtre n'est pas correctement appliquée à l'index Clumnstore de cluster


10

En utilisant l'exemple ci-dessous, les prédicats sont les mêmes, cependant l'instruction supérieure (correctement) renvoie 0 lignes, l'instruction inférieure renvoie 1 - même si les prédicats ne correspondent PAS:

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

Pourquoi cela pourrait-il arriver?

Plus d'infos:

  • L'index non groupé dans l'instruction supérieure n'est PAS filtré
  • CheckDB renvoie 0 problème
  • Version du serveur: Microsoft SQL Azure (RTM) - 12.0.2000.8 Dec 19 2018 08:43:17 Copyright (C) 2018 Microsoft Corporation

Collez le lien Plan:

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

Plus d'infos:

Ont couru, dbcc checktable ([transaction]) with all_errormsgs, extended_logical_checks, data_purityce qui indique aucun problème.

Je peux reproduire le problème de manière fiable sur cette table lors de la restauration d'une sauvegarde de cette base de données.


Les commentaires ne sont pas pour une discussion approfondie; cette conversation a été déplacée vers le chat .
Jack dit d'essayer topanswers.xyz

Réponses:


7

Ce bogue ne nécessite pas de supprimer ou de renommer les colonnes.

Vous verrez également le même comportement pour statusId = 100lequel il n'a jamais été présent dans aucune version de la colonne.

Exigences

  • Un magasin de colonnes en cluster
  • Index b-tree non clusterisé
  • Un plan qui effectue une recherche sur le magasin de colonnes avec
    • Ligne (s) cible (s) dans le magasin delta
    • Un prédicat poussé non SARG
    • Une comparaison avec NULL à l'aide d'un test d'égalité

Exemple

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;

L'un des éléments suivants évitera le bogue:

  • Déplacement de lignes hors du magasin delta à l'aide de n'importe quelle méthode, y compris la réorganisation avec l'option de compression des groupes de lignes spécifiée
  • Écriture du prédicat à rejeter explicitement = NULL
  • Activation de l'indicateur de trace non documenté 9130 pour éviter de pousser le prédicat dans la recherche

démo db <> fiddle .


Ce bogue a été corrigé dans CU15 pour SQL Server 2017 (et CU7 pour SQL Server 2016 SP2):

CORRECTIF: requête sur la table avec à la fois l'index clusterstore columnstore et nonclustered rowstore peut renvoyer des résultats incorrects dans SQL Server 2016 et 2017


8

Il s'agit d'un bogue avec SQL Server. Si une colonne est supprimée d'une table avec un index columnstore en cluster, puis qu'une nouvelle colonne est ajoutée avec le même nom, elle semble utiliser l'ancienne colonne supprimée pour le prédicat. Voici le MVCE:

Ce script commence par des 10000lignes avec statusIdde 1et statusId2de 5- puis supprime la statusIDcolonne et renomme statusId2en statusId. Donc, à la fin, toutes les lignes doivent avoir un statusId5.

Mais la requête suivante frappe l'index non cluster ...

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

... et retourne des 2lignes (avec la sélection statusIddifférente de celle impliquée par la WHEREclause) ...

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

... alors que celui-ci accède au magasin de colonnes et renvoie correctement 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;

J'ai également soulevé un problème sur le portail de commentaires Azure :

Et pour tous ceux qui rencontrent cela, la reconstruction de l'index de clustered Columnstore résout le problème:

alter index cci_example on example rebuild

La reconstruction de la CCI ne corrige que les données existantes. Si de nouveaux enregistrements sont ajoutés, le problème se pose à nouveau sur ces enregistrements; donc actuellement le seul correctif connu pour la table est de la recréer entièrement.


1
Pas seulement le problème qu'il utilise l'ancien pour le prédicat. L'autre chose étrange est qu'il casse complètement le prédicat résiduel sur les différentes colonnes and id2 = @id2doivent garantir aucune ligne de toute façon comme @id2est , nullmais vous obtenez toujours le 2
Martin Smith

RE: Votre montage 2 fait REORGANIZE WITH (COMPRESS_ALL_ROW_GROUPS = ON);le travail? Cela effacera le deltastore - le problème persiste-t-il pour les nouvelles lignes ajoutées par la suite?
Martin Smith

Non, semble-t-il malheureusement être exactement le même résultat?
Uberzen1

-4

Sur la base des plans, il semble que l'index Columnstore a été créé avec SET ANSI_NULLS OFF. Les tables et les index conservent le paramètre tel qu'il était lors de la création de l'index. Vous pouvez vérifier en créant un index Columnstore en double tout en vous assurant que ANSI_NULLS est activé, puis en supprimant l'original ou en le désactivant.

Mais, à moins que vous n'ayez découvert un bogue SQL Server, c'est la seule façon dont les résultats peuvent se produire.


2
Êtes-vous sûr que 1) les index non filtrés peuvent conserver les paramètres ANSI_NULLS séparément de la table de base et 2) que le paramètre ANSI_NULLS de session peut réellement provoquer des écarts lorsque la table est créée avec ANSI_NULLS OFF?
Forrest

J'ai pensé cela, mais quand je script la définition de la CCI, il n'a pas d'options définies, et si je le crée avec SET ANSI_NULLS ON avant la définition de l'index, le résultat est le même?
Uberzen1
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.