Трансакции, конкурентно извршување и заклучување на ресурсите
Трансакции
Трансакциите се фунфаментален концепт на системите за управување со бази на податоци, што обезбедува атомичност, конзистентост и изолација на состојбата во базата, додека се извршуваат повеќе операции групирани во единствена логичка целина. Ваквата логичка целина (трансакција) или се извршува во целост или не се извршува воопшто.
Во PostgreSQL клучните зборови кои се користат во контекст на трансакции за започнување, потврдување и поништување на трансакциите се BEGIN
, COMMIT
и ROLLBACK
соодветно. Покрај ова, PostgreSQL поддржува и парцијално зачувување на состојбата во рамки на трансакцијата со SAVEPOINT
.
Во однос на „типови“ на трансакции, односно нивоа на изолираност нa трансакции, PostgreSQL поддржува четири, и тоа:
- READ UNCOMMITED
Ова е најниското ниво на изолација и нешто што практично излегува од концептот на трансакција бидејќи до некој степен ја нарушува конзистентноста во рамки на една трансакција. Имено, ова ниво на изолација дава можност во рамки на трансакцијта да се има пристап до непотврдени промени од други трансакции. Па така, доколку во една сесија се изврши следниот код:
BEGIN TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; SELECT balance FROM accounts WHERE name = 'User Test'; -- Резултат: 500
Притоа, во друга сесија се изврши следниот код:
BEGIN; UPDATE accounts SET balance = balance + 500 WHERE name = 'User Test'; -- сѐ уште не се потврдува трансакцијата
Доколку се вратиме во првата сесија и го извршиме повторно истиот SELECT, резултатот овој пат ќе биде 1000.
- READ COMMITED
Ова е стандардното однесување и ниво на изолација на трансакциите кое го добиваме без дополнителни клучни зборови при започнување на трансакција. Промените направени во рамки на трансакциите се достапни по нивно потврдување
- REPEATABLE READ
Ова ниво на изолација обезбедува практично замрзнување на состојбата на базата во моментот на започнување на трансакцијата. Доколку по започнување на трансакција со ниво на изолација REPEATABLE READ
, во друга сесија се започне и потврди друга трансакција, резултатот од истата нема да биде видлив во рамки на трансакцијата започната во првата сесија.
Во рамки на базата на системот dbLearnStar, едно сценарио каде ова би бил валиден пример е следното. Доколку за време на испит професорот има потреба да види резултати до тој момент, наместо истото да го прави со рачно филтрирање според датумот на поднесок, истиот ефект може да го постигне преку започнување трансакција со ниво на изолација REPEATABLE READ
.
BEGIN TRANSACTION ISOLATION LEVEL REPEATABLE READ; -- Пример прашање: Број на испратени решенија од почетокот на испитот до моментот на започнување на трансакцијата SELECT COUNT(*) as total_submissions FROM student_submit_solution sss JOIN student_started_test sst ON sss.student_started_test_id = sst.student_started_test_id JOIN test_instance ti ON sst.test_instance_id = ti.test_instance_id WHERE ti.test_collection_id = 10;
Во меѓувреме студентите можат непречено да работат и да испраќаат решенија. Доколку трансакцијата е отворена подолго време и професорот го изврши истиот прашалник и по 10 минути и притоа студентите имаат испратено нови решенија, резултатот ќе биде ист.
- SERIALIZABLE
Ова е највисокото ниво на изолација кое го нуди PostgreSQL. Пристапот за читање е практично ист како и со READ COMMITED, но разликата е во начинот на кој се справува со менување на податоци во редиците. Доколку во една сесија се изврши следново:
BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE; UPDATE inventory SET quantity = 1 where product_id = 1;
И во втора сесија се изврши:
BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE; UPDATE inventory SET quantity = 2 where product_id = 1; COMMIT;
Резултатот ќе биде грешка поради тоа што SERIALIZABLE не дозволува конкурентно менување на исти записи од две трансакции (сесии).
Конкурентно извршување и пристап и заклучување
PostgreSQL користи софистициран систем на заклучувања за да обезбеди конзистентност на податоците при конкурентен пристап. Заклучувањата се автоматски механизам што го контролираат пристапот до ресурсите и спречуваат конфликти помеѓу трансакциите, особено кога се извршуваат операции кои прават промени. Во PostgreSQL заклчувањата може да бидат на ниво на база, табела или ред. Иако е возможно експлицитно, со мануелно извршена команда да направиме заклучување, овој концепт генерално е управуван автоматски од системот за управување со бази на податоци.
Тип заклучување | Автоматски се поставува при | Дозволува конкурентно | Блокира | Типична употреба |
---|---|---|---|---|
ACCESS SHARE | SELECT | Сите освен ACCESS EXCLUSIVE | ACCESS EXCLUSIVE | Читање на податоци |
ROW SHARE | SELECT FOR UPDATE/SHARE | Повеќето операции | EXCLUSIVE, ACCESS EXCLUSIVE | Читање со намера за ажурирање |
ROW EXCLUSIVE | INSERT, UPDATE, DELETE | Читање и други промени | SHARE и повисоки | Стандардни промени |
SHARE UPDATE EXCLUSIVE | VACUUM, ANALYZE | Читање и ROW EXCLUSIVE | SHARE UPDATE EXCLUSIVE и повисоки | Одржување |
SHARE | CREATE INDEX | Читање и други SHARE | ROW EXCLUSIVE и повисоки | Индексирање |
SHARE ROW EXCLUSIVE | Ретко | Само читање | ROW EXCLUSIVE и повисоки | Специјални операции |
EXCLUSIVE | ALTER TABLE | Само читање | Сè освен читање | Структурни промени |
ACCESS EXCLUSIVE | DROP, TRUNCATE, VACUUM FULL | Ништо | Сите | Сите други операции |
Мошне корисна примена во системот за испити по бази може да има концептот на SELECT FOR UPDATE
и SKIP LOCKED
. Доколку постоечкото решение со прошири и е потребно последно испратените решенија на студентите за секоја од задачите, по завршување на испитот да се реизвршат врз посебна шема/база и притоа сакаме ова извршување да е паралелизирано, на повеќе инстанци (или на повеќе нитки), со примена на овој концепт можеме да го постигнеме ефектот на редица, така што секое решение кое треба да се реизврши ќе го додадеме во една табела која што ќе ја претставува редицата. Секоја од инстанците/процесите каде се извршуваат решенијата ќе го зема својот следен job со следниот прашалник
SELECT * FROM submissions_queue WHERE status = 'PENDING' ORDER BY id LIMIT 1 FOR UPDATE SKIP LOCKED
Со ова практично добиваме две нивоа на контрола, и тоа, со користење на 'FOR UPDATE', ја заклчуваме таа редица за евентуална промена од друга сесија, а со SKIP LOCKED
овозможуваме секој од процесите да го земе следното решение во редицата коешто не е испроцесирано, притоа избегнувајќи ги веќе заклучените