Транзакции в InterSystems IRIS globals

Транзакции в InterSystems IRIS globalsСУБД InterSystems IRIS поддържа интересни структури за съхранение на данни – глобали. По същество това са многостепенни ключове с различни допълнителни екстри под формата на транзакции, бързи функции за преминаване през дървета с данни, ключалки и собствен език ObjectScript.

Прочетете повече за глобалните в поредицата от статии „Глобалните са мечове-съкровища за съхранение на данни“:

дървета. Част 1
дървета. Част 2
Разредени масиви. Част 3

Стана ми интересно как се изпълняват транзакциите в глобали, какви функции има. В крайна сметка това е напълно различна структура за съхранение на данни от обичайните таблици. Много по-ниско ниво.

Както е известно от теорията на релационните бази данни, добрата реализация на транзакциите трябва да отговаря на изискванията ACID:

A - Атомен (атомарност). Всички промени, направени в транзакцията или никакви промени, се записват.

C - Консистенция. След като транзакцията приключи, логическото състояние на базата данни трябва да бъде вътрешно последователно. В много отношения това изискване засяга програмиста, но в случая на SQL бази данни то се отнася и за външни ключове.

I - Изолирам. Транзакциите, изпълнявани паралелно, не трябва да се влияят една на друга.

D - Издръжлив. След успешно завършване на транзакция, проблеми на по-ниски нива (спиране на захранването, например) не трябва да засягат данните, променени от транзакцията.

Глобалите са нерелационни структури от данни. Те са проектирани да работят супер бързо на много ограничен хардуер. Нека да разгледаме изпълнението на транзакциите в глобалните данни с помощта официален докер образ на IRIS.

За поддържане на транзакции в IRIS се използват следните команди: TSTART, TCOMMIT, ТРОЛБЕК.

1. Атомност

Най-лесният начин за проверка е атомарността. Проверяваме от конзолата на базата данни.

Kill ^a
TSTART
Set ^a(1) = 1
Set ^a(2) = 2
Set ^a(3) = 3
TCOMMIT

След това заключаваме:

Write ^a(1), “ ”, ^a(2), “ ”, ^a(3)

Получаваме:

1 2 3

Всичко е наред. Атомичността се поддържа: всички промени се записват.

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

Нека отново проверим атомарността:

Kill ^A
TSTART
Set ^a(1) = 1
Set ^a(2) = 2
Set ^a(3) = 3

Тогава ще спрем принудително контейнера, ще го пуснем и ще видим.

docker kill my-iris

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

Може би транзакцията е била частично запазена?

WRITE ^a(1), ^a(2), ^a(3)
^
<UNDEFINED> ^a(1)

- Не, не е оцеляло.

Нека опитаме командата за връщане назад:

Kill ^A
TSTART
Set ^a(1) = 1
Set ^a(2) = 2
Set ^a(3) = 3
TROLLBACK

WRITE ^a(1), ^a(2), ^a(3)
^
<UNDEFINED> ^a(1)

Нищо също не е оцеляло.

2. Последователност

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

Например, имаме глобален ^person, в който съхраняваме личности и използваме TIN като ключ.

^person(1234567, ‘firstname’) = ‘Sergey’
^person(1234567, ‘lastname’) = ‘Kamenev’
^person(1234567, ‘phone’) = ‘+74995555555
...

За да имаме бързо търсене по фамилия и име, направихме ключ ^index.

^index(‘Kamenev’, ‘Sergey’, 1234567) = 1

За да бъде базата данни последователна, трябва да добавим персоната по следния начин:

TSTART
^person(1234567, ‘firstname’) = ‘Sergey’
^person(1234567, ‘lastname’) = ‘Kamenev’
^person(1234567, ‘phone’) = ‘+74995555555
^index(‘Kamenev’, ‘Sergey’, 1234567) = 1
TCOMMIT

Съответно, когато изтриваме, трябва да използваме и транзакция:

TSTART
Kill ^person(1234567)
ZKill ^index(‘Kamenev’, ‘Sergey’, 1234567)
TCOMMIT

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

3. Изолация

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

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

Базата данни трябва да сортира всичко в реално време. Като се има предвид, че в сериозните компании дори има специален човек, който отговаря за контрола на версиите (за обединяване на клонове, разрешаване на конфликти и т.н.), а базата данни трябва да прави всичко това в реално време, сложността на задачата и коректността на дизайн на база данни и код, който я обслужва.

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

Друг проблем е, че по време на изпълнение на транзакция (преди ангажимент), състоянието на базата данни може да е несъвместимо, така че е желателно другите транзакции да нямат достъп до несъгласуваното състояние на базата данни, което се постига в релационните бази данни по много начини: създаване на моментни снимки, многоверсионни редове и др.

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

SQL дефинира 4 нива на изолация:

  • ЧЕТЕТЕ НЕОБЩАДНО
  • ЧЕТЕНЕ АНГАЖИРАНО
  • ПОВТОРЕН ПРОЧИТ
  • СЕРИАЛИЗУЕМ

Нека разгледаме всяко ниво поотделно. Разходите за внедряване на всяко ниво нарастват почти експоненциално.

ЧЕТЕТЕ НЕОБЩАДНО - това е най-ниското ниво на изолация, но в същото време и най-бързото. Транзакциите могат да четат промени, направени една от друга.

ЧЕТЕНЕ АНГАЖИРАНО е следващото ниво на изолация, което е компромис. Транзакциите не могат да четат промените на другия преди ангажимента, но могат да четат всички промени, направени след ангажимента.

Ако имаме дълга транзакция T1, по време на която са извършени комити в транзакции T2, T3 ... Tn, които са работили със същите данни като T1, тогава при искане на данни в T1 ще получаваме всеки път различен резултат. Това явление се нарича неповторимо четене.

ПОВТОРЕН ПРОЧИТ — в това ниво на изолация нямаме феномена на неповтарящо се четене, поради факта, че за всяка заявка за четене на данни се създава моментна снимка на резултатите от данните и когато се използват повторно в същата транзакция, данните от моментната снимка се използва. Въпреки това е възможно да се четат фантомни данни на това ниво на изолация. Това се отнася до четене на нови редове, които са добавени от паралелно ангажирани транзакции.

СЕРИАЛИЗУЕМ — най-високо ниво на изолация. Характеризира се с факта, че данните, използвани по какъвто и да е начин в дадена транзакция (четене или промяна), стават достъпни за други транзакции едва след приключване на първата транзакция.

Първо, нека да разберем дали има изолация на операциите в транзакция от основната нишка. Нека отворим 2 прозореца на терминала.

Kill ^t

Write ^t(1)
2

TSTART
Set ^t(1)=2

Няма никаква изолация. Едната нишка вижда какво прави втората, която е отворила транзакцията.

Да видим дали транзакциите на различни нишки виждат какво се случва вътре в тях.

Нека отворим 2 терминални прозореца и паралелно да отворим 2 транзакции.

kill ^t
TSTART
Write ^t(1)
3

TSTART
Set ^t(1)=3

Паралелните транзакции виждат данните на другия. И така, имаме най-простото, но и най-бързото ниво на изолация, ПРОЧЕТЕТЕ НЕИЗПЪЛНЕНО.

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

Ами ако се нуждаем от по-високо ниво на изолация в операциите на глобали?

Тук трябва да помислите защо изобщо са необходими нива на изолация и как работят.

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

Можем да направим това с помощта на интелигентни заключвания в ObjectScript, които имат много различни приложения: можете да правите редовно, инкрементално, многократно заключване с командата LOCK.

По-ниските нива на изолация са компромиси, предназначени да увеличат скоростта на базата данни.

Нека да видим как можем да постигнем различни нива на изолация с помощта на брави.

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

Повече информация за метода на двуфазно блокиране на руски и английски език:

Двуфазно блокиране
Двуфазно заключване

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

Използвайки ключалки, ще създадем прозорци за видимост, в които състоянието на базата данни ще бъде последователно. И целият достъп до такива прозорци за видимост на договореното състояние ще се контролира от ключалки.

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

Изключителните заключвания се използват за промени в данните - само един процес може да вземе такова заключване. Ексклузивно заключване може да бъде взето от:

  1. Всеки процес, ако данните са безплатни
  2. Само процесът, който има споделено заключване на тези данни и е първият, който е поискал изключително заключване.

Транзакции в InterSystems IRIS globals

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

READ_COMMITTED — същността на това ниво е, че виждаме само ангажирани данни от други нишки. Ако данните в друга транзакция все още не са ангажирани, тогава виждаме старата им версия.

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

Без специални трикове няма да можем да видим старата версия на данните в IRIS, така че ще трябва да се задоволим с ключалки.

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

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

Момент на трансфер от лице 123 към лице 242:

LOCK +^person(123), +^person(242)
Set ^person(123, amount) = ^person(123, amount) - amount
Set ^person(242, amount) = ^person(242, amount) + amount
LOCK -^person(123), -^person(242)

Моментът на заявяване на паричната сума от лице 123 преди дебитиране трябва да бъде придружен от изключителен блок (по подразбиране):

LOCK +^person(123)
Write ^person(123)

И ако трябва да покажете състоянието на акаунта в личния си акаунт, тогава можете да използвате споделена ключалка или изобщо да не я използвате:

LOCK +^person(123)#”S”
Write ^person(123)

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

ПОВТОРЕН ПРОЧИТ - Това ниво на изолация позволява множество четения на данни, които могат да бъдат модифицирани чрез едновременни транзакции.

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

За щастие, операторът LOCK ви позволява да изброите подробно всички необходими ключалки, от които може да има много, в едно изявление.

LOCK +^person(123, amount)#”S”
чтение ^person(123, amount)

други операции (в момента паралелните нишки се опитват да променят ^person(123, сума), но не могат)

LOCK +^person(123, amount)
изменение ^person(123, amount)
LOCK -^person(123, amount)

чтение ^person(123, amount)
LOCK -^person(123, amount)#”S”

Когато изброявате ключалки, разделени със запетаи, те се вземат последователно, но ако направите това:

LOCK +(^person(123),^person(242))

тогава те се вземат атомно всички наведнъж.

издавам на части — ще трябва да зададем ключалки, така че в крайна сметка всички транзакции, които имат общи данни, да се изпълняват последователно. За този подход повечето брави трябва да бъдат ексклузивни и да се използват в най-малките области на глобалното за ефективност.

Ако говорим за дебитиране на средства в глобалния ^person, тогава за него е приемливо само нивото на изолация SERIALIZE, тъй като парите трябва да се изразходват строго последователно, в противен случай е възможно да изразходвате една и съща сума няколко пъти.

4. Издръжливост

Проведох тестове с твърдо рязане на контейнера с помощта на

docker kill my-iris

Базата ги понесе добре. Не бяха установени проблеми.

Заключение

За глобални, InterSystems IRIS има поддръжка на транзакции. Те са наистина атомни и надеждни. За да се осигури последователност на база данни, базирана на глобални стойности, са необходими усилия на програмиста и използването на транзакции, тъй като тя няма сложни вградени конструкции като външни ключове.

Нивото на изолация на глобалните без използване на ключалки е READ UNCOMMITED, а при използване на ключалки може да бъде гарантирано до ниво SERIALIZE.

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

Източник: www.habr.com

Добавяне на нов коментар