PostgreSQL 事務(wù)

2021-08-25 17:12 更新

事務(wù)是所有數(shù)據(jù)庫系統(tǒng)的基礎(chǔ)概念。事務(wù)最重要的一點(diǎn)是它將多個(gè)步驟捆綁成了一個(gè)單一的、要么全完成要么全不完成的操作。步驟之間的中間狀態(tài)對(duì)于其他并發(fā)事務(wù)是不可見的,并且如果有某些錯(cuò)誤發(fā)生導(dǎo)致事務(wù)不能完成,則其中任何一個(gè)步驟都不會(huì)對(duì)數(shù)據(jù)庫造成影響。

例如,考慮一個(gè)保存著多個(gè)客戶賬戶余額和支行總存款額的銀行數(shù)據(jù)庫。假設(shè)我們希望記錄一筆從Alice的賬戶到Bob的賬戶的額度為100.00美元的轉(zhuǎn)賬。在最大程度地簡化后,涉及到的SQL命令是:

UPDATE accounts SET balance = balance - 100.00
    WHERE name = 'Alice';
UPDATE branches SET balance = balance - 100.00
    WHERE name = (SELECT branch_name FROM accounts WHERE name = 'Alice');
UPDATE accounts SET balance = balance + 100.00
    WHERE name = 'Bob';
UPDATE branches SET balance = balance + 100.00
    WHERE name = (SELECT branch_name FROM accounts WHERE name = 'Bob');

這些命令的細(xì)節(jié)在這里并不重要,關(guān)鍵點(diǎn)是為了完成這個(gè)相當(dāng)簡單的操作涉及到多個(gè)獨(dú)立的更新。我們的銀行職員希望確保這些更新要么全部發(fā)生,或者全部不發(fā)生。當(dāng)然不能發(fā)生因?yàn)橄到y(tǒng)錯(cuò)誤導(dǎo)致Bob收到100美元而Alice并未被扣款的情況。Alice當(dāng)然也不希望自己被扣款而Bob沒有收到錢。我們需要一種保障,當(dāng)操作中途某些錯(cuò)誤發(fā)生時(shí)已經(jīng)執(zhí)行的步驟不會(huì)產(chǎn)生效果。將這些更新組織成一個(gè)事務(wù)就可以給我們這種保障。一個(gè)事務(wù)被稱為是原子的:從其他事務(wù)的角度來看,它要么整個(gè)發(fā)生要么完全不發(fā)生。

我們同樣希望能保證一旦一個(gè)事務(wù)被數(shù)據(jù)庫系統(tǒng)完成并認(rèn)可,它就被永久地記錄下來且即便其后發(fā)生崩潰也不會(huì)被丟失。例如,如果我們正在記錄Bob的一次現(xiàn)金提款,我們當(dāng)然不希望他剛走出銀行大門,對(duì)他賬戶的扣款就消失。一個(gè)事務(wù)型數(shù)據(jù)庫保證一個(gè)事務(wù)在被報(bào)告為完成之前它所做的所有更新都被記錄在持久存儲(chǔ)(即磁盤)。

事務(wù)型數(shù)據(jù)庫的另一個(gè)重要性質(zhì)與原子更新的概念緊密相關(guān):當(dāng)多個(gè)事務(wù)并發(fā)運(yùn)行時(shí),每一個(gè)都不能看到其他事務(wù)未完成的修改。例如,如果一個(gè)事務(wù)正忙著總計(jì)所有支行的余額,它不會(huì)只包括Alice的支行的扣款而不包括Bob的支行的存款,或者反之。所以事務(wù)的全做或全不做并不只體現(xiàn)在它們對(duì)數(shù)據(jù)庫的持久影響,也體現(xiàn)在它們發(fā)生時(shí)的可見性。一個(gè)事務(wù)所做的更新在它完成之前對(duì)于其他事務(wù)是不可見的,而之后所有的更新將同時(shí)變得可見。

PostgreSQL中,開啟一個(gè)事務(wù)需要將SQL命令用BEGINCOMMIT命令包圍起來。因此我們的銀行事務(wù)看起來會(huì)是這樣:

BEGIN;
UPDATE accounts SET balance = balance - 100.00
    WHERE name = 'Alice';
-- etc etc
COMMIT;

如果,在事務(wù)執(zhí)行中我們并不想提交(或許是我們注意到Alice的余額不足),我們可以發(fā)出ROLLBACK命令而不是COMMIT命令,這樣所有目前的更新將會(huì)被取消。

PostgreSQL實(shí)際上將每一個(gè)SQL語句都作為一個(gè)事務(wù)來執(zhí)行。如果我們沒有發(fā)出BEGIN命令,則每個(gè)獨(dú)立的語句都會(huì)被加上一個(gè)隱式的BEGIN以及(如果成功)COMMIT來包圍它。一組被BEGINCOMMIT包圍的語句也被稱為一個(gè)事務(wù)塊。

注意

某些客戶端庫會(huì)自動(dòng)發(fā)出BEGINCOMMIT命令,因此我們可能會(huì)在不被告知的情況下得到事務(wù)塊的效果。具體請(qǐng)查看所使用的接口文檔。

也可以利用保存點(diǎn)來以更細(xì)的粒度來控制一個(gè)事務(wù)中的語句。保存點(diǎn)允許我們有選擇性地放棄事務(wù)的一部分而提交剩下的部分。在使用SAVEPOINT定義一個(gè)保存點(diǎn)后,我們可以在必要時(shí)利用ROLLBACK TO回滾到該保存點(diǎn)。該事務(wù)中位于保存點(diǎn)和回滾點(diǎn)之間的數(shù)據(jù)庫修改都會(huì)被放棄,但是早于該保存點(diǎn)的修改則會(huì)被保存。

在回滾到保存點(diǎn)之后,它的定義依然存在,因此我們可以多次回滾到它。反過來,如果確定不再需要回滾到特定的保存點(diǎn),它可以被釋放以便系統(tǒng)釋放一些資源。記住不管是釋放保存點(diǎn)還是回滾到保存點(diǎn)都會(huì)釋放定義在該保存點(diǎn)之后的所有其他保存點(diǎn)。

所有這些都發(fā)生在一個(gè)事務(wù)塊內(nèi),因此這些對(duì)于其他數(shù)據(jù)庫會(huì)話都不可見。當(dāng)提交整個(gè)事務(wù)塊時(shí),被提交的動(dòng)作將作為一個(gè)單元變得對(duì)其他會(huì)話可見,而被回滾的動(dòng)作則永遠(yuǎn)不會(huì)變得可見。

記住那個(gè)銀行數(shù)據(jù)庫,假設(shè)我們從Alice的賬戶扣款100美元,然后存款到Bob的賬戶,結(jié)果直到最后才發(fā)現(xiàn)我們應(yīng)該存到Wally的賬戶。我們可以通過使用保存點(diǎn)來做這件事:

BEGIN;
UPDATE accounts SET balance = balance - 100.00
    WHERE name = 'Alice';
SAVEPOINT my_savepoint;
UPDATE accounts SET balance = balance + 100.00
    WHERE name = 'Bob';
-- oops ... forget that and use Wally's account
ROLLBACK TO my_savepoint;
UPDATE accounts SET balance = balance + 100.00
    WHERE name = 'Wally';
COMMIT;

當(dāng)然,這個(gè)例子是被過度簡化的,但是在一個(gè)事務(wù)塊中使用保存點(diǎn)存在很多種控制可能性。此外,ROLLBACK TO是唯一的途徑來重新控制一個(gè)由于錯(cuò)誤被系統(tǒng)置為中斷狀態(tài)的事務(wù)塊,而不是完全回滾它并重新啟動(dòng)。

以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)