フィルター条件がクラスター化列ストアインデックスに正しく適用されない


10

以下の例を使用すると、述語は同じですが、上のステートメントは(正しく)0行を返し、下のステートメントは1を返します-述語が一致しない場合でも:

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

なぜこれが起こっているのでしょうか?

詳細情報:

  • 先頭のステートメントの非クラスター化インデックスはフィルターされません
  • CheckDBが0の問題を返す
  • サーバーのバージョン: Microsoft SQL Azure (RTM) - 12.0.2000.8 Dec 19 2018 08:43:17 Copyright (C) 2018 Microsoft Corporation

計画リンクを貼り付けます。

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

詳細情報:

dbcc checktable ([transaction]) with all_errormsgs, extended_logical_checks, data_purity問題がないことを示す実行しました。

このデータベースのバックアップを復元するときに、このテーブルに対する問題を確実に再現できます。


コメントは詳細な議論のためのものではありません。この会話はチャットに移動しました
ジャックはtopanswers.xyzを試してみる

回答:


7

このバグでは、列の削除や名前の変更は必要ありません。

またstatusId = 100、どのバージョンの列にも存在しなかった同じ動作が表示されます。

必要条件

  • クラスター化列ストア
  • 非クラスター化bツリーインデックス
  • 列ストアでルックアップを実行するプラン
    • デルタストアのターゲット行
    • プッシュされた非SARG述語
    • 等価テストを使用したNULLとの比較

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;

次のいずれかでバグを回避できます。

  • 指定した圧縮行グループオプションを使用した再編成を含む任意の方法を使用して、デルタストアから行を移動する
  • 明示的に拒否する述語を書く = NULL
  • 文書化されていないトレースフラグ9130を有効にして、述語をルックアップにプッシュしないようにする

db <> fiddleデモ。


このバグは、SQL Server 2017のCU15(およびSQL Server 2016 SP2のCU7)で修正さました。

FIX:SQL Server 2016および2017で、クラスター化された列ストアインデックスと非クラスター化された行ストアインデックスの両方を持つテーブルに対するクエリが誤った結果を返すことがある


8

これはSQL Serverのバグです。クラスター化された列ストアインデックスを持つテーブルから列が削除され、同じ名前で新しい列が追加された場合、述語に古い削除された列が使用されているように見えます。MVCEは次のとおりです。

このスクリプトは10000statusIdof 1statusId2ofの行で始まり5、次にstatusID列を削除しstatusId2て名前をに変更しstatusIdます。したがって、最後にすべての行のa statusIdは5になるはずです。

しかし、次のクエリは非クラスター化インデックスにヒットします...

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

... 2行を返します(句statusIdによって暗黙的に選択されたものとは異なりWHEREます)...

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

...一方、これは列ストアにアクセスして正しく返します 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;

また、Azureフィードバックポータルで問題を提起しました

そして、これに遭遇した人のために、クラスター化列ストアインデックスを再構築すると問題が解決します。

alter index cci_example on example rebuild

CCIを再構築すると、既存のデータのみが修正されます。新しいレコードが追加された場合、問題はこれらのレコードで再び発生します。そのため、現在のところ、このテーブルの唯一の既知の修正は、テーブルを完全に再作成することです。


1
述語に古い問題を使用しているという問題だけではありません。他の奇妙なことは、それは完全に異なる列上の残留述語を壊すことであるand id2 = @id2として、とにかくゼロの行を保証すべき@id2であるnullが、あなたはまだ2取得
マーティン・スミス

RE:あなたの編集2はREORGANIZE WITH (COMPRESS_ALL_ROW_GROUPS = ON);その仕事をしますか?これでdeltastoreがクリアされます-その後に追加された新しい行でも問題は発生しますか?
マーティン・スミス

いいえ、悲しいことにまったく同じ結果のようですか?
Uberzen1

-4

プランに基づいて、ColumnstoreインデックスはSET ANSI_NULLS OFFで作成されたようです。テーブルとインデックスは、インデックスが作成されたときの設定を保持します。ANSI_NULLSがONであることを確認しながら重複する列ストアインデックスを作成し、元の列を削除するか無効にすることで確認できます。

ただし、SQL Serverのバグを発見していない限り、これが結果が発生する唯一の方法です。


2
1)フィルター処理されていないインデックスがANSI_NULLS設定をベーステーブルとは別に維持できること、および2)ANSI_NULLSがOFFでテーブルが作成されたときにセッションANSI_NULLS設定が実際に不一致を引き起こす可能性があることを確認しますか?
フォレスト

私はこれを考えましたが、CCIの定義をスクリプト化すると、設定オプションがありません。インデックス定義の前にSET ANSI_NULLS ONを使用して作成すると、結果は同じですか?
Uberzen1
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.