Обработка нескольких результатов из хранимой процедуры CLR в T-SQL - PullRequest
4 голосов
/ 27 мая 2011

У меня есть некоторый сложный алгоритм, написанный на C # как хранимая процедура CLR. Процедура не является детерминированной (это зависит от текущего времени). Результатом процедуры являются две таблицы. Я не нашел никакого решения, как обрабатывать мульти-результаты из хранимых процедур в T-SQL. Выполнение этой процедуры является ключевым (процедура вызывается каждые ~ 2 секунды).

Я нашел самый быстрый способ обновления таблиц:

UPDATE [db-table] SET ... SELECT * FROM [clr-func]

Это намного быстрее, чем обновлять db-таблицу из процедуры CLR через ADO.NET.

Я использовал статическое поле для хранения результатов и запроса к ним после выполнения хранимой процедуры clr.

Стек вызовов:

T-SQL proc
    -> CLR proc (MyStoredProcedure)
        -> T-SQL proc (UpdateDataFromMyStoredProcedure)
            -> CLR func (GetFirstResultOfMyStoredProcedure)
            -> CLR func (GetSecondResultOfMyStoredProcedure)

Проблема в том, что иногда функции CLR имеют нулевое значение в статическом поле result, но в процедуре CLR result не равно нулю. Я обнаружил, что иногда функции CLR вызываются в другом AppDomain, чем процедура CLR. Однако процедура CLR все еще выполняется и может выполнять следующие операции, и исключение не выдается.

Есть ли способ заставить функции CLR вызываться в том же AppDomain, что и "родительская" процедура CLR?

Или есть какой-то другой способ, как достичь моего намерения?

P.S .: Первоначально сложный алгоритм был написан на T-SQL, но производительность была низкой (примерно в 100 раз медленнее, чем алгоритм в C #).

Спасибо!

Упрощенный код:

// T-SQL
CREATE PROC [dbo].[UpdateDataFromMyStoredProcedure] AS BEGIN
    UPDATE [dbo].[tblObject]
        SET ...
        SELECT * FROM [dbo].[GetFirstResultOfMyStoredProcedure]()
    UPDATE [dbo].[tblObjectAction]
        SET ...
        SELECT * FROM [dbo].[GetSecondResultOfMyStoredProcedure]()
END

// ... somewhere else
EXEC [dbo].[MyStoredProcedure]

-

// C#
public class StoredProcedures {
    // store for result of "MyStoredProcedure ()"
    private static MyStoredProcedureResult result;
    [SqlProcedure]
    public static int MyStoredProcedure() {
        result = null;
        result = ComputeComplexAlgorithm();
        UpdateDataFromMyStoredProcedure();
        result = null;
    }
    [SqlFunction(...)]
    public static IEnumerable GetFirstResultOfMyStoredProcedure() {
        return result.First;
    }
    [SqlFunction(...)]
    public static IEnumerable GetSecondResultOfMyStoredProcedure() {
        return result.Second;
    }
    private static void UpdateDataFromMyStoredProcedure() {
        using(var cnn = new SqlConnection("context connection=true")) {
            using(var cmd = cnn.CreateCommand()) {
                cmd.CommandType = System.Data.CommandType.StoredProcedure;
                cmd.CommandText = "[dbo].[UpdateDataFromMyStoredProcedure]";
                cmd.ExecuteNonQuery();
            }
        }
    }
}

Ответы [ 2 ]

2 голосов
/ 31 марта 2015

Существует две возможности:

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

    Однако есть определенный нюанс последовательности событий в том, как SQL Server обрабатывает выгрузку доменов приложений, чтовы испытываетеПроисходит следующее: ваша система испытывает нехватку памяти и помечает домен приложения для выгрузки.Это можно увидеть в журналах SQL Server, поскольку в нем будет указано точное имя выгружаемого домена приложения.

    AppDomain 61 ({имя_базы_данных}. {Имя_хозяина} [время выполнения] .60) помечен для выгрузки из-за нехватки памяти.

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

    1. Выполнить MyStoredProcedure: домен приложения 1 создается, если он еще не существует.«Состояние» домена приложения 1 составляет E_APPDOMAIN_SHARED.result установлено на null.
      1. result заполняется, как и ожидалось
      2. MyStoredProcedure выполняется GetFirstResultOfMyStoredProcedure: «Состояние» домена приложения 1 по-прежнему E_APPDOMAIN_SHARED.result получено, как и ожидалось.
      3. Домен приложения 1 помечен для выгрузки: «Состояние» домена 1 приложения изменено на E_APPDOMAIN_DOOMED
      4. MyStoredProcedure выполняется GetSecondResultOfMyStoredProcedure: Домен приложения1 «состояние» по-прежнему E_APPDOMAIN_DOOMED и поэтому не может быть использовано.Домен приложения 2 создан.App Domain 2 "состояние" - E_APPDOMAIN_SHARED.result установлено на null.Вот почему вы иногда ничего не получаете назад: этот процесс происходит в Домене приложений 2 (даже если он был инициирован из Домена приложений 1), без доступа к домену приложений 1.
    2. MyStoredProcedure завершено: домен приложения 1 выгружен.


    И существует еще одна возможность того, как может происходить эта последовательность событий: домен приложения 1 может быть помечен для выгрузки до выполнения GetFirstResultOfMyStoredProcedure.В этом случае домен приложения 2 создается при выполнении GetFirstResultOfMyStoredProcedure, и он, и GetSecondResultOfMyStoredProcedure работают в домене приложения 2 и ничего не возвращают.

    Следовательно, если вы хотите / хотите, чтобы была выдана ошибкапри этих условиях ваши Get*ResultOfMyStoredProcedure методы должны проверить, если result == null, прежде чем пытаться получить, и если это ноль, то выдать ошибку.ИЛИ, если есть возможность пересчитать значение того, что хранится в статической переменной, то, если оно равно null, просто повторно его заполнить (например, снова вызвать ComputeComplexAlgorithm).

  • Менее вероятная вероятность состоит в том, что, поскольку домен приложения совместно используется всеми сеансами / вызывающими сторонами для этого кода, это возможно , если вы не гарантируете, что когда-либо будет выполняться только 1 выполнение этого процесса навремя, когда кто-то еще или задание агента SQL или что-то еще выполнили MyStoredProcedure, что обнуляет статическую переменную при запуске.

    Поскольку вы уже согласились использовать сборку UNSAFE для полученияобновляемую статическую переменную, вы можете также добавить механизм блокировки, чтобы гарантировать, что MyStoredProcedure является однопоточным.


Помимо этих областей, на которые можно обратить внимание, этот процесс может наиболеескорее всего, это будет сделано еще быстрее и менее запутанно.Вы можете использовать табличные параметры (TVP) для потоковой передачи данных обратно на SQL Server, как в коде приложения.Просто создайте один или два пользовательских табличных типа (UDTT), которые соответствуют структурам двух результирующих наборов, возвращаемых TVF (GetFirstResultOfMyStoredProcedure и GetSecondResultOfMyStoredProcedure).Пожалуйста, смотрите мой ответ здесь о том, как правильно транслировать результаты.Используя эту модель, вы можете:

  • сделать оба обновления встроенными в MyStoredProcedure CLR proc
  • избавиться от статической переменной
  • Возможно, больше не нужно UNSAFE (если оно использовалось только для статическогопеременная).Вам по-прежнему может потребоваться EXTERNAL_ACCESS, если вы не можете передать результаты через контекстное соединение, и в этом случае вы будете использовать обычное соединение (т. Е. Строка подключения использует либо «Сервер = (локальный)», либо не указывает «Сервер».").
  • избавиться от UpdateDataFromMyStoredProcedure метод
  • избавиться от UpdateDataFromMyStoredProcedure T-SQL proc
  • избавиться от GetFirstResultOfMyStoredProcedure Функция CLR
  • избавиться от функции GetSecondResultOfMyStoredProcedure CLR
  • освободить всю память, которая в данный момент используется этой статической переменной для хранения двух результирующих наборов !!

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

2 голосов
/ 27 мая 2011

В соответствии с Бобом Бочемином"SQLCLR создает один домен приложения на владельца сборки, а не один домен на базу данных" У обеих ваших сборок SQLCLR один и тот же владелец?

...