Database
 sql >> база данни >  >> RDS >> Database

Урок за SQL транзакции

В SQL транзакциите се използват за поддържане на целостта на данните, като се гарантира, че поредица от SQL изрази се изпълняват напълно или изобщо не се изпълняват.

Транзакциите управляват поредици от SQL изрази, които трябва да се изпълняват като една единица работа, така че базата данни никога да не съдържа резултатите от частични операции.

Когато транзакция направи множество промени в базата данни, или всички промени са успешни, когато транзакцията е ангажиментирана, или всички промени се отменят, когато транзакцията бъде връщана назад.

Кога да използвам транзакция?

Транзакциите са от първостепенно значение в ситуации, при които целостта на данните би била застрашена, в случай че някой от поредица от SQL изрази се провали.

Например, ако премествате пари от една банкова сметка в друга, ще трябва да приспаднете парите от една сметка и да ги добавите към другата. Не бихте искали да се провали по средата, в противен случай парите могат да бъдат дебитирани от една сметка, но не и кредитирани към другата.

Възможните причини за неуспех могат да включват недостатъчни средства, невалиден номер на сметката, повреда на хардуера и др.

Така че вие ​​не искате да бъдете в ситуация, в която остава така:

Debit account 1 (Done)
Credit account 2 (Not Done)
Record transaction in transaction journal (Not Done)

Това би било наистина лошо. Базата данни ще има непоследователни данни и парите ще изчезнат във въздуха. Тогава банката ще загуби клиент (банката вероятно ще загуби всичките си клиенти, ако това продължи да се случва), а вие ще загубите работата си.

За да запазите работата си, можете да използвате транзакция, която би била нещо подобно:

START TRANSACTION
Debit account 1
Credit account 2
Record transaction in transaction journal
END TRANSACTION 

Можете да напишете условна логика вътре в тази транзакция, която връща обратно транзакцията, ако нещо се обърка.

Например, ако нещо се обърка между дебитиране на сметка 1 и кредитиране на сметка 2, цялата транзакция се отменя.

Следователно би имало само два възможни резултата:

Debit account 1 (Not Done)
Credit account 2 (Not Done)
Record transaction in transaction journal (Not Done)

Или:

Debit account 1 (Done)
Credit account 2 (Done)
Record transaction in transaction journal (Done)

Това е опростено изображение, но е класическа илюстрация на това как работят SQL транзакциите. SQL транзакциите имат ACID.

Видове транзакции

SQL транзакциите могат да се изпълняват в следните режими.

Режим на транзакция Описание
Автоматично извършване на транзакция Всяко отделно изявление е транзакция.
Неявна транзакция Нова транзакция се стартира имплицитно, когато предишната транзакция завърши, но всяка транзакция е изрично завършена, обикновено с COMMIT или ROLLBACK изявление в зависимост от СУБД.
Изрична транзакция Изрично започва с ред като START TRANSACTION , BEGIN TRANSACTION или подобен, в зависимост от СУБД, и изрично ангажирани или върнати обратно със съответните оператори.
Транзакция с обхват на партида Приложимо само за множество активни набори от резултати (MARS). Изрична или неявна транзакция, която започва под сесия на MARS, става транзакция с обхват на партида.

Точните налични режими и опции може да зависят от СУБД. Тази таблица очертава режимите на транзакции, налични в SQL Server.

В тази статия се фокусираме основно върху изрични транзакции.

Вижте как работят неявните транзакции в SQL Server за обсъждане на разликата между неявните транзакции и автоматичното извършване.

Sytnax

Следващата таблица очертава основния синтаксис за стартиране и завършване на изрична транзакция в някои от по-популярните СУБД.

DBMS Явен синтаксис на транзакциите
MySQL, MariaDB, PostgreSQL Изричните транзакции започват с START TRANSACTION или BEGIN изявление. COMMIT извършва текущата транзакция, като прави нейните промени постоянни. ROLLBACK отменя текущата транзакция, като отменя промените й.
SQLite Изричните транзакции започват с BEGIN TRANSACTION оператор и завършва с COMMIT или ROLLBACK изявление. Може също да завършва с END изявление.
SQL сървър Изричните транзакции започват с BEGIN TRANSACTION оператор и завършва с COMMIT или ROLLBACK изявление.
Оракул Изричните транзакции започват с SET TRANSACTION оператор и завършва с COMMIT или ROLLBACK изявление.

В много случаи някои ключови думи са незадължителни, когато се използват изрични транзакции. Например в SQL Server и SQLite можете просто да използвате BEGIN (вместо BEGIN TRANSACTION ) и/или бихте могли да завършите с COMMIT TRANSACTION (за разлика от просто COMMIT ).

Има и различни други ключови думи и опции, които можете да посочите при създаване на транзакция, така че вижте документацията на вашата СУБД за пълния синтаксис.

Пример за SQL транзакция

Ето пример за проста транзакция в SQL Server:

BEGIN TRANSACTION
    DELETE OrderItems WHERE OrderId = 5006;
    DELETE Orders WHERE OrderId = 5006;
COMMIT TRANSACTION;

В този случай информацията за поръчката се изтрива от две таблици. И двете твърдения се третират като една единица работа.

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

Именуване на транзакция

Някои СУБД ви позволяват да предоставите име за вашите транзакции. В SQL Server можете да добавите избраното от вас име след BEGIN и COMMIT изявления.

BEGIN TRANSACTION MyTransaction
    DELETE OrderItems WHERE OrderId = 5006;
    DELETE Orders WHERE OrderId = 5006;
COMMIT TRANSACTION MyTransaction;

Пример за връщане назад на SQL транзакция 1

Ето отново предишния пример, но с допълнителен код. Допълнителният код се използва за връщане на транзакцията в случай на грешка.:

BEGIN TRANSACTION MyTransaction

  BEGIN TRY

    DELETE OrderItems WHERE OrderId = 5006;
    DELETE Orders WHERE OrderId = 5006;

    COMMIT TRANSACTION MyTransaction

  END TRY

  BEGIN CATCH

      ROLLBACK TRANSACTION MyTransaction

  END CATCH

TRY...CATCH оператор реализира обработка на грешки в SQL Server. Можете да затворите всяка група от T-SQL изрази в TRY блок. След това, ако възникне грешка в TRY блок, контролата се предава на друга група от изрази, която е затворена в CATCH блок.

В този случай използваме CATCH блокирайте за връщане на транзакцията. Като се има предвид, че е в CATCH блок, връщане се извършва само ако има грешка.

Пример за връщане назад на SQL транзакция 2

Нека разгледаме по-отблизо базата данни, от която току-що изтрихме редове.

В предишния пример изтрихме редове от Orders и OrderItems таблици в следната база данни:

В тази база данни всеки път, когато клиент направи поръчка, се вмъква ред в Orders таблица и един или повече реда в OrderItems маса. Броят на редовете, вмъкнати в OrderItems зависи от това колко различни продукти поръчва клиента.

Освен това, ако е нов клиент, нов ред се вмъква в Customers таблица.

В този случай редовете трябва да бъдат вмъкнати в три таблици.

В случай на неуспех, не бихме искали да имаме ред, вмъкнат в Orders таблица, но няма съответни редове в OrderItems маса. Това ще доведе до поръчка без артикули за поръчка. По принцип искаме и двете таблици да бъдат напълно актуализирани или изобщо нищо.

Същото беше и когато изтрихме редовете. Искахме всички редове да бъдат изтрити или никакви.

В SQL Server бихме могли да напишем следната транзакция за INSERT изявления.

BEGIN TRANSACTION
    BEGIN TRY 
        INSERT INTO Customers ( CustomerId, CustomerName, PostalAddress, City, StateProvince, ZipCode, Country, Phone )
        VALUES (1006, 'Hi-Five Solutionists', '5 High Street', 'Highlands', 'HI', '1254', 'AUS', '(415) 413-5182');

        INSERT INTO Orders ( OrderId, OrderDate, CustomerId )
        VALUES ( 5006, SYSDATETIME(), 1006 );
        
        INSERT INTO OrderItems ( OrderId, OrderItemId, ProductId, Quantity, ItemPrice )
        VALUES ( 5006, 1, 1, 20, 25.99 );
        
        INSERT INTO OrderItems ( OrderId, OrderItemId, ProductId, Quantity, ItemPrice )
        VALUES ( 5006, 2, 7, 120, 9.99 );

        COMMIT TRANSACTION;
        
    END TRY
    BEGIN CATCH
        ROLLBACK TRANSACTION;
    END CATCH

Този пример предполага, че има логика другаде, която определя дали клиентът вече съществува или не в базата данни.

Клиентът може да е бил вмъкнат извън тази транзакция:


INSERT INTO Customers ( CustomerId, CustomerName, PostalAddress, City, StateProvince, ZipCode, Country, Phone )
VALUES (1006, 'Hi-Five Solutionists', '5 High Street', 'Highlands', 'HI', '1254', 'AUS', '(415) 413-5182');

BEGIN TRANSACTION
    BEGIN TRY 

        INSERT INTO Orders ( OrderId, OrderDate, CustomerId )
        VALUES ( 5006, SYSDATETIME(), 1006 );
        
        INSERT INTO OrderItems ( OrderId, OrderItemId, ProductId, Quantity, ItemPrice )
        VALUES ( 5006, 1, 1, 20, 25.99 );
        
        INSERT INTO OrderItems ( OrderId, OrderItemId, ProductId, Quantity, ItemPrice )
        VALUES ( 5006, 2, 7, 120, 9.99 );

        COMMIT TRANSACTION;
        
    END TRY
    BEGIN CATCH
        ROLLBACK TRANSACTION;
    END CATCH

Ако транзакцията е неуспешна, клиентът все още ще бъде в базата данни (но без никакви поръчки). Приложението ще трябва да провери дали клиентът вече съществува, преди да извърши транзакцията.

SQL транзакция с точки за запис

Точката за запис дефинира местоположение, до което транзакцията може да се върне, ако част от транзакцията бъде условно отменена. В SQL Server ние указваме точка за запис с SAVE TRANSACTION savepoint_name (където име_на_точка на запис е името, което даваме на точката за запис).

Нека пренапишем предишния пример, за да включим точка за запис:


BEGIN TRANSACTION
    INSERT INTO Customers ( CustomerId, CustomerName, PostalAddress, City, StateProvince, ZipCode, Country, Phone )
    VALUES (1006, 'Hi-Five Solutionists', '5 High Street', 'Highlands', 'HI', '1254', 'AUS', '(415) 413-5182');
    SAVE TRANSACTION StartOrder;

    INSERT INTO Orders ( OrderId, OrderDate, CustomerId )
    VALUES ( 5006, SYSDATETIME(), 1006 );
    
    INSERT INTO OrderItems ( OrderId, OrderItemId, ProductId, Quantity, ItemPrice )
    VALUES ( 5006, 1, 1, 20, 25.99 );
    
    INSERT INTO OrderItems ( OrderId, OrderItemId, ProductId, Quantity, ItemPrice )
    VALUES ( 5006, 2, 7, 120, 9.99 );
    ROLLBACK TRANSACTION StartOrder;
COMMIT TRANSACTION;
SELECT @@TRANCOUNT;

Тук сме задали точка за запис веднага след клиентския INSERT изявление. По-късно в транзакцията използвам ROLLBACK изявление, за да инструктира транзакцията да се върне към тази точка на запис.

Когато стартирам това изявление, клиентът се вмъква, но не се въвежда информация за поръчката.

Ако транзакция се връща обратно до точка на запис, тя трябва да продължи към завършване с повече SQL оператори, ако е необходимо и COMMIT TRANSACTION изявление или трябва да бъде анулиран изцяло чрез отмяна на цялата транзакция.

Ако преместя ROLLBACK изявление обратно към предишния INSERT изявление, като това:

BEGIN TRANSACTION
    INSERT INTO Customers ( CustomerId, CustomerName, PostalAddress, City, StateProvince, ZipCode, Country, Phone )
    VALUES (1006, 'Hi-Five Solutionists', '5 High Street', 'Highlands', 'HI', '1254', 'AUS', '(415) 413-5182');
    SAVE TRANSACTION StartOrder;

    INSERT INTO Orders ( OrderId, OrderDate, CustomerId )
    VALUES ( 5006, SYSDATETIME(), 1006 );
    
    INSERT INTO OrderItems ( OrderId, OrderItemId, ProductId, Quantity, ItemPrice )
    VALUES ( 5006, 1, 1, 20, 25.99 );
    ROLLBACK TRANSACTION StartOrder;
    
    INSERT INTO OrderItems ( OrderId, OrderItemId, ProductId, Quantity, ItemPrice )
    VALUES ( 5006, 2, 7, 120, 9.99 );
COMMIT TRANSACTION;
SELECT @@TRANCOUNT;

Това води до грешка при конфликт на външен ключ. По-конкретно получавам следната грешка:

(1 row affected)
(1 row affected)
(1 row affected)
Msg 547, Level 16, State 0, Line 13
The INSERT statement conflicted with the FOREIGN KEY constraint "FK_OrderItems_Orders". The conflict occurred in database "KrankyKranes", table "dbo.Orders", column 'OrderId'.
The statement has been terminated.
(1 row affected)

Това се случи, защото, въпреки че поръчката вече беше вмъкната, тази операция беше отменена, когато се върнахме към точката за запис. След това сделката продължи към завършване. Но когато срещна крайния елемент на поръчката, нямаше съответна поръчка (защото това беше отменено) и получихме грешката.

Когато проверявам базата данни, клиентът беше вмъкнат, но отново не беше въведена информация за поръчката.

Можете да препратите към една и съща точка на запис от множество места в транзакцията, ако е необходимо.

На практика бихте използвали условно програмиране, за да върнете транзакцията в savepont.

Вложени транзакции

Можете също да вложите транзакции в други транзакции, ако е необходимо.

Като това:

BEGIN TRANSACTION Transaction1;  
    UPDATE table1 ...;
    BEGIN TRANSACTION Transaction2;
        UPDATE table2 ...;
        SELECT * from table1;
    COMMIT TRANSACTION Transaction2;
    UPDATE table3 ...;
COMMIT TRANSACTION Transaction1;

Както споменахме, точният синтаксис, който използвате за създаване на транзакция, ще зависи от вашата СУБД, така че проверете документацията на вашата СУБД за пълна картина на вашите опции при създаване на транзакции в SQL.


  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Нотация на пачи крак

  2. Поправката на грешки от 2008 R2, която разбива RCSI

  3. Използване на AT TIME ZONE за коригиране на стар отчет

  4. Основно използване на sys.dm_os_wait_stats

  5. Обединяване на файлове с данни със Statistica, част 2