Проблемы с производительностью параметра табличного значения - PullRequest
11 голосов
/ 20 мая 2011

Я не знаю, связано ли это с тем, как я их использую, или с реализацией Microsoft, но параметры табличных значений SQL 2008 мучительно медленны.

Обычно, если мне нужно использовать TVP, это потому, что у меня много записей - в настоящее время они кажутся необычайно медленными для чего-то большего, чем наименьшее количество записей.

Я звоню им в .Net так:

// get the data
DataTable data = GetData();

com.CommandText = "sprocName"

// create the table-value parameter
var tvp = com.Parameters.AddWithValue("data", data);
tvp.SqlDbType = SqlDbType.Structured;

com.ExecuteNonQuery();

Я запустил профилировщик, чтобы понять, почему, и фактический оператор SQL выглядит примерно так:

declare @data table ...

insert into @data ( ... fields ... ) values ( ... values ... )
-- for each row
insert into @data ( ... fields ... ) values ( ... values ... )

sprocName(@data)

Это действительно очень медленный способ сделать это. Было бы намного быстрее, если бы он сделал это вместо:

insert into @data ( ... fields ... ) 
values ( ... values ... ),
       ( ... values ... ),
       -- for each row
       ( ... values ... )

Я не уверен, почему он не использует более новый, более быстрый синтаксис. Или даже все, что он делает под капотом с SqlBulkCopy.

Новый синтаксис был добавлен в SQL 2008, но также и TVP (я думаю).

Есть ли какая-нибудь опция, чтобы заставить это сделать это? Или что-то, чего мне не хватает?

Ответы [ 2 ]

11 голосов
/ 24 февраля 2015

Если TVP "заметно медленнее", чем другие опции, то, скорее всего, вы не реализуете их правильно.

  1. Вам не следует использовать DataTable, если ваше приложение не использует его за пределамиотправки значений в TVP.Использование интерфейса IEnumerable<SqlDataRecord> быстрее и использует меньше памяти, поскольку вы не дублируете коллекцию в памяти только для отправки ее в БД.Я задокументировал это в следующих местах:
  2. Вы не должны использовать AddWithValue для SqlParameter, хотя это, скорее всего, не проблема с производительностью.Но все же, это должно быть:

    SqlParameter tvp = com.Parameters.Add("data", SqlDbType.Structured);
    tvp.Value = MethodThatReturnsIEnumerable<SqlDataRecord>(MyCollection);
    
  3. TVP являются табличными переменными и поэтому не поддерживают статистику.Это означает, что они сообщают оптимизатору запросов только одну строку.Итак, в вашем proc, либо:
    • Используйте перекомпиляцию на уровне операторов для любых запросов с использованием TVP для чего-либо, кроме простого SELECT: OPTION (RECOMPILE)
    • Создайте локальную временную таблицу (т.е. одну#) и скопируйте содержимое TVP во временную таблицу
    • Вы можете попробовать добавить кластерный первичный ключ в определяемый пользователем тип таблицы
    • Если вы используете SQL Server 2014 или новее,Вы можете попробовать использовать In-Memory OLTP / оптимизированные для памяти таблицы.Смотрите: Более быстрая временная таблица и табличная переменная с использованием оптимизации памяти

Относительно того, почему вы видите:

insert into @data ( ... fields ... ) values ( ... values ... )
-- for each row
insert into @data ( ... fields ... ) values ( ... values ... )

вместоиз:

insert into @data ( ... fields ... ) 
values ( ... values ... ),
       ( ... values ... ),

ЕСЛИ это действительно то, что происходит, то:

  • Если вставки выполняются внутри транзакции, то реальной разницы в производительности нет
  • Более новый синтаксис списка значений (т. Е. VALUES (row1), (row2), (row3)) ограничен чем-то вроде 1000 строк и, следовательно, не является приемлемым вариантом для TVP, у которых нет этого ограничения.ОДНАКО, это вряд ли является причиной использования отдельных вставок, учитывая, что нет никаких ограничений при выполнении INSERT INTO @data (fields) SELECT tab.[col] FROM (VALUES (), (), ...) tab([col]), что я задокументировал здесь: Максимальное количество строк для конструктора табличных значений .Вместо этого ...
  • Причина, скорее всего, в том, что выполнение отдельных вставок позволяет передавать значения из кода приложения в SQL Server:
    1. с использованием итератора (то есть IEnumerable<SqlDataRecord>, отмеченного в #1 выше), код приложения отправляет каждую строку, когда она возвращается из метода, и
    2. создает список VALUES (), (), ..., даже если используется подход INSERT INTO ... SELECT FROM (VALUES ...) (который не ограничивается 1000 строк),для этого все равно потребуется создать список весь VALUES перед отправкой любых данных в SQL Server.Если данных много, для создания супердлинной строки потребуется больше времени, и при этом потребуется гораздо больше памяти.

Пожалуйста, такжесм. этот технический документ от консультативной группы по SQL Server: Максимальная пропускная способность с TVP

4 голосов
/ 20 мая 2011

См. Раздел «Табличные параметры против операций BULK INSERT»http://msdn.microsoft.com/en-us/library/bb510489.aspx

Цитата: "... табличные параметры хорошо работают для вставки менее 1000 строк."

В нем также есть таблица, показывающая, какую технологию использовать в зависимости от скоростиопераций вставки.

Надеюсь, это поможет, удачи.

...