Транзакції в MySQL

673

Від автора: дуже часто при вирішенні тих чи інших завдань необхідно виконувати кілька запитів до бази даних. Але як же вчинити, якщо це дуже важливі запити, і кожен з них повинен обов’язково виконається. Іншими словами, якщо при виконанні запиту сталася помилка або запит не виконується по іншим стороннім причин – це може порушити роботу всього скрипта в цілому і призвести до досить неприємних результатів. Тому в даному уроці ми з Вами розглянемо транзакції, які забезпечать 100% виконання всіх запитів в базі даних або не виконання їх взагалі при виникненні помилок.

Транзакції в MySQLТранзакції в MySQL

1. Постановка задачі

Для сьогоднішнього уроку я підготував ось таку HTML сторінку:

Транзакції в MySQL

Як Ви бачите, — це лише одна сторінка інтернет-магазину. А саме сторінка відображення товарів із каталогу – центральний блок, і кошик, у якій міститься декілька товарів.

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

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

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

Отже, основа логіки сайту – це файл functions.php, в якому описані всі необхідні для роботи функції сайту.

Коротко про кожній функції:

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

get_goods – функція яка отримує з бази даних, дані про товари і повертає їх у вигляді масиву.

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

zakaz — функція оформлення замовлення. Ця функція послідовно виконує запити до бази даних про які я говорив вище.

Також частиною вихідного коду, є файли index.php і cart.php, але їх код я зараз наводити не буду, так як він дуже простий. Дані файли, Ви зможете знайти в исходниках до даного уроку.

З усього вище сказаного випливає – для того що б оформити замовлення необхідно що б виконано без помилок SQL запити функції zakaz. Але уявіть собі наступну ситуацію: наприклад користувач вибрав товари для покупки, натиснув на кнопку Оформити замовлення і починає виконуватися функція zakaz, тобто виконується перший запит – зберігаємо дані про замовлення, далі другий – списуємо грошові кошти з рахунку користувача (змінюємо дані в таблиці user_cash), але при виконанні третього запиту сталася помилка. Що при цьому виходить — замовлення оформлений, гроші у користувача зняті з рахунку, але товар не був переданий на відправку. Погодьтеся досить неприємна ситуація. Тому давайте знайдемо рішення даної проблеми.

2. Що таке транзакції?

Транзакція – це послідовність різних SQL запитів, що виконуються як одне ціле, і не переривається іншими клієнтами. Тобто, коли виконуються запити транзакції, доступ до записів ніхто отримати не може.

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

Наприклад, якщо Вам потрібно виконати підряд 100 SQL запитів, і Ви при цьому використовуєте транзакції, то, якщо в ході виконання цих запитів хоча б один з них виконається з помилкою, або не виконується взагалі – стан бази даних повернеться до свого вихідного стану на момент виконання даних запитів. І тільки після успішного виконання останньої 100го запиту всі зміни вступлять в силу.

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

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

3. Застосування транзакцій до тестового скрипту

Отже, для того що б почати працювати з транзакціями, насамперед необхідно скасувати автоматичне підтвердження виконання SQL-запитів в базі даних. Для цього використовуємо функцію:

mysqli_autocommit($db,FALSE);

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

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

mysqli_rollback($db);

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

mysqli_commit($db);

Тепер давайте змінимо функцію zakaz таким чином, що б надсилання та виконання запитів здійснювалося з допомогою транзакцій.

function zakaz($db) {
mysqli_autocommit($db,FALSE);
foreach($_SESSION[‘кошик’] as $key=>$value) {
$sql1 = “INSERT INTO zakazi (title) VALUES (‘$value’)”;
$result1 = mysqli_query($db,$sql1);
if(!$result1) {
mysqli_rollback($db);
return;
}
$sql2 = “UPDATE user_cash SET cash=cash-(SELECT price FROM magazine WHERE id=’$key’)”;
$result2 = mysqli_query($db,$sql2);
if(!$result2) {
mysqli_rollback($db);
return;
}
$sql3 = “INSERT INTO otpravleno (ti1tle) VALUES (‘$value’)”;
$result3 = mysqli_query($db,$sql3);
if(!$result3) {
mysqli_rollback($db);
return;
}
$sql4 = “UPDATE magazine SET kolvo=kolvo-1 WHERE id=’$key'”;
$result4 = mysqli_query($db,$sql4);
if(!$result4) {
mysqli_rollback($db);
return;
}
}
mysqli_commit($db);
}

Як Ви бачите, виконуємо все, про що я говорив вище, тобто відключаємо автоподтверждение виконуваних запитів (mysqli_autocommit($db,FALSE)), потім після кожного запиту виконуємо перевірку наявності помилок, якщо ж є помилки негайно виконуємо відкат всіх змін (mysqli_rollback($db)). Тут також важливо виконувати вихід їх функції, в нашому випадку простого повернення (return), так як якщо запит виконаний з помилкою, немає сенсу продовжувати виконання коду функції zakaz. Якщо ж всі запити виконані успішно, підтверджуємо транзакцію (mysqli_commit($db);).

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

function zakaz($db) {
mysqli_autocommit($db,FALSE);
try {
foreach($_SESSION[‘кошик’] 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 не виконується, значить, треба просто підтвердити транзакцію, що ми власне і робимо.

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

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