Почему TVP должны быть READONLY, и почему параметры других типов не могут быть READONLY

19

Согласно этому блогу параметры функции или хранимой процедуры по существу передаются по значению, если они не являются OUTPUTпараметрами, и по существу рассматриваются как более безопасная версия передачи по ссылке, если они являются OUTPUTпараметрами.

Сначала я подумал, что целью заставить TVP быть объявленным READONLYбыло ясно дать понять разработчикам, что TVP нельзя использовать в качестве OUTPUTпараметра, но должно быть что-то еще, потому что мы не можем объявить non-TVP как READONLY. Например, следующее не удается:

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

Сообщение 346, уровень 15, состояние 1, проверка процедуры
Параметр "@a" не может быть объявлен READONLY, поскольку он не является табличным параметром.

  1. Поскольку статистика не хранится на TVP, что является причиной предотвращения операций DML?
  2. Это связано с тем, что TVP по OUTPUTкаким-то причинам не желает быть параметрами?
Erik
источник

Ответы:

19

Похоже, что объяснение связано с сочетанием: а) подробностей из связанного блога, которые не были упомянуты в этом вопросе, б) прагматики ТВП, подходящей к тому, как параметры всегда передавались внутрь и наружу, в) и характера табличных переменных.

  1. Недостающая деталь, содержащаяся в связанном посте блога, в точности описывает, как переменные передаются в и из хранимых процедур и функций (что относится к формулировке в вопросе «более безопасной версии передачи по ссылке, если они являются параметрами OUTPUT») :

    TSQL использует семантику копирования / копирования для передачи параметров хранимым процедурам и функциям ....

    ... когда хранимый процесс завершает выполнение (не нажимая на ошибку), создается копия, которая обновляет переданный параметр с учетом любых изменений, внесенных в него в хранимом процессе.

    Реальное преимущество этого подхода в случае ошибки. Если во время выполнения хранимой процедуры возникает ошибка, любые изменения, внесенные в параметры, не будут передаваться вызывающей стороне.

    Если ключевое слово OUTPUT отсутствует, копирование не производится.

    Нижняя строка:
    параметры хранимых процедур никогда не отражают частичное выполнение сохраненного процесса, если он обнаружил ошибку.

    Первая часть этой головоломки состоит в том, что параметры всегда передаются «по значению». И только когда параметр помечен как OUTPUT и хранимая процедура успешно завершена, текущее значение действительно отправляется обратно. Если OUTPUTзначения были действительно переданы «по ссылке», то указатель на место в памяти этой переменной будет передаваемой вещью, а не само значение. И если вы передадите указатель (т. Е. Адрес памяти), то любые сделанные изменения будут немедленно отражены, даже если следующая строка хранимой процедуры вызывает ошибку и прерывает выполнение.

    Подводя итог, часть 1: значения переменных всегда копируются; на них не ссылается их адрес памяти.

  2. С учетом части 1 политика постоянного копирования значений переменных может привести к проблемам с ресурсами, когда передаваемая переменная достаточно велика. Я не проверял , чтобы видеть , как типы BLOB обрабатываются ( VARCHAR(MAX), NVARCHAR(MAX), VARBINARY(MAX), XML, и те , которые не должны больше использоваться: TEXT, NTEXTи IMAGE), но с уверенностью можно сказать , что любая таблица данных, передаваемых в может быть довольно большим. Для тех, кто разрабатывает функцию TVP, имело бы смысл иметь истинную возможность «передачи по ссылке», чтобы их новая классная функция не разрушила здоровое количество систем (т.е. хотел бы иметь более масштабируемый подход). Как вы можете видеть в документации , вот что они сделали:

    Transact-SQL передает табличные параметры в подпрограммы по ссылке, чтобы избежать копирования входных данных.

    Кроме того, эта проблема управления памятью не была новой концепцией, поскольку ее можно найти в API SQLCLR, который был представлен в SQL Server 2005 (TVP были введены в SQL Server 2008). При передаче NVARCHARи VARBINARYданных в код SQLCLR (т. Е. Входные параметры для методов .NET в сборке SQLCLR) у вас есть возможность перейти к подходу "по значению", используя один SqlStringили SqlBinaryсоответственно, или вы можете перейти к "по ссылке «подход с использованием либо SqlCharsили SqlBytesсоответственно. SqlCharsИ SqlBytesтипы позволяют полностью стриминг данных в .NET CLR, что вы можете тянуть небольшие куски больших значений , в отличие от копирования всего 200 МБ (до 2 Гб, справа) значения.

    Подводя итог, можно сказать, что часть 2: TVP по своей природе будут иметь склонность потреблять много памяти (и, следовательно, ухудшать производительность), если останутся в рамках модели «всегда копировать значение». Следовательно, TVP делают «передачу по ссылке».

  3. Последняя часть - то, почему Часть 2 имеет значение: почему передача TVP действительно «по ссылке» вместо того, чтобы делать копию этого, что-то меняет. И на это отвечает цель разработки, которая является основой для Части 1: Хранимые процедуры, которые не завершаются успешно, никоим образом не должны изменять любые входные параметры, независимо от того, помечены они как OUTPUTили нет. Разрешение операций DML будет иметь непосредственное влияние на значение TVP, поскольку оно существует в вызывающем контексте (поскольку передача по ссылке означает, что вы меняете то, что было передано, а не копию того, что было передано).

    Теперь кто-то где-то в этот момент, вероятно, разговаривает со своим монитором и говорит: «Ну, просто встроите автоматическое средство для отката любых изменений, внесенных в параметры TVP, если они были переданы в хранимую процедуру. Дух. Проблема решена». Не так быстро. Вот где возникает природа табличных переменных: изменения, вносимые в переменные таблицы, не связаны транзакциями! Так что нет возможности откатить изменения обратно. И на самом деле, это трюк, используемый для сохранения информации, сгенерированной в транзакции, если требуется откат :-).

    Подводя итог: Часть 3. Табличные переменные не допускают «отмены» внесенных в них изменений в случае ошибки, которая приводит к прерыванию хранимой процедуры. И это нарушает цель разработки, состоящую в том, чтобы параметры никогда не отражали частичное выполнение (часть 1).

Ergo:READONLY ключевое слово не требуется , чтобы предотвратить операции DML на TVPs , так как они являются Табличными переменными, которые на самом деле прошли «по ссылке», и , следовательно , любые изменения в них будут немедленно отображаться, даже если хранимая процедура обнаруживает ошибку, и нет Другой способ предотвратить это.

Кроме того, параметры других типов данных не могут использоваться, READONLYпотому что они уже являются копиями того, что было передано, и поэтому не защитят ничего, что еще не защищено. Это и то, как параметры других типов данных были предназначены для чтения-записи, так что, вероятно, было бы еще больше работы по изменению этого API, чтобы теперь включить концепцию только для чтения.

Соломон Руцкий
источник
Очень подробное объяснение. Благодарю. То есть нет способа изменить переданную табличную переменную (пользовательскую TYPEпеременную TVP или a DECLARE x as TABLE (...)) с помощью хранимой процедуры? Могу ли я сделать это, хотя и с большим объемом памяти, с помощью функции вместо, set @tvp = myfunction(@tvp)если значение моей функции RETURNS- это таблица с тем же DDL, что и тип TVP?
mpag
@mpag Спасибо. TVP - это переменная таблицы, различий нет. Вы не передаете тип, вы передаете табличную переменную, созданную из типа или из явного объявления схемы. Кроме того, вы не можете SETтабличной переменной, по крайней мере, я не знаю. И даже если бы вы могли: а) вы не можете получить доступ к результирующему набору через =оператора, и б) TVP все еще помечен как READONLY, поэтому его установка нарушит это. Просто выгрузите содержимое во временную таблицу или другую переменную таблицы, которую вы создаете в proc.
Соломон Руцкий
Еще раз спасибо. Я решил по существу использовать подход временного стола.
mpag
5

Ответ сообщества Wiki, созданный на основе комментария к вопросу Мартина Смита

Для этого есть активный элемент Connect (предоставленный Erland Sommarskog):

Ослабьте ограничение, что параметры таблицы должны быть доступны только для чтения, когда SP вызывают друг друга

Единственный ответ Microsoft до сих пор говорит (выделение добавлено):

Спасибо за отзыв об этом. Мы получили аналогичные отзывы от большого числа клиентов. Разрешение табличных параметров для чтения / записи требует довольно много работы на стороне SQL Engine, а также на клиентских протоколах. Из-за нехватки времени / ресурсов, а также других приоритетов мы не сможем приступить к этой работе в рамках выпуска SQL Server 2008. Тем не менее, мы исследовали эту проблему и твердо находимся в нашем радаре для решения этой проблемы в рамках следующего выпуска SQL Server. Мы ценим и приветствуем отзывы здесь.

Срини Ачарья
Старший менеджер программ
SQL Server Relational Engine

Пол Уайт
источник