为什么 TVP 必须是 READONLY,为什么其他类型的参数不能是 READONLY

Eri*_*rik 21 sql-server parameter table-variable table-valued-parameters

根据此博客,函数或存储过程的参数如果不是OUTPUT参数,则本质上是按值传递的,如果它们是参数,则基本上被视为按引用传递的更安全版本OUTPUT

起初我认为强制声明 TVP 的目的READONLY是向开发人员明确表示 TVP 不能用作OUTPUT参数,但必须有更多的进展,因为我们不能将非 TVP 声明为READONLY. 例如以下失败:

create procedure [dbo].[test]
@a int readonly
as
    select @a
Run Code Online (Sandbox Code Playgroud)

消息 346,级别 15,状态 1,过程测试
参数“@a”不能声明为 READONLY,因为它不是表值参数。

  1. 由于统计信息未存储在 TVP 上,因此阻止 DML 操作的基本原理是什么?
  2. 是否与OUTPUT出于某种原因不希望 TVP 成为参数有关?

Sol*_*zky 20

该解释似乎与以下组合有关:a)此问题中未提及的链接博客中的细节,b)符合参数传入和传出方式的 TVP 的语用学,c)和性质表变量。

  1. 链接的博客文章中包含的缺失细节正是变量如何传入和传出存储过程和函数(这与“如果它们是 OUTPUT 参数的更安全版本的引用传递”问题中的措辞有关) :

    TSQL 使用 copy-in/copy-out 语义将参数传递给存储过程和函数......

    ...当存储过程完成执行(没有遇到错误)时,会进行复制,以使用存储过程中对其进行的任何更改来更新传入的参数。

    这种方法的真正好处是在错误情况下。如果在存储过程的执行过程中发生错误,对参数所做的任何更改都不会传播回调用者。

    如果 OUTPUT 关键字不存在,则不会进行复制。

    底线:
    如果存储过程遇到错误,存储过程的参数永远不会反映存储过程的部分执行。

    这个谜题的第 1 部分是参数总是“按值”传递。并且,只有当参数被标记为OUTPUT 并且存储过程成功完成时,当前值才被实际发回。如果OUTPUT值确实是“通过引用”传递的,那么指向该变量在内存中的位置的指针将是传递的对象,而不是值本身。如果您确实传入了指针(即内存地址),那么所做的任何更改都会立即反映出来,即使存储过程的下一行导致错误并中止执行。

    总结第 1 部分:始终复制变量值;它们不是由它们的内存地址引用的。

  2. 考虑到第 1 部分,当传入的变量非常大时,始终复制变量值的策略可能会导致资源问题。我还没有测试过如何处理 blob 类型(VARCHAR(MAX)NVARCHAR(MAX)VARBINARY(MAX)XML以及那些不应再使用的类型:TEXTNTEXT、 和IMAGE),但可以肯定地说,任何传入的数据表都可能非常大。对于那些开发 TVP 功能的人来说,希望拥有真正的“传递引用”能力以防止他们的酷新功能破坏健康数量的系统(即想要更具可扩展性的方法)是有意义的。正如您在文档中看到的那样他们做了什么:

    Transact-SQL 通过引用将表值参数传递给例程以避免复制输入数据。

    此外,这种内存管理问题并不是一个新概念,因为它可以在 SQL Server 2005 中引入的 SQLCLR API 中找到(TVP 是在 SQL Server 2008 中引入的)。当传递NVARCHARVARBINARY数据转换成SQLCLR代码(即输入参数上的SQLCLR组件内的.NET方法),则必须通过使用去与“按值”的方法的选项SqlStringSqlBinary分别,也可以使用“通过引用去" 方法分别使用SqlCharsSqlBytes。该SqlCharsSqlBytes类型允许数据的完全流进.NET CLR,这样你可以拉大的值的小块,而不是复制整个200 MB(最大2 GB,右)值。

    总结第 2 部分:如果保持在“始终复制值”模型中,就其本质而言,TVP 将倾向于消耗大量内存(从而降低性能)。因此,TVP 执行了真正的“通过引用传递”。

  3. 最后一部分是为什么第 2 部分很重要:为什么真正“通过引用”传入 TVP 而不是复制它会改变任何事情。这由作为第 1 部分基础的设计目标回答:未成功完成的存储过程不应以任何方式更改任何输入参数,无论它们是否标记为OUTPUT。允许 DML 操作将对 TVP 的值产生直接影响,因为它存在于调用上下文中(因为通过引用传递意味着您正在更改传入的内容,而不是传入内容的副本)。

    现在,某个地方的某个人此时可能正在对他们的监视器说:“好吧,只需构建一个自动魔术工具,用于回滚对 TVP 参数所做的任何更改(如果有任何更改),如果有任何更改被传递到存储过程。呃。问题解决了。” 没那么快。这就是表变量的本质所在:对表变量所做的更改不受事务的约束!所以没有办法回滚更改。事实上,如果需要回滚,这是一个用于保存事务中生成的信息的技巧:-)。

    总结第 3 部分:在导致存储过程中止的错误的情况下,表变量不允许“撤消”对它们所做的更改。这违反了参数从不反映部分执行的设计目标(第 1 部分)。

人机工程学:READONLY需要的关键字,以防止对台湾居民入境许可证,因为他们是被“引用”实际传递表变量DML操作,因此对他们的任何修改会立即反映,即使存储过程遇到错误,并没有防止这种情况的其他方法。

此外,其他数据类型的参数不能使用,READONLY因为它们已经是传入内容的副本,因此它不会保护任何尚未受到保护的东西。那,以及其他数据类型的参数的工作方式旨在是读写的,因此更改该 API 以现在包含只读概念可能需要更多的工作。


Pau*_*ite 7

Martin Smith对问题的评论生成的社区 Wiki 答案

有一个活跃的 Connect 项目(由 Erland Sommarskog 提交):

放宽限制,SP 相互调用时表参数必须是只读的

到目前为止,微软唯一的回应是(强调):

感谢您对此的反馈。我们收到了大量客户的类似反馈。允许读取/写入表值参数涉及 SQL 引擎端以及客户端协议的大量工作。由于时间/资源限制以及其他优先事项,我们将无法将这项工作作为 SQL Server 2008 版本的一部分进行。但是,我们已经调查了这个问题,并将其作为下一版 SQL Server 的一部分予以解决。我们感谢并欢迎这里的反馈。

Srini Acharya
高级项目经理
SQL Server 关系引擎