C, PHP, VB, .NET

Дневникът на Филип Петров


* Трансакции

Публикувано на 18 април 2009 в раздел Бази от Данни.

Трансакция наричаме последователност от SQL заявки, които трябва да изпълняват условието или всичките да бъдат изпълнени или нито една от тях да не бъде изпълнена. Може да дадем класически пример с банковите трансакции. Например, ако искаме да прехвърлим 50 лева от акаунт 1 в акаунт 2, то трябва да изпълним следните две заявки:

UPDATE accounts
SET amount = amount - 50
WHERE id = 1;

UPDATE accounts
SET amount = amount + 50
WHERE id = 2;

Какво обаче ще се случи, ако първата заявка се изпълни, но поради някаква причина втората не (например възникне грешка)? Отговорът е, че парите ще бъдат изгубени. Тук на помощ ни идват именно трансакциите – те гарантират, че ако някоя заявка не се изпълни, то данните ще бъдат възстановени в първоначалния им вид.

Групирането на заявки в трансакция се изпълнява изключително лесно. Единствено трябва да оградим данните с BEGIN (започване на трансакция) и COMMIT (край на трансакция):

BEGIN;

UPDATE accounts
SET amount = amount - 50
WHERE id = 1;

UPDATE accounts
SET amount = amount + 50
WHERE id = 2;

COMMIT;

Ако някоя от трансакциите пропадне, то се прави т.нар. ROLLBACK. За целта се използва innodb log файл, в който се записват старите данни преди изпълнението на всяка заявка. Естествено ние можем да правим ROLLBACK и сами. Например:

mysql> SELECT amount
FROM accounts
WHERE id = 1;
+--------+
| amount |
+--------+
| 106.38 |
+--------+
1 row in set (0.00 sec)

mysql> BEGIN;
Query OK, 0 rows affected (0.00 sec)

mysql> UPDATE accounts
SET amount = amount - 50
WHERE id = 1;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0

mysql> ROLLBACK;
Query OK, 0 rows affected (0.36 sec)

mysql> SELECT amount
FROM accounts
WHERE id = 1;
+--------+
| amount |
+--------+
| 106.38 |
+--------+
1 row in set (0.00 sec)

Виждате, че резултатите от заявката SELECT са едни и същи, тоест ROLLBACK е „върнал“ данните в първоначалния им вид преди заявката UPDATE.

Трансакциите трябва да отговарят на условие за консистентно четене. Това означава, че всеки SELECT чете данните записани точно след последния COMMIT. Ето един пример – отворили сме две връзки към базата данни. С едната започваме трансакция и правим UPDATE като даваме 200 лева на акаунт 1. След това във втората връзка искаме да проверим наличността на този акаунт:

Transactions Consitency

След това с първата връзка завършваме трансакцията и отново проверяваме каква е стойността във втората връзка:

Transactions Consitency

Важно е да споменем, че при InnoDB всяка заявка, която НЕ участва в блокове „BEGIN – COMMIT“ е автоматично записана, т.е. можем да приемем, че единичните заявки са завършени трансакции сами по себе си.

В следващата статия ще разгледаме „заключвания“ с цел синхронизация на трансакции.

Забележка: За стартиране на трансакция можете да използвате и по-популярната сред останалите СУБД команда „START TRANSACTION“.

 



5 коментара


  1. cufi_pufi каза:

    Искам само да попитам първия и най-лесен пример за транзакции как ще стане на Access, прочетох, че се слага BEGIN TRANSACTION и COMMIT TRANSACTION, но пак не ми тръгва.

  2. SQL стандартът попринцип казва „START TRANSACTION“ и „COMMIT“. Честно си признавам, че не знам как е в ACCESS.

  3. Филип Славов – Здравей,

    Бъркаш цялата концепция. За MySQL това, че даден UPDATE връща „грешка“ (от твоя пример – няма такова id) не означава, че заявката не е изпълнена успешно. Не е сървъра за базата данни този, който взима решението кога трансакция е успешна и кога не – ние сме.

    В твоя пример си представи следното – тези заявки се подават от програма написана на С. След първата заявка резултатът се подава към програмата и той е „OK, 1 rows affected“, което за програмата означава „първата заявка е успешна“. След втората заявка обаче се връща грешка – тогава програмата вместо COMMIT ще подаде заявка ROLLBACK. Това е логиката. Надявам се, че се изразих ясно.

    Има и автоматичен ROLLBACK – той се прави ако програмата „изчезне“, т.е. започнала е трансакция, но не я е завършила и е настъпил timeout. Друг вариант с автоматичен ROLLBACK е да забие самия MySQL сървър – при неговото рестартиране ще настъпи връщане на всички незавършени трансакции.

  4. Филип Славов каза:

    Нещо не схващам обаче…
    Пиша това във MySQL конзолния прозорец:

    BEGIN;

    UPDATE Customer
    SET t_id = 4
    WHERE cus_id = 4;

    UPDATE Customer
    SET t_id = 5
    WHERE cus_id = 4213;

    COMMIT;

    Както се сещате на 2-рия UPDATE такова id няма и изплюва грешка там. Но в момента в който дам COMMIT промяната на първия UPDATE се изпълнява въпреки всичко. Нали идеята на транзакциите беше да се опаковат няколко заявки във едно (между Begin и Commit-а) за да се изпълнят всички заедно и така или се изпълняват всички или никоя не се изпълнява. Как така въпреки грешката се изпълнява първия UPDATE? Или ако не при какви условия би треело транзакцията да пропадне.

    ПП: Докато не дам commit, в базата ми няма промяна, проверих…
    ПП2: Ползвам ENGINE = InnoDB;
    ПП3: Нещо като напиша коментар не ми излизат след тва… Писах и за View преди неколко дена и все още не е излезъл..

  5. Филип Славов каза:

    Аха… значи все пак ние ръчно решаваме дали да commit-нем, или Rollback-нем в зависимост от общия резултат на заявките (дали ни харесва или не :D )
    10x…

Добави коментар

Адресът на електронната поща няма да се публикува


*