Условие фильтра неправильно применено к индексу Clustered Columnstore

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что указывает на отсутствие проблем.

Я могу надежно воспроизвести проблему с этой таблицей при восстановлении резервной копии этой базы данных.

Uberzen1
источник
Комментарии не для расширенного обсуждения; этот разговор был перенесен в чат .
Джек говорит, попробуйте topanswers.xyz

Ответы:

7

Эта ошибка не требует удаления или переименования столбцов.

Вы также увидите то же поведение, statusId = 100которого никогда не было ни в одной версии столбца.

Требования

  • Кластерное хранилище столбцов
  • Некластерный индекс b-дерева
  • План, который выполняет поиск в хранилище columns
    • Целевые строки в дельта-магазине
    • Выдвинутый предикат без 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, чтобы избежать вставки предиката в поиск

БД <> Fiddle demo.


Эта ошибка была исправлена в CU15 для SQL Server 2017 (и CU7 для SQL Server 2016 с пакетом обновления 2):

ИСПРАВЛЕНИЕ: Запрос к таблице с кластеризованным индексом хранилища столбцов и некластеризованным индексом хранилища строк может возвращать неверные результаты в SQL Server 2016 и 2017

Пол Уайт 9
источник
8

Это ошибка с SQL Server. Если столбец удаляется из таблицы с кластеризованным индексом хранилища столбцов, а затем добавляется новый столбец с тем же именем, он, похоже, использует старый, удаленный столбец для предиката. Вот MVCE:

Этот скрипт начинается со 10000строк с statusIdof 1и statusId2of 5- затем удаляет statusIDстолбец и переименовывается statusId2в statusId. Таким образом, в конце все строки должны иметь statusId5.

Но следующий запрос попадает в некластеризованный индекс ...

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 |
+-------+---------+------+-------+----------+

... тогда как этот получает доступ к columnstore и правильно возвращает 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 :

И для всех, кто столкнулся с этим, перестройка Clustered Columnstore Index решает проблему:

alter index cci_example on example rebuild

Восстановление CCI только исправляет любые существующие данные. Если новые записи добавляются, проблема снова возникает на этих записях; поэтому в настоящее время единственным известным исправлением для таблицы является ее полное воссоздание.

Uberzen1
источник
1
Не только проблема в том, что он использует старый для предиката. Другая странная вещь заключается в том, что он полностью разбивает остаточный предикат в разных столбцах, что and id2 = @id2должно гарантировать нулевые строки в любом случае, как @id2есть, nullно вы все равно получаете 2
Martin Smith,
RE: Ваша правка 2 выполняет REORGANIZE WITH (COMPRESS_ALL_ROW_GROUPS = ON);свою работу? Это очистит deltastore - проблема все еще возникает для новых строк, добавленных после этого?
Мартин Смит
Нет, похоже, точно такой же результат, к сожалению?
Uberzen1
-4

Исходя из планов, создается впечатление, что индекс Columnstore был создан с параметром SET ANSI_NULLS OFF. Таблицы и индексы сохраняют настройку, которая была при создании индекса. Вы можете проверить, создав дубликат индекса Columnstore, убедившись, что ANSI_NULLS включен, а затем либо сбросив оригинал, либо отключив его.

Но, если вы не обнаружили ошибку SQL Server, это единственный способ получить результаты.

Смеющийся Вергилий
источник
2
Вы уверены, что 1) нефильтрованные индексы могут поддерживать параметры ANSI_NULLS отдельно от базовой таблицы, и 2) что параметр ANSI_NULLS сеанса действительно может вызвать расхождения при создании таблицы с отключенным ANSI_NULLS?
Форрест
Я так и думал, но когда я записываю в сценарий определение CCI, у него нет заданных параметров, и если я создаю его с помощью SET ANSI_NULLS ON до определения индекса, результат будет таким же?
Uberzen1