【问题标题】:SQL Server : indexed views validating inserts in the base table incorrectlySQL Server:索引视图错误地验证基表中的插入
【发布时间】:2021-11-03 16:27:58
【问题描述】:

我相信 SQL Server 索引视图正在错误地验证基表中的插入。

为了模拟,考虑以下

创建表:

CREATE TABLE [dbo].[table_e]
(
    [id] [int] NOT NULL,
    [module] [varchar](50) NULL,
    [event] [varchar](50) NULL,
    [params] [nvarchar](max) NULL,

    CONSTRAINT [PK_table_e] 
        PRIMARY KEY CLUSTERED ([id] ASC)
                    WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, 
                          IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON,  
                          ALLOW_PAGE_LOCKS = ON, OPTIMIZE_FOR_SEQUENTIAL_KEY = OFF) ON [PRIMARY]
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]
GO

填充表格

INSERT INTO [dbo].[table_e] ([id], [module], [event], [params])
VALUES (1, 'ModuleB', 'EventT', N'[{"type":"AccountId","value":"AccountX"},{"type":"AccountId","value":"AccountZ"},{"type":"Balance","value":10},{"type":"Balance","value":10}]'),
(2, 'ModuleB', 'EventT', N'[{"type":"AccountId","value":"AccountY"},{"type":"AccountId","value":"AccountX"},{"type":"Balance","value":20}]'),
(3, 'ModuleP', 'EventA', N'[{"type":"AccountId","value":"AccountZ"},{"type":"AccountId","value":"AccountY"},{"type":"Hash","value":"SomeHash"}]')

检查是否一切正常

SELECT * 
FROM [dbo].[table_e] -- returning 3 out of 3

创建模式绑定视图

CREATE VIEW [dbo].[iv_test]
WITH SCHEMABINDING
AS
    SELECT 
        e.[id],
        CAST(JSON_VALUE(e.[params], '$[0].value') AS CHAR(66)) AS [AccountAddress_From],
        CAST(JSON_VALUE(e.[params], '$[1].value') AS CHAR(66)) AS [AccountAddress_To],
        CAST(JSON_VALUE(e.[params], '$[2].value') AS DECIMAL (36)) AS [Amount_Transferred],
        CAST(JSON_VALUE(e.[params], '$[3].value') AS DECIMAL (36)) AS [Amount_Fees]
    FROM 
        [dbo].[table_e] e 
    WHERE 
        e.[module] = 'ModuleB' AND e.[event] = 'EventT'
GO

检查是否一切正常

SELECT * 
FROM [dbo].[iv_test]     -- returning 2 out of 3

清空表格

DELETE FROM [table_e] --3 rows affected

通过创建聚集索引实现视图:

CREATE UNIQUE CLUSTERED INDEX [PK_iv_test] 
ON [dbo].[iv_test]([id] ASC)
         WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, 
               SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, 
               DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, 
               ALLOW_PAGE_LOCKS = ON, OPTIMIZE_FOR_SEQUENTIAL_KEY = OFF) ON [PRIMARY]
GO

现在尝试添加与之前相同的数据

INSERT INTO [dbo].[table_e] ([id], [module], [event], [params])
VALUES (1, 'ModuleB', 'EventT', N'[{"type":"AccountId","value":"AccountX"},{"type":"AccountId","value":"AccountZ"},{"type":"Balance","value":10},{"type":"Balance","value":10}]'),
(2, 'ModuleB', 'EventT', N'[{"type":"AccountId","value":"AccountY"},{"type":"AccountId","value":"AccountX"},{"type":"Balance","value":20}]'),
(3, 'ModuleP', 'EventA', N'[{"type":"AccountId","value":"AccountZ"},{"type":"AccountId","value":"AccountY"},{"type":"Hash","value":"SomeHash"}]')

结果:INSERT 不起作用。聚集索引的创建阻止了我的 INSERT 并出现此错误:

消息 8115,第 16 级,状态 6,第 105 行
将 nvarchar 转换为数字数据类型时出现算术溢出错误。 声明已终止。

这里发生了什么?

它看起来像是应用于索引视图的 where 子句,例如e.[module_id] = 'Balances' AND e.[event_id] = 'Transfer' 未应用。

因此,所有插入基表的数据都会根据视图 [iv_test] 检查,而在我看来,只有满足视图 [iv_test] 中指定的 WHERE 条件的插入数据才应根据格式检查视图 [iv_test]。

有趣的是:以下方法确实有效:

  • 首先插入 ID 为 1 和 2 的记录;
  • 然后创建聚集索引;
  • 然后插入ID为3的记录;

这当然不能解决我的问题,但很明显,在创建聚集索引后,索引视图有一些 WITH NOCHECK 约束处于活动状态。

欢迎提出意见

【问题讨论】:

    标签: sql-server indexing view schemabinding


    【解决方案1】:

    我有一个“解决方案”给你,但没有完整的解释。

    在您的索引视图定义中,将最后两个 cast 更改为 try_cast

    CREATE VIEW [dbo].[iv_test]
    WITH SCHEMABINDING
    AS
    SELECT 
        e.[id]
        ,CAST(JSON_VALUE(e.[params], '$[0].value') AS CHAR(66)) AS [AccountAddress_From]
        ,CAST(JSON_VALUE(e.[params], '$[1].value') AS CHAR(66)) AS [AccountAddress_To]
        ,TRY_CAST(JSON_VALUE(e.[params], '$[2].value') AS DECIMAL (36)) AS [Amount_Transferred]
        ,TRY_CAST(JSON_VALUE(e.[params], '$[3].value') AS DECIMAL (36)) AS [Amount_Fees]
    FROM [dbo].[table_e] e 
    WHERE e.[module] = 'ModuleB' AND e.[event] = 'EventT'
    GO
    

    我知道你在想什么:

    但是视图定义上的where 子句应该过滤掉cast 会失败的行,所以这些不应该被物化,所以物化应该成功

    是的,从逻辑上讲,我猜这是对的...这就是为什么在插入第一行(很高兴将其转换为十进制)之后创建索引,然后添加第三行时它起作用的原因。

    所以我想推论是,引擎在为多行数据创建过滤索引时执行的操作顺序与在表中插入一行时执行的操作顺序必须有所不同。具体来说,似乎引擎会评估数据的视图输出,然后然后在决定将什么放入索引时应用谓词。

    我敢打赌,Paul White 可以提供有关幕后事件的详细信息。

    【讨论】:

    • 是的,TRY_CAST 是一个有趣的选项。但是,由于值/值类型可以无限组合,我可能最终会得到一个无法管理的 CASE 构造(CASE WHEN TRY_CAST IS NULL ...)。因此,影响引擎评估顺序(不会产生不必要的副作用)将是我的首选。这可能需要一些额外的调查时间,但希望能提供更持久的解决方案。
    • @user158936 如果无法知道视图中的列类型是什么,那么显然您将无法创建尝试转换为任何可能失败的类型的视图转换。因此,从解析的 json 生成的所有视图列必须是 varcharsql_variant,在这种情况下,您可以使用 cast
    • 得到了意想不到的惊喜。如果我拆分插入并将它们按模块和事件分组,它可以正常工作......?!
    • INSERT INTO [dbo].[table_e] ([id], [module], [event], [params]) VALUES (1, 'ModuleB', 'EventT', N'[{ "type":"AccountId","value":"AccountX"},{"type":"AccountId","value":"AccountZ"},{"type":"Balance","value":10} ,{"type":"Balance","value":10}]'), (2, 'ModuleB', 'EventT', N'[{"type":"AccountId","value":"AccountY" },{"type":"AccountId","value":"AccountX"},{"type":"Balance","value":20}]')
    • INSERT INTO [dbo].[table_e] ([id], [module], [event], [params]) VALUES (3, 'ModuleP', 'EventA', N'[{ "type":"AccountId","value":"AccountZ"},{"type":"AccountId","value":"AccountY"},{"type":"Hash","value":"SomeHash "}]')
    猜你喜欢
    • 2010-10-27
    • 2019-05-26
    • 1970-01-01
    • 2012-03-01
    • 2011-02-16
    • 1970-01-01
    • 2012-02-18
    • 1970-01-01
    • 2011-07-16
    相关资源
    最近更新 更多