Переполнение размера строки слияния в SQL Server - «Невозможно создать строку размера ...»

8

Целевая таблица, с которой я пытаюсь объединить данные, имеет ~ 660 столбцов. Код для слияния:

MERGE TBL_BM_HSD_SUBJECT_AN_1 AS targetTable
USING
        (                
SELECT * 
FROM TBL_BM_HSD_SUBJECT_AN_1_STAGING
WHERE [ibi_bulk_id] in (20150520141627106) and  id in(101659113)
    ) AS sourceTable 
ON (...)
WHEN MATCHED AND ((targetTable.[sampletime] <= sourceTable.[sampletime]))
        THEN UPDATE SET ...
WHEN NOT MATCHED 
        THEN INSERT (...)
    VALUES (...)

Первый раз, когда я запустил это (то есть, когда таблица пуста), это привело к успеху, и вставил одну строку.

Во второй раз, когда я запустил это с тем же набором данных, была возвращена ошибка:
невозможно создать строку с размером 8410, который превышает максимально допустимый максимальный размер строки 8060.

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

Итак, я попробовал две вещи (и это удалось!):

  • Удаление раздела «ЕСЛИ НЕ СОГЛАСОВАНО» из оператора слияния
  • Запуск оператора обновления с той же строкой, которую я пытался объединить

Почему обновление с использованием слияния не удается, в то время как вставка делает, и прямое обновление также делает?

ОБНОВИТЬ:

Удалось найти фактический размер строки - 4978. Я создал новую таблицу, в которой есть только эта строка, и нашел размер строки следующим образом: введите описание изображения здесь

И я до сих пор не вижу что-то, превышающее допустимый предел.

ОБНОВЛЕНИЕ (2):

Полное воспроизведение

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

Пробовал это на нескольких серверах, начиная с версии 2012 и одного с 2008 года, и смог полностью воспроизвести на всех них.

Йоси Дахари
источник

Ответы:

10

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

Во-первых, спасибо за сценарий воспроизведения.

Проблема не в том, что SQL Server не может вставить или обновить конкретную видимую пользователю строку. Как вы заметили, строка, уже вставленная в таблицу, безусловно, не может быть слишком большой для обработки SQL Server.

Проблема возникает из-за того, что реализация SQL Server MERGEдобавляет вычисленную информацию (в виде дополнительных столбцов) на промежуточных этапах плана выполнения. Эта дополнительная информация необходима по техническим причинам, чтобы отслеживать, должна ли каждая строка приводить к вставке, обновлению или удалению; а также связано с тем, как SQL Server обычно предотвращает временные нарушения ключей при изменениях индексов.

Механизм хранения SQL Server требует, чтобы индексы были уникальными (внутренне, включая любой скрытый Uniquifier) ​​всегда - при обработке каждой строки - а не в начале и в конце полной транзакции. В более сложных MERGEсценариях это требует разделения (преобразование обновления в отдельное удаление и вставку), сортировки и необязательного свертывания (превращение смежных вставок и обновлений для одного и того же ключа в обновление). Более подробная информация .

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

В простых MERGE запросах с подходящими уникальными индексами и прямыми отношениями между исходными и целевыми строками (обычно совпадающими с использованием ONпредложения, содержащего все ключевые столбцы) оптимизатор запросов может упростить большую часть общей логики, что приводит к сравнительно простым планам, которые делают не требует, чтобы Split-Sort-Collapse или Segment-Sequence Project проверяли, что целевые строки касаются только один раз.

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

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

MERGE MERGE_REPRO_TARGET AS targetTable
USING
(
    SELECT * FROM 
    (
        SELECT 
            *, 
            ROW_NUMBER() OVER (
                PARTITION BY ww,id, tenant 
                ORDER BY 
                (
                    SELECT COUNT(1) 
                    FROM MERGE_REPRO_SOURCE AS targetTable
                    WHERE 
                        targetTable.[ibi_bulk_id] = sourceTable.[ibi_bulk_id] 
                        AND targetTable.[ibi_row_id] <> sourceTable.[ibi_row_id] 
                        AND 
                        (
                            (targetTable.[ww] = sourceTable.[ww]) 
                            AND (targetTable.[id] = sourceTable.[id]) 
                            AND (targetTable.[tenant] = sourceTable.[tenant])
                        ) 
                        AND NOT ((targetTable.[sampletime] <= sourceTable.[sampletime]))
                ),
                sourceTable.ibi_row_id DESC
            ) AS idx
        FROM MERGE_REPRO_SOURCE sourceTable 
        WHERE [ibi_bulk_id] in (20150803110418887)
    ) AS bulkData
    where idx = 1
) AS sourceTable 
ON 
    (targetTable.[ww] = sourceTable.[ww]) 
    AND (targetTable.[id] = sourceTable.[id]) 
    AND (targetTable.[tenant] = sourceTable.[tenant])
...

Это приводит ко многим дополнительным вычисляемым столбцам, в первую очередь связанным с разделением и данными, необходимыми при преобразовании обновления в пару вставка / обновление. Эти дополнительные столбцы приводят к тому, что промежуточная строка превышает разрешенные 8060 байтов при более ранней сортировке - той, что сразу после фильтра:

Проблема сортировки

Обратите внимание, что Фильтр имеет 1319 столбцов (выражений и базовых столбцов) в своем списке вывода. Присоединение отладчика показывает стек вызовов в точке, где возникает фатальное исключение:

Трассировки стека

Заметим попутно , что проблема заключается не в Spool - исключение там преобразуется в предупреждение о потенциале для строки , чтобы быть слишком большим.

Почему обновление с использованием слияния не удается, в то время как вставка делает, и прямое обновление также делает?

Прямое обновление не имеет такой же внутренней сложности, как MERGE. Это принципиально более простая операция, которая имеет тенденцию упрощать и оптимизировать лучше. Удаление NOT MATCHEDпредложения может также устранить достаточную сложность, так что в некоторых случаях ошибка не генерируется. Однако этого не произойдет.

В конечном счете, мой совет - избегать MERGEбольших или более сложных задач. По моему опыту, отдельные операторы вставки / обновления / удаления, как правило, лучше оптимизируются, их легче понять, а также они в целом работают лучше по сравнению с MERGE.

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