Filterbedingung nicht korrekt auf Clustered Columnstore-Index angewendet

10

Im folgenden Beispiel sind die Prädikate identisch, jedoch gibt die obere Anweisung (korrekt) 0 Zeilen zurück, die untere Anweisung 1 - obwohl die Prädikate NICHT übereinstimmen:

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

Warum könnte das passieren?

Weitere Infos:

  • Der nicht gruppierte Index in der obersten Anweisung wird NICHT gefiltert
  • CheckDB gibt 0 Probleme zurück
  • Serverversion: Microsoft SQL Azure (RTM) - 12.0.2000.8 Dec 19 2018 08:43:17 Copyright (C) 2018 Microsoft Corporation

Fügen Sie den Plan-Link ein:

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

Weitere Infos:

Sind gelaufen, dbcc checktable ([transaction]) with all_errormsgs, extended_logical_checks, data_puritywas auf keine Probleme hinweist.

Ich kann das Problem anhand dieser Tabelle zuverlässig reproduzieren, wenn eine Sicherung dieser Datenbank wiederhergestellt wird.

Uberzen1
quelle
Kommentare sind nicht für eine ausführliche Diskussion gedacht. Dieses Gespräch wurde in den Chat verschoben .
Jack sagt, versuchen Sie es mit topanswers.xyz am

Antworten:

7

Für diesen Fehler müssen keine Spalten gelöscht oder umbenannt werden.

Sie werden auch das gleiche Verhalten sehen, für statusId = 100das in keiner Version der Spalte etwas vorhanden war.

Bedarf

  • Ein Clustered Columnstore
  • Nicht gruppierter B-Tree-Index
  • Ein Plan, der eine Suche im Columnstore mit durchführt
    • Zielzeile (n) im Delta-Speicher
    • Ein Push-Nicht-SARG-Prädikat
    • Ein Vergleich mit NULL unter Verwendung eines Gleichheitstests

Beispiel

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;

Eine der folgenden Möglichkeiten vermeidet den Fehler:

  • Verschieben von Zeilen aus dem Delta-Speicher mit einer beliebigen Methode, einschließlich Reorganisation mit der angegebenen Option zum Komprimieren von Zeilengruppen
  • Schreiben des Prädikats, um es explizit abzulehnen = NULL
  • Aktivieren des undokumentierten Ablaufverfolgungsflags 9130, um zu vermeiden, dass das Prädikat in die Suche verschoben wird

db <> Geigen- Demo.


Dieser Fehler wurde festgelegt in WE 15 für SQL Server 2017 (und CU7 für SQL Server 2016 SP2):

UPDATE: Abfragen für Tabellen mit Clustered Columnstore-Index und Nonclustered Rowstore-Index können in SQL Server 2016 und 2017 zu falschen Ergebnissen führen

Paul White 9
quelle
8

Dies ist ein Fehler mit SQL Server. Wenn eine Spalte aus einer Tabelle mit einem Clustered Columnstore-Index gelöscht wird und dann eine neue Spalte mit demselben Namen hinzugefügt wird, wird anscheinend die alte, gelöschte Spalte für das Prädikat verwendet. Hier ist die MVCE:

Dieses Skript beginnt mit 10000Zeilen mit statusIdvon 1und statusId2von 5- löscht dann die statusIDSpalte und benennt sie statusId2in um statusId. Am Ende sollten alle Zeilen eine statusId5 haben.

Die folgende Abfrage trifft jedoch den nicht gruppierten Index ...

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

... und gibt 2Zeilen zurück (wobei sich die Auswahl statusIdvon der in der WHEREKlausel implizierten unterscheidet ) ...

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

... während dieser auf den Spaltenspeicher zugreift und korrekt zurückgibt 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;

Ich habe auch ein Problem im Azure-Feedback-Portal angesprochen :

Und für alle anderen, die darauf stoßen, behebt die Neuerstellung des Clustered Columnstore-Index das Problem:

alter index cci_example on example rebuild

Durch die Neuerstellung der CCI werden nur vorhandene Daten repariert. Wenn neue Datensätze hinzugefügt werden, tritt das Problem bei diesen Datensätzen erneut auf. Derzeit besteht die einzige bekannte Lösung für die Tabelle darin, sie vollständig neu zu erstellen.

Uberzen1
quelle
1
Nicht nur das Problem, dass das alte für das Prädikat verwendet wird. Die andere seltsame Sache ist , dass es vollständig das restliche Prädikat auf verschiedene Spalten bricht and id2 = @id2sollte Null Reihen sowieso garantieren wie @id2ist nullaber immer noch den 2 bekommen
Martin Smith
RE: Ihre Bearbeitung 2 macht REORGANIZE WITH (COMPRESS_ALL_ROW_GROUPS = ON);den Job? Dadurch wird der Deltastore gelöscht. Tritt das Problem weiterhin bei neuen Zeilen auf, die anschließend hinzugefügt werden?
Martin Smith
Nein, scheint leider genau das gleiche Ergebnis zu sein?
Uberzen1
-4

Basierend auf den Plänen scheint der Columnstore-Index mit SET ANSI_NULLS OFF erstellt worden zu sein. Tabellen und Indizes behalten die Einstellung bei, als der Index erstellt wurde. Sie können dies überprüfen, indem Sie einen doppelten Columnstore-Index erstellen und gleichzeitig sicherstellen, dass ANSI_NULLS aktiviert ist, und dann entweder das Original löschen oder deaktivieren.

Sofern Sie keinen SQL Server-Fehler entdeckt haben, können die Ergebnisse nur so erzielt werden.

Lachender Vergil
quelle
2
Sind Sie sicher, dass 1) nicht gefilterte Indizes ANSI_NULLS-Einstellungen getrennt von der Basistabelle verwalten können und 2) dass die Sitzungs-ANSI_NULLS-Einstellung tatsächlich zu Diskrepanzen führen kann, wenn die Tabelle mit ANSI_NULLS OFF erstellt wird?
Forrest
Ich habe das gedacht, aber wenn ich die Definition der CCI ausschreibe, gibt es keine festgelegten Optionen, und wenn ich sie mit SET ANSI_NULLS ON vor der Indexdefinition erstelle, ist das Ergebnis dasselbe?
Uberzen1