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


19

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

最初,我认为强制声明TVP的目的READONLY是向开发人员明确表示不能将TVP用作OUTPUT参数,但由于我们无法将非TVP声明为,因此还必须继续进行下去READONLY。例如,以下失败:

create procedure [dbo].[test]
@a int readonly
as
    select @a

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

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

Answers:


19

解释似乎与以下各项有关:a)链接博客中未在此问题中提及的细节,b)TVP的实用性适合始终如何传入和传出参数,c)和性质表变量。

  1. 链接的博客文章中缺少的详细信息正是变量如何传入和传出存储过程和函数(这与“如果引用参数是输出的更安全版本”问题中的措词有关) :

    TSQL使用复制/复制语义将参数传递给存储过程和函数。

    ...当存储的proc完成执行(不会遇到错误)时,将执行复制操作,并使用存储proc中对其进行的任何更改来更新传入的参数。

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

    如果不存在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(SQL Server 2008中引入TVP)中找到它。将数据传递到SQLCLR代码(即SQLCLR程序集内.NET方法上的输入参数)NVARCHAR并将VARBINARY数据传递到SQLCLR代码时,可以选择分别使用“按值”方法SqlString或使用SqlBinary“按引用”方法,也可以选择“按引用” 分别使用“ SqlChars或”方法SqlBytes。该SqlCharsSqlBytes类型允许数据的完全流进.NET CLR,这样你可以拉大的值的小块,而不是复制整个200 MB(最大2 GB,右)值。

    总结第二部分:TVP从本质上讲,如果停留在“始终复制价值”模型中,则倾向于消耗大量内存(从而降低性能)。因此,TVP做真正的“通过引用传递”。

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

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

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

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

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


非常详细的解释。谢谢。因此,没有办法用存储过程来修改传递的表变量(用户TVP TYPE变量还是DECLARE x as TABLE (...))?set @tvp = myfunction(@tvp)如果我的函数的RETURNS值是与TVP类型具有相同DDL的表,是否可以使用函数代替函数(尽管占用的内存更大)?
mpag

@mpag谢谢。TVP 表变量,没有区别。您不传递类型,而是传递从类型或从显式模式声明创建的表变量。另外,您不能SET使用表变量,至少我不知道。即使可以:a)您无法通过=操作员访问结果集,并且b)TVP仍标记为READONLY,因此设置它会违反该结果。只需将内容转储到临时表或您在proc中创建的另一个表变量中即可。
所罗门·鲁兹基'18

再次感谢。我决定本质上使用临时表方法。
mpag

5

社区Wiki答案来自Martin Smith对问题的评论

为此有一个活动的Connect项(由Erland Sommarskog提交):

放宽限制,即当SP相互调用时,表参数必须为只读

到目前为止,Microsoft唯一的回答是(强调):

感谢您对此的反馈。我们已经收到了大量客户的类似反馈。允许读取或写入表值参数需要在SQL Engine端以及客户端协议上进行大量工作。由于时间/资源的限制以及其他优先级,我们将无法在SQL Server 2008版本中进行这项工作。但是,我们已经研究了此问题,并将其牢牢地放在了雷达中,以便作为SQL Server下一版本的一部分来解决。我们感谢并欢迎此处的反馈。

Srini Acharya
高级程序经理
SQL Server关系引擎

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.