筛选条件未正确应用于“群集列存储”索引


10

使用下面的示例,谓词是相同的,但是top语句(正确)返回0行,bottom语句返回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

Answers:


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;

以下任何一项都会避免该错误:

  • 使用任何方法(包括使用指定的compress rowgroups选项进行重组)将行移出增量存储区
  • 编写谓词以明确拒绝 = NULL
  • 启用未记录的跟踪标志9130以避免将谓词推入查找中

db <> fiddle演示。


此bug已被固定在CU15为SQL Server 2017年(和CU7为SQL Server 2016 SP2):

FIX:对具有群集列存储索引和非群集行存储索引的表的查询可能会在SQL Server 2016和2017中返回错误的结果


8

这是SQL Server的错误。如果从具有聚集的列存储索引的表中删除了列,然后添加了一个具有相同名称的新列,则该列似乎使用了已删除的旧列作为谓词。这是MVCE:

该脚本开始与10000同排statusId1statusId25-然后删除statusID列,重命名statusId2statusId。因此,最后所有行应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应保证零行反正是@id2null,但你仍然得到2
马丁·史密斯

RE:您的编辑2可以REORGANIZE WITH (COMPRESS_ALL_ROW_GROUPS = ON);完成这项工作吗?这将清除deltastore-在此之后添加的新行是否仍然会出现问题?
马丁·史密斯,

不,看起来可悲的是完全一样的结果吗?
Uberzen1

-4

根据计划,看来Columnstore索引是使用SET ANSI_NULLS OFF创建的。表和索引保留与创建索引时相同的设置。您可以通过创建重复的Columnstore索引进行检查,同时确保ANSI_NULLS为ON,然后删除原始索引或禁用它。

但是,除非您发现了SQL Server错误,否则这是可能发生结果的唯一方法。


2
您是否确定1)未过滤的索引可以将ANSI_NULLS设置与基本表分开保存,以及2)会话ANSI_NULLS设置在使用ANSI_NULLS OFF创建表时实际上可能导致差异?
福雷斯特

我确实这么认为,但是当我用脚本编写CCI的定义时,它没有设置选项,并且如果在索引定义之前使用SET ANSI_NULLS ON创建它,结果是否相同?
Uberzen1
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.