Базы данных. Транзакции

источник материала по транзакциям
https://webformyself.com/tranzakcii-v-mysql/

План:
1 Транзакции
2 Что такое транзакции?
3 Требования к транзакционным системам
3 Применение транзакций к тестовому скрипту
4 функция заказа с транзакциями

Транзакции

Посмотрим преимущества использования транзакций при работе
с базой данных на примере оформления заказа товара.
Но перед этим, разберём устройство сайта.

Весь контент данного тестового сайта, содержится в базе
данных, а именно в таблице magazine. Также в базе содержатся
еще три таблицы – otpravleno, user_cash, zakazi.

Таблица otpravleno – используется для оплаченных и как бы
отправленных заказов (опять же виртуально), таблица user_cash –
хранит виртуальные средства пользователя, как будто у нас
на сайте действуют виртуальные денежные средства,
которыми можно оплачивать товар (это нужно просто для примера)
и наконец, таблица zakazi – все заказы оформленные на сайте.

То есть процесс оформления заказа сводится к последовательному
выполнению нескольких SQL запросов к базе данных: добавление
в таблицу zakazi – данных об оформленном заказе, снятие
денежных средств у пользователя за купленные товары
(то есть изменение данных в таблице user_cash), добавление
в таблицу otpravleno данных об оплаченных товарах и, наконец,
изменение количества товаров на складе
(то есть уменьшение количества товаров в таблице magazine,
что были куплены).

Конечно, в реальном магазине такие запросы не выполняются,
но для данного урока такой пример будет как раз кстати.
Теперь давайте кратко рассмотрим исходный код данного сайта.
Основа логики сайта – это файл functions.php, в котором описаны
все необходимые для работы сайта функции.

function connect_db() {
    //Подключаемся к серверу базы данных
    $db = mysqli_connect('localhost','Viktor','1234','for_cart');
    if(!$db) {
        exit('Error'.mysqli_error());
    }
    //Устанавливаем кодировку запросов
    mysqli_query($db,"SET NAMES cp1251");

    return $db;
}

function get_goods($db) {
    //Запрос на выборку все товаров
    $sql = "SELECT * FROM magazine";
    $result = mysqli_query($db,$sql);
    for($i = 0;$i < mysqli_num_rows($result); $i++) {
        $goods[] = mysqli_fetch_array($result);
    }

    return $goods;
}

function get_cart($db) {
    $sql = "SELECT id,title FROM magazine LIMIT 3";
    $result = mysqli_query($db,$sql);
    for($i = 0;$i < mysqli_num_rows($result); $i++) {
        $res =  mysqli_fetch_array($result,MYSQLI_ASSOC);
        $_SESSION['cart'][$res['id']] = $res['title'];
    }
}

function zakaz($db) {
    foreach($_SESSION['cart'] as $key=>$value) {
        $sql1 = "INSERT INTO zakazi (title) VALUES ('$value')";
        $result1 = mysqli_query($db,$sql1);

        $sql2 = "UPDATE user_cash SET cash=cash-(SELECT price FROM magazine WHERE id='$key')";
        $result2 = mysqli_query($db,$sql2);

        $sql3 = "INSERT INTO otpravleno (ti1tle) VALUES ('$value')";
        $result3 = mysqli_query($db,$sql3);

        $sql4 = "UPDATE magazine SET kolvo=kolvo-1 WHERE id='$key'";
        $result4 = mysqli_query($db,$sql4);
    }
}

connect_db – функция которая выполняет подключение к базе данных.

get_goods – функция которая получает из базы данных,
данные о товарах и возвращает их в виде массива.

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

zakaz — функция оформления заказа. Данная функция последовательно
выполняет запросы к базе данных о которых я говорил выше.

Из всего выше сказанного следует – для того что бы оформить
заказ необходимо что бы выполнились без ошибок SQL запросы
в функции zakaz. Но представьте себе следующую ситуацию:
к примеру пользователь выбрал товары для покупки, нажал
на кнопку Оформить заказ и начинает выполняться функция zakaz,
то есть выполняется первый запрос – сохраняем данные о заказе,
далее второй – списываем денежные средства со счета пользователя
(изменяем данные в таблице user_cash), но при выполнении
третьего запроса возникает ошибка. Что при этом получается —
заказ оформлен, деньги у пользователя сняты со счета, но товар
не был передан на отправку. Согласитесь довольно неприятная ситуация.
Поэтому давайте найдем решение данной проблемы.

Что такое транзакции?

Транзакции – это последовательность различных SQL запросов,
выполняющихся как одно целое, и не прерывающееся другими
клиентами. То есть, когда выполняются запросы транзакции,
доступ к записям никто получить не может.

Благодаря транзакциям мы можем создать группу
запросов, которые гарантированно будут выполнены без ошибок.
Если же в ходе выполнения транзакции происходит сбой,
или возникают ошибки, то результаты выполнения всех запросов
до места возникновения ошибки отменяются. Тем самым,
состояние базы данных, возвращается в исходное –
до момента выполнения запросов транзакции.

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

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

Транзакции выполняются путем ведения журнала всех изменений,
вносимых в базу данных при работе каждой транзакции.
Когда выполняется откат изменений, база данных обращается
к журналу и отменяет все выполненные запросы для текущей транзакции.

Требования к транзакционным системам

Atomicity (атомарность)

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

Consistency (консистентность, согласованность)

Это свойство даёт гарантию того, что все данные будут целостны. Данные будут корректны в соотвествии со всеми предопределёнными правилами, ограничениями, каскадами и триггерами, применёнными к БД.

Isolation (изолированность)

Гарантирует, что все транзакции будут выполняться изолированно. Ни одна транзакция не зааффектит на другую транзакцию. Другими словами, одна транзакция не сможет прочитать данные второй транзакции, которая ещё не выполнилась.

Durability (стойкость)

Durability означает, что когда транзакция будет применена, она останется в системе, даже если БД упала сразу после выполнения этой транзакции. Любые изменения, внесённые транзакцией, должны оставаться навсегда. Если БД сообщила об успешном выполнении транзакции, то она должна быть действительно применена.

Применение транзакций к тестовому скрипту

Итак, для того что бы начать работать с транзакциями,
первым делом необходимо отменить автоматическое
подтверждение выполнения SQL запросов в базе данных.
Для этого используем функцию:

mysqli_autocommit($db,FALSE);

Хочу отметить, что для работы с базой данных я использую
расширения языка php mysqli, так как расширение mysql уже
существенно устарело, и не содержит в себе, функций
для работы с транзакциями. Еще одно важное замечание:
для работы с транзакциями необходимо использовать таблицы в базе
данных типа InnoDB, тип MyISAM – транзакции не поддерживает.

Далее, после каждого запроса необходимо выполнить проверку –
успешно ли выполнен данный запрос, если возникла ошибка,
то сразу же необходимо отправить базе данных сообщение
об откате всех изменений. Для этого необходимо использовать
функцию:

mysqli_rollback($db);

Если же Вы уверены, что все запросы успешно выполнены –
значит можно отправлять сообщение commit – подтверждение
выполнения транзакции:

mysqli_commit($db);

Заметьте что при откате изменений также важно выполнять выход
из функции, в нашем случае простой возврат (return), так как
если запрос выполнен с ошибкой, нет смысла продолжать выполнение
кода функции zakaz.

Перед тем как проверить, я предлагаю функцию zakaz описать
другим способом, а именно, использовать блоки try-catch для
проверки выполненных запросов. Напомню, что блоки try-catch –
реализуют механизм обработки исключений языка PHP.

функция заказа с транзакциями

function zakaz($db) {
    mysqli_autocommit($db,FALSE);
    try {
        foreach($_SESSION['cart'] as $key=>$value) {
            $sql1 = "INSERT INTO zakazi (title) VALUES ('$value')";
            $result1 = mysqli_query($db,$sql1);
            if(!$result1) {
                throw new Exception();
            }
            $sql2 = "UPDATE user_cash SET cash=cash-(SELECT price FROM magazine WHERE id='$key')";
            $result2 = mysqli_query($db,$sql2);
            if(!$result2) {
                throw new Exception();
            }
            $sql3 = "INSERT INTO otpravleno (ti1tle) VALUES ('$value')";
            $result3 = mysqli_query($db,$sql3);
            if(!$result3) {
                throw new Exception();
            }
            $sql4 = "UPDATE magazine SET kolvo=kolvo-1 WHERE id='$key'";
            $result4 = mysqli_query($db,$sql4);
            if(!$result4) {
                throw new Exception();
            }
        }
    }
    catch(Exception $e) {
        mysqli_rollback($db);
    }
    mysqli_commit($db);
}

Смотрите, весь код цикла мы заключаем в блок try, а в каждом
блоке if() мы с Вами генерируем исключение. То есть когда
выполнится, хотя бы один блок if, сразу же будет создано
исключение, и скрипт мгновенно будет перенаправлен в блок catch,
который в свою очередь и выполнит откат всех изменений.
Если же все нормально, и ни один из блоков if не выполнится,
значит нужно просто подтвердить транзакцию, что мы собственно и делаем.

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

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

Ваш адрес email не будет опубликован. Обязательные поля помечены *