Трансакция наричаме последователност от SQL заявки, които трябва да изпълняват условието: или всичките да бъдат изпълнени или нито една от тях да не бъде изпълнена.
Може да дадем класически пример с банковите трансакции. Първо създаваме таблица за клиенти, после таблица за сметки.
Ще слагаме само основни колони, тъй като за нашата цел други няма да са ни необходими.
create database if not exists transaction_test; use transaction_test; drop table if exists customer_accounts; drop table if exists customers; create table customers( id int auto_increment primary key, name varchar(255) not null, address varchar(255) ); create table if not exists customer_accounts( id int auto_increment primary key, amount double not null, currency varchar(10), customer_id int not null, constraint foreign key (customer_id) references customers(id) on delete restrict on update cascade ); INSERT INTO `transaction_test`.`customers` (`name`, `address`) VALUES ('Ivan Petrov Iordanov', 'Sofia, Krasno selo 1000'); INSERT INTO `transaction_test`.`customers` (`name`, `address`) VALUES ('Stoyan Pavlov Pavlov', 'Sofia, Liuylin 7, bl. 34'); INSERT INTO `transaction_test`.`customers` (`name`, `address`) VALUES ('Iliya Mladenov Mladenov', 'Sofia, Nadezhda 2, bl 33'); INSERT INTO `transaction_test`.`customer_accounts` (`amount`, `currency`, `customer_id`) VALUES ('5000', 'BGN', '1'); INSERT INTO `transaction_test`.`customer_accounts` (`amount`, `currency`, `customer_id`) VALUES ('10850', 'EUR', '1'); INSERT INTO `transaction_test`.`customer_accounts` (`amount`, `currency`, `customer_id`) VALUES ('1450000', 'BGN', '2'); INSERT INTO `transaction_test`.`customer_accounts` (`amount`, `currency`, `customer_id`) VALUES ('17850', 'EUR', '2');
Например, ако искаме да прехвърлим 50 лева от акаунт 1 в акаунт 2, то трябва да изпълним следните две заявки:
use transaction_test; UPDATE customer_accounts SET amount = amount - 50 WHERE id = 1; UPDATE customer_accounts SET amount = amount + 50 WHERE id = 2;
Какво обаче ще се случи, ако първата заявка се изпълни, но поради някаква причина втората не (например възникне грешка)?
Тук на помощ ни идват именно трансакциите – те гарантират, че ако някоя заявка не се изпълни, то данните ще бъдат възстановени в първоначалния им вид.
Синтаксис:
Групирането на заявки в трансакция се изпълнява изключително лесно:
Единствено трябва да оградим данните с BEGIN (започване на трансакция) и с COMMIT (край на трансакция).
Горният пример ще изглежда така:
begin; use transaction_test; UPDATE customer_accounts SET amount = amount - 50 WHERE id = 1; UPDATE customer_accounts SET amount = amount + 50 WHERE id = 2; commit;
Ако някоя от заявките пропадне, то се прави т.нар. ROLLBACK. За целта sql сървъра използва innodb log файл, в който се записват старите данни, преди изпълнението на всяка заявка.
Нека видим какво има в customer_accounts:
Изпълняваме долната заявка:
begin; use transaction_test; UPDATE customer_accounts SET amount = amount - 50 WHERE id = 1; UPDATE customer_accounts SET amount = amount + 50 WHERE id = 2;
Съвсем умишлено няма да правим commit. Сега се връзваме към сървъра през друг клиент и проверяваме какво се случва:
Даваме commit; през MySQL Workbench-a и отново проверяваме през другия клиент:
Както виждате след commit вече промените са приложени върху таблиците и са видими за всички.
Трансакциите трябва да отговарят на условие за консистентно четене- тоест, последната сигурно отразена в базата информация. Това означава, че всеки SELECT чете данните, записани точно след последния COMMIT.
Когато един или повече редове се модифицират в рамките на незавършена транзакция, то те биват заключвани докато не се подаде команда COMMIT или ROLLBACK. Всяка друга заявка, опитваща се да достъпи такива заключени редове, моментално попада в т.нар. „спящ“ (idle-режим). Съществува определен timeout, след който заявката се анулира, ако съответните редове все още не са отключени и транзакцията прави rollback. Заключването на връзките важи само за заявки от тип UPDATE, INSERT и DELETE. По подразбиране то не е валидно за заявки от тип SELECT, но може и да бъде променено.
Даниел Джолев
Здравейте,
със заявката по-горе искаме да прехвърлим 50 лв. от акаунт 1 в акаунт 2. Добре, но акаунт 1 е в лева, а акаунт 2 – в евро. След като заявката се изпълни в акаунт 2 постъпват 50 евро, а не 50 лв., бихте ли обяснили защо това е така ?
Благодаря !
Здравейте,
Съществен въпрос е. В реална ситуация ще трябва да се вземе предвид валутата. Ако тя е същата, няма проблем. Ако е различна, ще трябва да се направи превалутиране на прехвърляната сума, съгласно валутата на сметката, в която се прехвърля. Това е просто пример, в който местим пари от един акаунт в друг и наистина не се съобразяваме с валутата, което по същество е грешно. В реална ситуация ще има процедура, която ще се погрижи да обхване всички особености на трансфера на пари от една сметка в друга. Ще има и смислени условия кога да се подаде commit и кога да се подаде rollback.
Поздрави!
решение на задачи от часа:
1) имена, класове, тел на всички които тренират футбол
SELECT students.name, students.class, students.phone
FROM students JOIN student_sport
ON students.id = student_sport.student_id
JOIN sportGroups
ON student_sport.sportGroup_id = sportGroups.id
JOIN sports
ON sportGroups.sport_id = sports.id
WHERE sports.name = ‘Football’;
2) изведете имената на всички треньори по волейбол
SELECT coaches.name
FROM coaches JOIN sportGroups
ON (coaches.id = sportGroups.coach_id)
JOIN sports
ON (sportGroups.sport_id = sports.id)
WHERE sports.name = ‘Volleyball’;
3) изведете името на треньора и спорта който тренира с име Илиан Георгиев
SELECT DISTINCT coaches.name, sports.name
FROM coaches JOIN sportGroups
ON coaches.id = sportGroups.coach_id
JOIN sports
ON sportGroups.sport_id = sports.id WHERE coaches.name = ‘Ilian Georgiev’;
4) изведете сумите от платените през годините такси на учениците по месеци, но само за такси над 700 лева и треньор с егн 8504132420
SELECT students.name, SUM(taxesPayments.paymentAmount) AS SumOfTaxes, taxesPayments.year
FROM students JOIN taxesPayments
ON students.id = taxesPayments.student_id
GROUP BY student_id, year
JOIN sportGroups
ON taxesPayments.group_id = sportGroups.id
JOIN coaches
ON sportGroups.coach_id = coaches.id
WHERE coaches.egn = ‘8504132420’
HAVING SumOfTaxes >= 700;
5) изведете броя на студентите във всяка една от групите
SELECT sportGroups.location, sportGroups.dayOfWeek, sportGroups.hourOfTraining , COUNT(student_sport.student_id) AS Count_of_students_by_group
FROM student_sport JOIN sportGroups
ON student_sport.sportGroup_id = sportGroups.id
GROUP BY sportGroup_id;
Десимир Красимиров Костадинов , 38 гр. , фак. № 121215122
задачите от упражнението по БД от 27. 03. 2017 г.
1 зад. Изведете име, клас и телефонен номер на всички , които тренират футбол
Решения:
начин 1 :
select students.name,students.class, students.phone
from students
join sports
on students.id in(
select student_id
from student_sports
where sportGroup_id in (select id
from sportgroups
where sportgroups.sport_id=sports.id)
)
where sports.name=’Football’;
начин 2 :
select students.name,students.class, students.phone
from students
join student_sport
on students.id=student_sport.student_id
join sportgroups
on student_sport.sportGroup_id=sportgroups.id
join sports
on sportgroups.sport_id=sports.id
where sports.name=’Football’;
2 зад. Изведете имената на всички треньори по волейбол
Решение :
select coaches.name as nameOfCoach
from coaches
join sportgroups
on coaches.id=sportgroups.coach_id
join sports
on sportgroups.sport_id=sports.id
where sports.name=’Volleyball’;
3 зад. Изведете името на треньор и име на спорт за студента Илиян Иванов
Решение :
select distinct coaches.name as nameOfCoach, sports.name as nameOfSport
from coaches join sportgroups
on coaches.id = sportgroups.coach_id
join sports
on sportgroups.sport_id = sports.id
where sportgroups.id in(
select sportGroup_id
from student_sport
where student_id in(
select id
from students
where students.name = “Iliyan Ivanov”));
4 зад. Изведете сумите от платените през годините такси на студентите по месеци, но само за такси над 700 лева и треньор с егн 8203142724
Решение :
select students.name, sum(taxesPayments.paymentAmount) as sumOfTaxes, taxesPayments.year
from students
join taxesPayments
on students.id = taxesPayments.student_id
group by student_id, year
join sportgroups
on taxesPayments.group_id = sportGroups.id
join coaches
on sportGroups.coach_id = coaches.id
where coaches.egn = ‘8203142724’
having sumOfTaxes >= 700;
5 зад. Изведете броя на студентите във всяка една от групите
Решение:
select student_sport.sportGroup_id, count(student_id) as numberOfStudents
from students_sports
group by sportgroup_id;