Изменение уровня изоляции только в отдельных транзакциях ADO.NET

Каков наилучший способ реализовать разные уровни изоляции для отдельных транзакций при использовании клиентской среды, ORM или аналогичных запросов на сборку, которая не поддерживает подсказки запросов, такие как WITH (NOLOCK)?

Представьте приложение, которое использует уровень ReadUncommitted для ряда сложных и длинных запросов (хорошо осведомленных о связанных рисках), и предполагается, что он должен работать с NHibernate и критериями запроса (или QueryOver/LINQ, просто нет конкатенации строк!).

NHibernate не поддерживает подсказку with (nolock) (за исключением случаев использования встроенного SQL, который в настоящее время используется во многих случаях).

Итак, чтобы заменить собственные строки SQL и их утомительный код здания, я хочу использовать транзакции с IsolationLevel.ReadUnCommitted для замены 'with (nolock)'.

Но соединение остается на измененном уровне изоляции даже после Commit/Rollback, запуская все на новом уровне. Даже после connection.Close(), он возвращается в пул соединений и повторно используется с измененным уровнем изоляции.

Я изначально заметил это, потому что я протестировал открытие соединения с уровнем изоляции Snapshot и отправку простого запроса, чтобы отключить Read Uncommitted, если включен режим моментального снимка в базе данных (вообще нет простого переключения на моментальный снимок). В тестовой базе данных отключен режим моментального снимка, поэтому я получил исключение и установил переменную UseReadUncommitted в значение "true" в блоке catch, но более поздние запросы из "нового" /повторно используемого соединения все равно получили одно и то же исключение.

Я написал простой класс для переноса обработки транзакций в используемом блоке, автоматически сбросив значение IsolationLevel в .Dispose(). Но это, по-видимому, вызывает два дополнительных обращения к БД, и я не уверен, может ли измененный уровень изоляции "выжить" в определенных ситуациях и повлиять на другие запросы. Код работал в первую очередь, он для простых соединений/транзакций ADO.NET(я буду делать еще один сеанс NHibernate, если это хорошо!).

Любые предложения?

public class TransactionContainerTempIsolationLevel : IDisposable
{
    public IsolationLevel OldIsolationLevel { get; private set; }

    public IsolationLevel TempIsolationLevel { get; private set; }

    public IDbTransaction Transaction { get; private set; }

    private readonly IDbConnection _conn;

    public TransactionContainerTempIsolationLevel(IDbConnection connection, IsolationLevel tempIsolationLevel)
    {
        _conn = connection;
        LocalIsolationLevel = localIsolationLevel;

        var checkTran = _conn.BeginTransaction();
        if (checkTran.IsolationLevel == tempIsolationLevel)
        {
            Transaction = checkTran;
        }
        else
        {
            OldIsolationLevel = checkTran.IsolationLevel;
            checkTran.Dispose();
            Transaction = _conn.BeginTransaction(tempIsolationLevel);
        }
    }

    public void Dispose()
    {
        Transaction.Dispose();
        if (OldIsolationLevel != TempIsolationLevel)
        {
            using (var restoreTran = _conn.BeginTransaction(OldIsolationLevel))
            {
                restoreTran.Commit();
            }
        }
    }
}

Ответ 1

Тот факт, что многие ORM не поддерживают (динамические) подсказки, является позором. Установка уровня изоляции или просмотр обертки и TVF являются общими способами.

Но соединение остается на измененном уровне изоляции даже после Commit/Rollback, запуская все на новом уровне. Даже после connection.Close(), возвращается в пул соединений и повторно используется с измененным уровнем изоляции.

Да, это ошибка дизайна в SQL Server, которая была исправлена ​​в 2014 году.

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

Именно так я узнал об этом. Тревожная находка.

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

Единственный разумный способ справиться с утечкой уровня изоляции в < 2014, который я нашел, заключался в том, чтобы всегда использовать явную транзакцию для каждого доступа к базе данных. Это, на мой взгляд, хорошая идея в большинстве случаев в любом случае. Вам часто нужно выбирать уровень изоляции и в любом случае обеспечивать атомарность. И если вы можете сделать переход на SNAPSHOT (который я рекомендую), вы, вероятно, захотите запустить много запросов в одной транзакции моментального снимка, чтобы все запросы отображали одни и те же данные.

В >= 2014 уровень по умолчанию для только что открытого соединения - READ COMMITTED.

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

Итак, вы можете просто использовать одну транзакцию и позволить уровню протекать. Если вы действительно хотите восстановить, я бы предложил этот T-SQL:

SELECT isolation_level FROM sys.sessions WHERE session_id = @@SPID
SET TRANSACTION ISOLATION LEVEL X
BEGIN TRAN

Надеюсь, это хорошо. Это одна поездка туда и обратно. Для восстановления старого уровня вам понадобится еще один раунд.

Если вы действительно заинтересованы в производительности, вы можете сохранить свой собственный пул соединений с соединениями в известном состоянии.

Или, используйте одну строку соединения на уровень изоляции. Сделайте их уникальными, используя AppName.

Если вы читаете только в RUC или RC, вам даже не нужна транзакция. Вы можете оказаться в одном кругосветном путешествии.

Я рекомендую вам использовать простейшее возможное решение, которое работает для вас.