Kondisi filter tidak diterapkan dengan benar ke indeks Clustered Columnstore index

10

Dengan menggunakan contoh di bawah ini, predikatnya sama, namun pernyataan atas (dengan benar) mengembalikan 0 baris, pernyataan bawah mengembalikan 1 - meskipun predikat tidak cocok:

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

Mengapa ini bisa terjadi?

Info lebih lanjut:

  • Indeks Non Clustered dalam pernyataan atas TIDAK disaring
  • CheckDB mengembalikan 0 masalah
  • Versi Server: Microsoft SQL Azure (RTM) - 12.0.2000.8 Dec 19 2018 08:43:17 Copyright (C) 2018 Microsoft Corporation

Tempel tautan Rencana:

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

Info lebih lanjut:

Telah berlari dbcc checktable ([transaction]) with all_errormsgs, extended_logical_checks, data_purityyang menunjukkan tidak ada masalah.

Saya dapat dipercaya mereproduksi masalah terhadap tabel ini ketika memulihkan cadangan database ini.

Uberzen1
sumber
Komentar bukan untuk diskusi panjang; percakapan ini telah dipindahkan ke obrolan .
Jack bilang coba topanswers.xyz

Jawaban:

7

Bug ini tidak perlu menjatuhkan atau mengganti nama kolom

Anda juga akan melihat perilaku yang sama statusId = 100yang tidak pernah ada di versi kolom mana pun.

Persyaratan

  • Toko kolom berkerumun
  • Indeks b-tree nonclustered
  • Sebuah rencana yang melakukan pencarian di kolom dengan
    • Baris target di toko delta
    • Predikat non-SARG yang didorong
    • Perbandingan dengan NULL menggunakan tes kesetaraan

Contoh

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;

Salah satu dari yang berikut ini akan menghindari bug:

  • Memindahkan baris keluar dari toko delta menggunakan metode apa pun termasuk mengatur ulang dengan opsi kompres rowgroup yang ditentukan
  • Menulis predikat untuk ditolak secara eksplisit = NULL
  • Mengaktifkan tanda jejak tidak berdokumen 9130 untuk menghindari mendorong predikat ke dalam pencarian

db <> demo biola .


Bug ini diperbaiki di CU15 untuk SQL Server 2017 (dan CU7 untuk SQL Server 2016 SP2):

MEMPERBAIKI: Permintaan terhadap tabel dengan kedua indeks kolomstore berkerumun dan indeks rowstore yang tidak tercakup dapat mengembalikan hasil yang salah di SQL Server 2016 dan 2017

Paul White 9
sumber
8

Ini adalah bug dengan SQL Server. Jika sebuah kolom dihapus dari tabel dengan indeks kolomstore berkerumun, dan kemudian kolom baru ditambahkan dengan nama yang sama, itu tampaknya menggunakan kolom lama, dihapus untuk predikat. Inilah MVCE:

Script ini dimulai dengan 10000baris dengan statusIddari 1dan statusId2dari 5- kemudian turun statusIDkolom dan ganti nama statusId2menjadi statusId. Jadi pada akhirnya semua baris harus memiliki statusId5.

Tetapi kueri berikut mengenai indeks yang tidak berkerumun ...

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

... dan mengembalikan 2baris (dengan yang dipilih statusIdberbeda dari yang tersirat oleh WHEREklausa) ...

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

... sedangkan yang ini mengakses toko kolom dan kembali dengan benar 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;

Saya juga telah mengangkat masalah pada portal umpan balik Azure :

Dan bagi siapa pun yang menjumpai ini, membangun kembali Clustered Columnstore Index memperbaiki masalah:

alter index cci_example on example rebuild

Membangun kembali CCI hanya memperbaiki data yang ada. Jika catatan baru ditambahkan, masalah muncul lagi pada catatan ini; jadi saat ini satu-satunya perbaikan yang diketahui untuk tabel adalah membuat ulang seluruhnya.

Uberzen1
sumber
1
Bukan hanya masalah bahwa itu menggunakan yang lama untuk predikat. Hal lain yang aneh adalah bahwa hal itu benar-benar istirahat predikat sisa pada kolom yang berbeda and id2 = @id2harus menjamin nol baris tetap seperti @id2ini nulltapi Anda masih mendapatkan 2
Martin Smith
RE: Suntingan Anda 2 REORGANIZE WITH (COMPRESS_ALL_ROW_GROUPS = ON);berhasil? Itu akan menghapus deltastore - apakah masalah masih terjadi untuk baris baru yang ditambahkan setelah itu?
Martin Smith
Tidak, sepertinya hasil yang persis sama dengan sedih?
Uberzen1
-4

Berdasarkan rencana, tampaknya indeks Columnstore dibuat dengan SET ANSI_NULLS OFF. Tabel dan indeks mempertahankan pengaturan seperti ketika indeks dibuat. Anda dapat memeriksa dengan membuat indeks Columnstore duplikat sambil memastikan bahwa ANSI_NULLS ON, lalu menjatuhkan yang asli atau menonaktifkannya.

Tapi, kecuali Anda telah menemukan bug SQL Server, ini adalah satu-satunya cara agar hasilnya bisa terjadi.

Tertawa Vergil
sumber
2
Apakah Anda yakin 1) indeks yang tidak difilter dapat mempertahankan pengaturan ANSI_NULLS terpisah dari tabel dasar, dan 2) bahwa pengaturan sesi ANSI_NULLS sebenarnya dapat menyebabkan perbedaan ketika tabel dibuat dengan ANSI_NULLS OFF?
Forrest
Saya memang memikirkan hal ini, tetapi ketika saya skrip definisi CCI tidak memiliki opsi set, dan jika saya membuatnya dengan SET ANSI_NULLS ON sebelum definisi indeks hasilnya sama?
Uberzen1