InterSystems IRIS 全域中的事務

InterSystems IRIS 全域中的事務InterSystems IRIS DBMS 支援有趣的資料儲存結構 - 全域變數。 本質上,這些是多層密鑰,具有事務形式的各種附加功能、用於遍歷資料樹的快速函數、鎖及其自己的 ObjectScript 語言。

在「全域變數是儲存資料的寶劍」系列文章中閱讀更多關於全域變數的資訊:

樹木。 第1部分
樹木。 第2部分
稀疏數組。 第三部分

我開始對全域事務如何實現、有哪些功能感興趣。 畢竟,這是與通常的表完全不同的儲存資料結構。 水平低很多。

從關聯式資料庫的理論可知,事務的良好實現必須滿足以下要求 ACID:

A - 原子性(原子性)。 交易中所做的所有更改或根本沒有更改都會被記錄。

C——一致性。 事務完成後,資料庫的邏輯狀態必須內部一致。 在許多方面,這項要求與程式設計師有關,但對於 SQL 資料庫,它也與外鍵有關。

我——隔離。 並行運行的事務不應互相影響。

D——耐用。 事務成功完成後,較低層級的問題(例如電源故障)不應影響事務變更的資料。

全域變數是非關係資料結構。 它們被設計為在非常有限的硬體上運行超快。 讓我們看看全域事務中使用的實現 官方 IRIS docker 映像.

為了支援 IRIS 中的事務,請使用以下命令: 啟動, 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

並行事務可以看到彼此的資料。 因此,我們得到了最簡單但最快的隔離等級:READ UNCOMMITED。

原則上,這對於全域變數來說是可以預期的,因為效能始終是優先考慮的。

如果我們在全域操作中需要更高層級的隔離怎麼辦?

在這裡,您需要考慮為什麼需要隔離等級以及它們如何運作。

最高隔離等級 SERIALIZE 意味著並行執行的交易的結果與其順序執行的結果等效,這保證了不存在衝突。

我們可以使用 ObjectScript 中的智慧鎖來做到這一點,它有很多不同的用途:您可以使用命令進行常規、增量、多重鎖定 LOCK.

較低的隔離等級是為了提高資料庫速度而設計的權衡。

讓我們看看如何使用鎖實現不同層級的隔離。

該運算符不僅允許您獲取更改資料所需的排它鎖,還允許獲取所謂的共享鎖,當多個執行緒在讀取過程中需要讀取不應被其他進程更改的資料時,可以並行獲取多個線程。

有關兩階段阻塞方法的更多資訊(俄語和英語):

兩相閉鎖
兩相鎖定

困難在於,在事務期間,資料庫的狀態可能不一致,但這種不一致的資料對其他進程是可見的。 如何避免這種情況?

使用鎖,我們將建立可見性窗口,其中資料庫的狀態將保持一致。 所有對約定狀態的可見性視窗的存取都將由鎖定控制。

相同資料上的共享鎖是可重複使用的-多個進程可以使用它們。 這些鎖可以防止其他進程更改數據,即它們用於形成一致資料庫狀態的視窗。

獨佔鎖用於資料變更-只有一個進程可以獲得這樣的鎖。 獨佔鎖可以透過以下方式取得:

  1. 如果數據免費,任何流程
  2. 只有對該資料具有共享鎖且第一個請求獨佔鎖的程序。

InterSystems IRIS 全域中的事務

可見性視窗越窄,其他行程等待它的時間就越長,但其中資料庫的狀態就越一致。

已提交讀 ——這個層級的本質是我們只看到來自其他執行緒提交的資料。 如果另一個事務中的資料尚未提交,那麼我們會看到它的舊版本。

這使我們能夠並行化工作,而不是等待鎖被釋放。

如果沒有特殊的技巧,我們將無法在 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, amount),但不能)

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

添加評論