SQL Server 在通过游标更新具有 INSTEAD OF UPDATE 触发器的视图时使用索引扫描而不是搜索

phe*_*ehr 6 trigger performance sql-server cursors sql-server-2014

我有一个 3rd 方应用程序,它使用一个游标来更新我的 SQL Server 2014 表中的行。我无法修改此代码,因此设计了一种方案,将光标指向具有INSTEAD OF UPDATE触发器的视图,我可以在其中拦截并完全控制更新。我很清楚游标是邪恶的,但我不得不使用它们,因为我无法修改源程序。

UPDATE ... WHERE CURRENT OF语句执行,它执行聚集索引扫描,而不是寻求以定位光标当前所指向的记录。我无法确定优化器在可能进行搜索时为什么要进行扫描。我相信它是导致问题的光标 + 视图 + 而不是更新触发器的组合,因为如果我从我的测试中删除这 3 个变量中的任何一个,索引查找就会被正确使用。

请注意,表中有多少行并不重要;优化器总是根据执行计划使用扫描。我什至删除了触发器中的所有逻辑以进一步简化测试。

下面是一些简单的代码来重现这个问题:

CREATE TABLE [dbo].[Person](
  [Id] [int] IDENTITY(1,1) NOT NULL,
  [Name] [varchar](40) NULL,
  CONSTRAINT [PK_Person_Id] PRIMARY KEY CLUSTERED ( [Id] ASC )
  WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF,
        ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY])
  ON [PRIMARY]
GO

CREATE VIEW [dbo].[vPerson]
AS SELECT Id, Name FROM dbo.Person
GO

CREATE TRIGGER [dbo].[InsteadOfUpdate_vPerson] ON [dbo].[vPerson]
INSTEAD OF UPDATE
AS
BEGIN
  SET NOCOUNT ON
END
GO

INSERT INTO Person VALUES('John Doe');
GO

--This will use a seek
UPDATE dbo.vPerson
SET Name = 'Jane Doe'
WHERE Id = 1
GO

--This will not use a seek
DECLARE c CURSOR FOR
SELECT * FROM dbo.vPerson
WHERE Id = 1
FOR UPDATE

OPEN c
FETCH NEXT FROM c

UPDATE dbo.vPerson
SET Name = 'Jane Doe'
WHERE CURRENT OF c
CLOSE c
DEALLOCATE c
GO
Run Code Online (Sandbox Code Playgroud)

任何有关尝试的帮助或建议将不胜感激。

Pau*_*ite 9

这看起来像是一个疏忽。

当使用执行更新WHERE CURRENT OF并且目标视图(模式绑定与否)具有 T-SQL 而不是更新触发器时,优化器无法生成应用样式索引循环连接,无论行数或任何其他考虑因素:

执行计划

这显示了一个表中几乎有 20,000 行的示例(从 AdventureWorks 的 Person 表中复制,碰巧)。

连接谓词“卡住”在嵌套循环连接运算符本身上,而不是被推入内侧以产生搜索:

加入属性

由于您无法更改代码,您应该通过正常的 Microsoft 支持渠道将此报告为错误。您也可以在 Connect 上报告错误,但通过该路线获得快速响应或修复的机会要低得多。


只是为了兴趣,您所追求的计划可以使用 API 游标定位更新(内部最类似的操作):

DECLARE 
    @cur integer,
    @scrollopt integer = 2 | 8192 | 32768 | 131072, -- DYNAMIC | AUTO_FETCH | CHECK_ACCEPTED_TYPES | DYNAMIC_ACCEPTABLE
    @ccopt integer = 2 | 32768 | 131072, -- SCROLL_LOCKS | CHECK_ACCEPTED_OPTS | SCROLL_LOCKS_ACCEPTABLE
    @rowcount integer = 1;

-- Open the cursor
EXECUTE sys.sp_cursoropen
    @cur OUTPUT,
    N'
    SELECT * FROM dbo.vPerson WHERE Id = 1;
    ',
    @scrollopt OUTPUT,
    @ccopt OUTPUT,
    @rowcount OUTPUT;

-- Request a positioned update
EXECUTE sys.sp_cursor
    @cur,
    1, -- UPDATE
    1, -- row number in buffer
    'dbo.vPerson', -- table (unambiguous in this case)
    'Name=''Banana'''; -- new value

-- Close    
EXECUTE sys.sp_cursorclose -1;
Run Code Online (Sandbox Code Playgroud)

执行计划是:

执行计划

注意 Person 上的索引查找(谓词不是“卡住”):

求属性

这不是您的解决方法,因为您无法更改源查询。没有办法暗示或计划指导您解决问题;优化器根本无法生成您在特定情况下期望的搜索计划。尝试例如FORCESEEK提示只会导致错误消息,指出优化器无法生成执行计划。