Чи повинен я здійснити або відмовити прочитану транзакцію?


95

У мене є запит на читання, який я виконую в рамках транзакції, щоб я міг вказати рівень ізоляції. Що робити після завершення запиту, що мені робити?

  • Здійснити транзакцію
  • Відкат транзакції
  • Нічого не робити (що призведе до відкочування транзакції в кінці використовуваного блоку)

Які наслідки має виконання кожного з них?

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.
Стефан Мозер,

Відповіді:


51

Ти робиш. Період. Іншої розумної альтернативи немає. Якщо ви розпочали транзакцію, вам слід закрити її. Фіксація звільняє будь-які замки, які ви мали, і це однаково розумно з рівнями ізоляції ReadUncommitted або Serializable. Надія на неявний відкат - хоча, можливо, технічно еквівалентна - це просто погана форма.

Якщо це вас не переконало, просто уявіть наступного хлопця, який вставляє оператор оновлення в середину вашого коду і повинен відстежувати неявний відкат, який відбувається, і видаляє його дані.


44
Є розумна альтернатива - відкат. Явний відкат, тобто. Якщо ви не хотіли щось змінювати, відкат гарантує, що щось скасовано. Звичайно, ніяких змін не повинно було бути; відкат гарантує це.
Джонатан Леффлер

2
Різні СУБД можуть мати різну семантику „неявного завершення транзакції”. IBM Informix (і я вважаю DB2) виконує неявний відкат; за чутками, Oracle робить неявний коміт. Я вважаю за краще неявний відкат.
Джонатан Леффлер

8
Припустимо, я створюю тимчасову таблицю, заповнюю її ідентифікаторами, приєдную її до таблиці даних, щоб вибрати дані, що йдуть з ідентифікаторами, а потім видаляю тимчасову таблицю. Я насправді просто читаю дані, і мені байдуже, що відбувається з тимчасовою таблицею, оскільки це тимчасово ... але з точки зору продуктивності, чи буде дорожчим відкат транзакції чи її фіксація? Який ефект робить коміт / відкат, коли не задіяно нічого, окрім тимчасових таблиць та операцій читання?
Трійко

4
@Triynko - Інтуїтивно я думаю, що ROLLBACK дорожчий. COMMIT - це звичайний випадок використання, а ROLLBACK - винятковий випадок. Але, окрім академічної, кого це цікавить? Я впевнений, що існує 1000 кращих пунктів оптимізації для вашого додатка. Якщо вам справді цікаво, ви можете знайти код обробки транзакцій mySQL за адресою bazaar.launchpad.net/~mysql/mysql-server/mysql-6.0/annotate/…
Марк Брекетт,

3
@Triynko - Єдиний спосіб оптимізації - це профіль. Це така проста зміна коду, немає жодної причини не профайлювати обидва методи, якщо ви дійсно хочете її оптимізувати. Обов’язково оновіть нас із результатами!
Марк Брекетт,

28

Якщо ви нічого не змінили, тоді ви можете використати COMMIT або ROLLBACK. Будь-який із них випустить усі отримані вами блокування читання, і оскільки ви не вносили жодних інших змін, вони будуть рівнозначними.


2
Дякуємо, що повідомили мені, що вони рівнозначні. На мій погляд, це найкраще відповідає на актуальне питання.
chowey

це дало б транзакцію неактивності, якщо ми використовуємо коміт без фактичних оновлень. я щойно зіткнувся з цим на своєму веб-сайті
Мухаммад Омер Аслам,

6

Якщо ви починаєте транзакцію, найкращою практикою є завжди її вчинення. Якщо всередині вашого блоку використання (транзакції) буде внесено виняток, транзакція буде автоматично відкатана.


3

IMHO може мати сенс обертати запити лише на читання в транзакціях, оскільки (особливо на Java) ви можете сказати, що транзакція має статус "лише для читання", що, у свою чергу, драйвер JDBC може розглянути можливість оптимізації запиту (але не обов'язково, тому ніхто заважатиме вам видавати INSERTвсе ж). Наприклад, драйвер Oracle повністю уникне блокування таблиць для запитів у транзакції, позначеній лише для читання, що значно покращує продуктивність програм, що сильно керуються читанням.


3

Розглянемо вкладені транзакції .

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

Наприклад, в MS SQL Server, відкат у внутрішній операції (яка не є реальною транзакцією, MS SQL Server тільки підраховує рівні транзакцій!) Буде відкотити все , що сталося в самому далекому від центру угоди (яка є реальною угодою).

Деякі обгортки баз даних можуть розглядати відкат у внутрішній транзакції як ознаку того, що сталася помилка, і відкотити все в самій транзакції, незалежно від того, здійснено чи відкотчено крайню транзакцію.

Отже, COMMIT - це безпечний спосіб, коли ви не можете виключити, що ваш компонент використовується якимось програмним модулем.

Зверніть увагу, що це загальна відповідь на запитання. Приклад коду розумно вирішує проблему із зовнішньою транзакцією, відкриваючи нове підключення до бази даних.

Щодо продуктивності: залежно від рівня ізоляції, SELECTS можуть вимагати різного ступеня БЛОКУВАННЯ та тимчасові дані (знімки). Це очищається, коли транзакція закрита. Не має значення, чи це робиться за допомогою COMMIT чи ROLLBACK. Може бути незначна різниця у витраченому часу процесора - COMMIT, швидше за все, швидше аналізується, ніж ROLLBACK (на два символи менше) та інші незначні відмінності. Очевидно, що це справедливо лише для операцій лише для читання!

Повністю не просять: інший програміст, який може прочитати код, може припустити, що 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?
}

І якщо ви трохи реструктуруєте речі, можливо, ви зможете також перемістити використовуючий блок для IDataReader вгору.


1

Якщо ви помістите SQL у збережену процедуру і додасте це над запитом:

set transaction isolation level read uncommitted

тоді вам не потрібно перестрибувати будь-які обручі в коді C #. Встановлення рівня ізоляції транзакції в збереженій процедурі не призводить до того, що параметр застосовується до всіх майбутніх застосувань цього підключення (це те, про що вам слід турбуватися з іншими налаштуваннями, оскільки з’єднання об’єднано). В кінці збереженої процедури вона просто повертається до того, з чим було ініціалізоване з'єднання.


1

ROLLBACK використовується здебільшого у разі помилки або виняткових обставин, а COMMIT - у випадку успішного завершення.

Ми повинні закривати транзакції з COMMIT (для успіху) та ROLLBACK (для помилки), навіть у випадку транзакцій лише для читання, де це, здається, не має значення. Насправді це має значення для послідовності та забезпечення майбутнього.

Транзакція лише для читання може логічно "провалитися" багатьма способами, наприклад:

  • запит не повертає рівно один рядок, як очікувалося
  • збережена процедура викликає виняток
  • отримані дані виявляються суперечливими
  • користувач перериває транзакцію, оскільки це займає занадто багато часу
  • тупик або час очікування

Якщо COMMIT і ROLLBACK використовуються належним чином для транзакції лише для читання, вона буде продовжувати працювати, як очікувалося, якщо в якийсь момент буде додано код запису DB, наприклад, для кешування, аудиту або статистики.

Неявний ЗВЕРНЕННЯ слід використовувати лише в ситуаціях "фатальної помилки", коли програма аварійно завершує роботу або виходить із непоправленою помилкою, збоєм мережі, відключенням електроживлення тощо.


0

Враховуючи, що READ не змінює стан, я б нічого не робив. Виконання коміту нічого не призведе, крім втрати циклу надсилання запиту до бази даних. Ви не виконували операцію, яка змінила стан. Так само для відкату.

Однак слід обов’язково очистити об’єкти та закрити з’єднання з базою даних. Якщо не закрити з’єднання, це може призвести до проблем, якщо цей код буде викликатися неодноразово.


3
Залежно від рівня ізоляції, обраний МОЖЕ отримати блокування, які блокуватимуть інші транзакції.
Graeme Perrow

З'єднання буде закрито в кінці використовуваного блоку - ось для чого воно існує. Але добре, що мережевий трафік - це, мабуть, найповільніша частина рівняння.
Джоел Коухорн,

1
Транзакція буде так чи інакше здійснена або відкатана, тому найкращою практикою буде завжди видавати коміт, якщо це вдалося.
Ніл Барнуелл

0

Якщо ви встановили AutoCommit false, тоді YES.

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


-2

У зразку коду, де ви маєте

  1. // Зробіть щось корисне

    Ви виконуєте оператор SQL, який змінює дані?

Якщо ні, транзакція "Прочитати" не існує ... У транзакції знаходяться лише зміни з інструкцій "Вставити", "Оновити" та "Видалити" (оператори, які можуть змінювати дані) ... Про що Ви говорите, це про замки, які SQL Сервер розміщує дані, які ви читаєте, через ІНШІ транзакції, які впливають на ці дані. Рівень цих блокувань залежить від рівня ізоляції SQL Server.

Але ви не можете ні комітувати, ні повертати нічого, якщо ваш оператор SQL нічого не змінив.

Якщо ви змінюєте дані, ви можете змінити рівень ізоляції, не починаючи явно транзакцію ... Кожен окремий оператор SQL неявно є транзакцією. явно розпочати транзакцію потрібно лише для того, щоб переконатися, що 2 або більше операторів знаходяться в межах однієї транзакції.

Якщо все, що вам потрібно зробити, це встановити рівень ізоляції транзакції, тоді просто встановіть CommandText команди на "Set Transaction Isolation level Repeatable Read" (або будь-який рівень, який ви хочете), встановіть CommandType на CommandType.Text і виконайте команду. (Ви можете використовувати Command.ExecuteNonQuery ())

ПРИМІТКА. Якщо ви виконуєте МНОЖЕВІ оператори читання і хочете, щоб усі вони "бачили" такий самий стан бази даних, як і перший, тоді вам потрібно встановити рівень ізоляції зверху Повторюване читання чи серіалізація ...


// Робіть щось корисне, не змінюючи жодних даних, просто прочитайте. Все, що я хочу зробити, це вказати рівень ізоляції запиту.
Стефан Мозер

Тоді ви можете зробити це, не починаючи явно транзакцію від клієнта ... Просто виконайте рядок sql "Встановити рівень ізоляції транзакції ReadUncommitted", "... Read Committed", "... RepeatableRead", "... Snapshot" , або "... Серійний доступ" "Встановити рівень ізоляції прочитаним"
Чарльз Бретана

3
Транзакції все ще важливі, навіть якщо ви лише читаєте. Якщо ви хочете виконати кілька операцій зчитування, виконуючи їх усередині транзакції, забезпечите узгодженість. Робити їх без одного не буде.
MarkR

так, вибачте, ви маєте рацію, принаймні це вірно, якщо значення рівня ізоляції встановлено на Повторне читання або вище.
Чарльз Бретана

-3

Вам потрібно заблокувати іншим читання тих самих даних? Навіщо використовувати транзакцію?

@Joel - Моє запитання було б краще сформулювати так: "Навіщо використовувати транзакцію для запиту на читання?"

@Stefan - Якщо ви збираєтеся використовувати AdHoc SQL, а не збережений процес, просто додайте WITH (NOLOCK) після таблиць у запиті. Таким чином ви не несете накладні витрати (хоча і мінімальні) у програмі та базі даних для транзакції.

SELECT * FROM SomeTable WITH (NOLOCK)

EDIT @ Коментар 3: Оскільки у вас у тегах запитань був "sqlserver", я припустив, що цільовим продуктом є MSSQLServer. Тепер, коли цей момент з’ясовано, я відредагував теги, щоб видалити конкретне посилання на товар.

Я досі не впевнений у тому, чому ви спочатку хочете здійснити транзакцію за прочитаним записом.


1
До встановленого рівня ізоляції відразу. Ви можете використовувати транзакцію для фактичного зменшення обсяг блокування для запиту.
Джоел Коухорн,

1
Я використовую транзакцію, щоб використовувати нижчий рівень ізоляції та зменшити блокування.
Стефан Мозер

@StingyJack - Цей код може виконуватися проти багатьох різних баз даних, тому NOLOCK не є опцією.
Стефан Мозер
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.