Sql-Server

為什麼 TVP 必須是 READONLY,其他類型的參數為什麼不能是 READONLY

  • March 17, 2021

根據此部落格,函式或儲存過程的參數如果不是參數,則本質上是按值傳遞,如果它們是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 成為參數有關?

解釋似乎與以下組合相關:a)連結部落格中未在此問題中提及的詳細資訊,b)TVP 的語用學適合參數始終如何傳入和傳出,c)和性質表變數。

  1. 連結的部落格文章中缺少的細節正是變數如何傳入和傳出儲存過程和函式(這與“如果它​​們是 OUTPUT 參數,則傳遞引用的更安全版本”的問題中的措辭有關) :

TSQL 使用複制輸入/複製輸出語義將參數傳遞給儲存過程和函式……

…當儲存過程完成執行(沒有遇到錯誤)時,會進行複制,以使用在儲存過程中對其進行的任何更改來更新傳入的參數。

這種方法的真正好處在於錯誤情況。如果在儲存過程的執行過程中發生錯誤,對參數所做的任何更改都不會傳播回呼叫者。

如果 OUTPUT 關鍵字不存在,則不會進行複制。

底線:

如果遇到錯誤,儲存過程的參數永遠不會反映儲存過程的部分執行。

這個難題的第 1 部分是參數總是“按值”傳遞。並且,只有當參數被標記為OUTPUT 並且儲存過程成功完成時,目前值才真正被發回。如果OUTPUT值真的是“通過引用”傳遞的,那麼指向該變數記憶體位置的指針將是傳遞的東西,而不是值本身。如果您確實傳入了指針(即記憶體地址),那麼所做的任何更改都會立即反映,即使儲存過程的下一行導致錯誤併中止執行。

總結第 1 部分:始終複製變數值;它們沒有被它們的記憶體地址引用。 2. 考慮到第 1 部分,當傳入的變數非常大時,始終複製變數值的策略可能會導致資源問題。我還沒有測試過如何處理 blob 類型(VARCHAR(MAX)NVARCHAR(MAX)VARBINARY(MAX)XML以及那些不應再使用的類型:TEXTNTEXTIMAGE),但可以肯定地說,傳入的任何數據表都可能非常大。對於那些開發 TVP 功能的人來說,希望真正的“通過引用”能力來防止他們的酷新功能破壞健康數量的系統(即想要一種更具可擴展性的方法)是有意義的。正如您在文件中看到的那樣,他們所做的就是:

Transact-SQL 通過引用將表值參數傳遞給常式,以避免複製輸入數據。

此外,這種記憶體管理問題並不是一個新概念,因為它可以在 SQL Server 2005 中引入的 SQLCLR API 中找到(TVP 是在 SQL Server 2008 中引入的)。在將數據傳遞NVARCHARVARBINARYSQLCLR 程式碼(即 SQLCLR 程序集中的 .NET 方法上的輸入參數)時,您可以選擇使用“按值”方法通過使用其中一個SqlStringSqlBinary分別使用,或者您可以使用“按引用” " 分別使用SqlCharsSqlBytes分別使用的方法。SqlCharsand類型允許將SqlBytes數據完全流式傳輸到 .NET CLR 中,這樣您就可以提取小塊的大值,而不是複制整個 200 MB(最多 2 GB,對)的值。

總結第 2 部分:TVP 就其本質而言,如果停留在“始終複製價值”模型中,就會傾向於消耗大量記憶體(從而降低性能)。因此,TVP 做了真正的“參考傳遞”。 3. 最後一點是為什麼第 2 部分很重要:為什麼真正“通過引用”傳遞 TVP 而不是複制它會改變任何事情。第 1 部分的基礎設計目標回答了這一點:未成功完成的儲存過程不應以任何方式更改任何輸入參數,無論它們是否標記為OUTPUT。允許 DML 操作將對 TVP 的值產生直接影響,因為它存在於呼叫上下文中(因為通過引用傳遞意味著您正在更改傳入的內容,而不是傳入內容的副本)。

現在,某個地方的某個人此時可能正在對他們的監視器說:“好吧,只需建構一個自動設備,用於回滾對 TVP 參數所做的任何更改(如果有任何更改被傳遞到儲存過程)。嗯。問題解決了。” 沒那麼快。這就是表變數的本質所在:對錶變數所做的更改不受事務的約束!所以沒有辦法回滾更改。事實上,如果需要回滾,這是一種用於保存事務中生成的資訊的技巧:-)。

總結第 3 部分:表變數不允許在導致儲存過程中止的錯誤的情況下“撤消”對它們所做的更改。這違反了讓參數從不反映部分執行的設計目標(第 1 部分)。

**Ergo:**需要該READONLY關鍵字來防止對 TVP 的 DML 操作,因為它們實際上是“通過引用”傳遞的表變數,因此對它們的任何修改都會立即反映,即使儲存過程遇到錯誤,也沒有防止這種情況的其他方法。

此外,其他數據類型的參數不能使用READONLY,因為它們已經是傳入內容的副本,因此它不會保護任何尚未受保護的內容。那個,以及其他數據類型的參數的工作方式是為了讀寫,所以改變這個 API 以包含一個只讀概念可能會做更多的工作。

引用自:https://dba.stackexchange.com/questions/120756