Должен ли я зафиксировать или откатить транзакцию чтения?


95

У меня есть запрос на чтение, который я выполняю в транзакции, чтобы я мог указать уровень изоляции. Что мне делать после завершения запроса?

  • Зафиксировать транзакцию
  • Откатить транзакцию
  • Ничего не делать (что приведет к откату транзакции в конце блока using)

Каковы последствия каждого из них?

using (IDbConnection connection = ConnectionFactory.CreateConnection())
{
    using (IDbTransaction transaction = connection.BeginTransaction(IsolationLevel.ReadUncommitted))
    {
        using (IDbCommand command = connection.CreateCommand())
        {
            command.Transaction = transaction;
            command.CommandText = "SELECT * FROM SomeTable";
            using (IDataReader reader = command.ExecuteReader())
            {
                // Read the results
            }
        }

        // To commit, or not to commit?
    }
}

EDIT: вопрос не в том, следует ли использовать транзакцию или есть ли другие способы установить уровень транзакции. Вопрос в том, имеет ли значение то, что транзакция, которая ничего не изменяет, фиксируется или откатывается. Есть разница в производительности? Влияет ли это на другие подключения? Есть ли другие отличия?


1
Вы, вероятно, уже знаете об этом, но, учитывая приведенный вами пример, вы можете получить эквивалентные результаты, просто выполнив запрос: SELECT * FROM SomeTable with NOLOCK
JasonTrue

@Stefan, кажется, большинство из нас задаются вопросом, почему вы пытаетесь выполнить операцию только для чтения. Можете ли вы сообщить нам, знаете ли вы о NOLOCK, и если да, то почему вы не пошли по этому пути.
StingyJack

Я знаю о NOLOCK, но эта система работает с разными базами данных, а также с SQL Server, поэтому я стараюсь избегать конкретных подсказок блокировки SQL Server. Это вопрос скорее из любопытства, чем из чего-либо еще, поскольку приложение отлично работает с приведенным выше кодом.
Стефан Мозер

Ах, в этом случае я удаляю тег sqlserver, потому что он обозначает MSSqlServer как целевой продукт.
StingyJack

@StingyJack - Вы правы, мне не следовало использовать тег sqlserver.
Stefan Moser

Ответы:


51

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

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


44
Есть разумная альтернатива - откат. То есть явный откат. Если вы не хотели ничего менять, откат гарантирует, что все будет отменено. Конечно, никаких изменений быть не должно; откат гарантирует это.
Джонатан Леффлер

2
Разные СУБД могут иметь разную семантику «неявного завершения транзакции». IBM Informix (и я считаю, что DB2) выполняет неявный откат; по слухам, Oracle делает неявную фиксацию. Я предпочитаю неявный откат.
Джонатан Леффлер

8
Предположим, я создаю временную таблицу, заполняю ее идентификаторами, присоединяю ее к таблице данных, чтобы выбрать данные, которые идут с идентификаторами, а затем удаляю временную таблицу. На самом деле я просто читаю данные, и меня не волнует, что происходит с временной таблицей, поскольку это временно ... но с точки зрения производительности, будет ли дороже откат транзакции или ее фиксация? Каков эффект фиксации / отката, когда задействованы только временные таблицы и операции чтения?
Трийнко

4
@Triynko - Интуитивно догадываюсь, что ROLLBACK дороже. COMMIT - это нормальный вариант использования, а ROLLBACK - исключительный случай. Но кого это волнует, кроме академического? Я уверен, что для вашего приложения есть 1000 лучших точек оптимизации. Если вам действительно интересно, вы можете найти код обработки транзакции mySQL
Марк Брэкетт

3
@Triynko - Единственный способ оптимизировать - профилировать. Это настолько простое изменение кода, что нет причин не профилировать оба метода, если вы действительно хотите его оптимизировать. Не забудьте сообщить нам о результатах!
Марк Брэкетт

28

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


2
Спасибо, что сообщили мне, что они эквивалентны. На мой взгляд, это лучший ответ на актуальный вопрос.
chowey

это будет означать, что транзакция неактивна, если мы используем фиксацию без фактических обновлений. я только что столкнулся с этим на моем живом сайте
Мухаммад Омер Аслам

6

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


3

IMHO может иметь смысл обернуть запросы только для чтения в транзакции, поскольку (особенно в Java) вы можете указать транзакции, чтобы она была «только для чтения», что, в свою очередь, драйвер JDBC может рассмотреть возможность оптимизации запроса (но не обязательно, поэтому никто все же помешает вам выдать INSERT). Например, драйвер Oracle полностью избегает блокировок таблиц для запросов в транзакции, помеченной только для чтения, что дает большую производительность в приложениях, ориентированных на чтение.


3

Рассмотрим вложенные транзакции .

Большинство СУБД не поддерживают вложенные транзакции или пытаются имитировать их очень ограниченным образом.

Например, в MS SQL Server откат внутренней транзакции (которая не является реальной транзакцией, MS SQL Server просто считает уровни транзакций!) Откатит все, что произошло в самой внешней транзакции (которая является реальной транзакцией).

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

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

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

Что касается производительности: в зависимости от уровня изоляции, SELECT может потребовать различной степени БЛОКИРОВКИ и временных данных (снимков). Это очищается при закрытии транзакции. Не имеет значения, выполняется ли это через COMMIT или ROLLBACK. Может быть незначительная разница в затраченном времени ЦП - COMMIT, вероятно, обрабатывается быстрее, чем ROLLBACK (на два символа меньше) и другие незначительные отличия. Очевидно, это верно только для операций только для чтения!

Совершенно не запрашивается: другой программист, который может прочитать код, может предположить, что ОТКАТ подразумевает состояние ошибки.


2

Небольшое примечание, но вы также можете написать этот код следующим образом:

using (IDbConnection connection = ConnectionFactory.CreateConnection())
using (IDbTransaction transaction = connection.BeginTransaction(IsolationLevel.ReadUncommitted))
using (IDbCommand command = connection.CreateCommand())
{
    command.Transaction = transaction;
    command.CommandText = "SELECT * FROM SomeTable";
    using (IDataReader reader = command.ExecuteReader())
    {
        // Do something useful
    }
    // To commit, or not to commit?
}

И если вы немного реструктурируете вещи, вы также сможете переместить блок using для IDataReader наверх.


1

Если вы поместите SQL в хранимую процедуру и добавите это над запросом:

set transaction isolation level read uncommitted

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


1

ROLLBACK обычно используется в случае ошибки или исключительных обстоятельств, а COMMIT - в случае успешного завершения.

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

Транзакция только для чтения может логически «потерпеть неудачу» по многим причинам, например:

  • запрос не возвращает ровно одну строку, как ожидалось
  • хранимая процедура вызывает исключение
  • полученные данные противоречивы
  • пользователь прерывает транзакцию, потому что она занимает слишком много времени
  • тупик или тайм-аут

Если COMMIT и ROLLBACK используются правильно для транзакции только для чтения, она продолжит работать должным образом, если в какой-то момент будет добавлен код записи DB, например, для кэширования, аудита или статистики.

Неявный ROLLBACK следует использовать только в ситуациях «фатальной ошибки», когда приложение вылетает из строя или завершается с неисправимой ошибкой, сбоем сети, отключением питания и т. Д.


0

Учитывая, что READ не меняет состояние, я бы ничего не делал. Выполнение фиксации ничего не даст, кроме как потратить цикл на отправку запроса в базу данных. Вы не выполнили операцию, изменившую состояние. Так же и с откатом.

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


3
В зависимости от уровня изоляции, выбранный CAN получает блокировки, которые будут блокировать другие транзакции.
Грэм Перроу

Соединение будет закрыто в конце блока using - для этого оно и есть. Но хорошо, что сетевой трафик, вероятно, является самой медленной частью уравнения.
Joel Coehoorn

1
Транзакция будет зафиксирована или откатана тем или иным способом, поэтому лучше всего всегда выполнять фиксацию, если она удалась.
Neil Barnwell

0

Если вы установите AutoCommit false, то ДА.

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


-2

В вашем примере кода, где у вас есть

  1. // Сделаем что-нибудь полезное

    Вы выполняете оператор SQL, который изменяет данные?

Если нет, то не существует такой вещи, как «Чтение» транзакции ... Только изменения из операторов Insert, Update и Delete (операторов, которые могут изменять данные) находятся в транзакции ... То, о чем вы говорите, - это блокировки, которые SQL Сервер помещает данные, которые вы читаете, из-за ДРУГИХ транзакций, которые влияют на эти данные. Уровень этих блокировок зависит от уровня изоляции SQL Server.

Но вы не можете зафиксировать или откатить что-либо, если ваш оператор SQL ничего не изменил.

Если вы изменяете данные, вы можете изменить уровень изоляции без явного запуска передачи ... Каждый отдельный оператор SQL неявно присутствует в транзакции. явный запуск транзакции необходим только для того, чтобы убедиться, что 2 или более операторов находятся в одной транзакции.

Если все, что вы хотите сделать, это установить уровень изоляции транзакции, просто установите для CommandText команды значение «Установить уровень изоляции транзакции, повторяющееся чтение» (или любой другой уровень, который вы хотите), установите для CommandType значение CommandType.Text и выполните команду. (вы можете использовать Command.ExecuteNonQuery ())

ПРИМЕЧАНИЕ. Если вы выполняете НЕСКОЛЬКО операторов чтения и хотите, чтобы все они «видели» то же состояние базы данных, что и первый, то вам необходимо установить верхний уровень изоляции Repeatable Read или Serializable ...


// Делаем что-то полезное не меняем никаких данных, просто читаем. Все, что я хочу сделать, это указать уровень изоляции запроса.
Stefan Moser

Тогда вы можете сделать это без явного запуска транзакции от клиента ... Просто выполните строку sql «Установить уровень изоляции транзакции ReadUncommitted», «... Read Committed», «... RepeatableRead», «... Snapshot» , или «... Serializable» «Установить уровень изоляции, прочитанное
подтверждено

3
Транзакции по-прежнему имеют значение, даже если вы только читаете. Если вы хотите выполнить несколько операций чтения, выполнение их внутри транзакции обеспечит согласованность. Делать их без них не получится.
MarkR

да, извините, вы правы, по крайней мере, это так, если для параметра Isolation level установлено значение Repeatable Read или выше.
Чарльз Бретана

-3

Вам нужно запретить другим читать те же данные? Зачем использовать транзакцию?

@Joel - Мой вопрос лучше сформулировать как «Зачем использовать транзакцию для запроса чтения?»

@Stefan - Если вы собираетесь использовать AdHoc SQL, а не хранимую процедуру, просто добавьте WITH (NOLOCK) после таблиц в запросе. Таким образом, вы не несете накладных расходов (хотя и минимальных) в приложении и базе данных для транзакции.

SELECT * FROM SomeTable WITH (NOLOCK)

РЕДАКТИРОВАТЬ @ Комментарий 3: Поскольку в тегах вопросов указано «sqlserver», я предположил, что MSSQLServer был целевым продуктом. Теперь, когда этот момент прояснился, я отредактировал теги, чтобы удалить конкретную ссылку на продукт.

Я до сих пор не уверен, почему вы вообще хотите совершить транзакцию по операции чтения.


1
Все сразу на заданный уровень изоляции. Вы можете использовать транзакцию, чтобы фактически уменьшить количество блокировок для запроса.
Joel Coehoorn

1
Я использую транзакцию, чтобы использовать более низкий уровень изоляции и уменьшить блокировку.
Stefan Moser

@StingyJack - этот код может выполняться в нескольких разных базах данных, поэтому NOLOCK не подходит.
Stefan Moser
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.