3.4. 事務
事務是所有資料庫系統的基礎概念。事務最重要的一點是它将多個步驟捆綁成了一個單一的、要麼全完成要麼全不完成的操作。步驟之間的中間狀态對于其他并發事務是不可見的,并且如果有某些錯誤發生導緻事務不能完成,則其中任何一個步驟都不會對資料庫造成影響。
例如,考慮一個儲存着多個客戶賬戶餘額和支行總存款額的銀行資料庫。假設我們希望記錄一筆從Alice的賬戶到Bob的賬戶的額度為100.00美元的轉賬。在最大程度地簡化後,涉及到的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');
這些指令的細節在這裡并不重要,關鍵點是為了完成這個相當簡單的操作涉及到多個獨立的更新。我們的銀行職員希望確定這些更新要麼全部發生,或者全部不發生。當然不能發生因為系統錯誤導緻Bob收到100美元而Alice并未被扣款的情況。Alice當然也不希望自己被扣款而Bob沒有收到錢。我們需要一種保障,當操作中途某些錯誤發生時已經執行的步驟不會産生效果。将這些更新組織成一個事務就可以給我們這種保障。一個事務被稱為是原子的:從其他事務的角度來看,它要麼整個發生要麼完全不發生。
我們同樣希望能保證一旦一個事務被資料庫系統完成并認可,它就被永久地記錄下來且即便其後發生崩潰也不會被丢失。例如,如果我們正在記錄Bob的一次現金提款,我們當然不希望他剛走出銀行大門,對他賬戶的扣款就消失。一個事務型資料庫保證一個事務在被報告為完成之前它所做的所有更新都被記錄在持久存儲(即磁盤)。
事務型資料庫的另一個重要性質與原子更新的概念緊密相關:當多個事務并發運作時,每一個都不能看到其他事務未完成的修改。例如,如果一個事務正忙着總計所有支行的餘額,它不會隻包括Alice的支行的扣款而不包括Bob的支行的存款,或者反之。是以事務的全做或全不做并不隻展現在它們對資料庫的持久影響,也展現在它們發生時的可見性。一個事務所做的更新在它完成之前對于其他事務是不可見的,而之後所有的更新将同時變得可見。
在PostgreSQL中,開啟一個事務需要将SQL指令用
BEGIN
和
COMMIT
指令包圍起來。是以我們的銀行事務看起來會是這樣:
BEGIN;
UPDATE accounts SET balance = balance - 100.00
WHERE name = 'Alice';
-- etc etc
COMMIT;
如果,在事務執行中我們并不想送出(或許是我們注意到Alice的餘額不足),我們可以發出
ROLLBACK
指令而不是
COMMIT
指令,這樣所有目前的更新将會被取消。
PostgreSQL實際上将每一個SQL語句都作為一個事務來執行。如果我們沒有發出
BEGIN
指令,則每個獨立的語句都會被加上一個隐式的
BEGIN
以及(如果成功)
COMMIT
來包圍它。一組被
BEGIN
COMMIT
包圍的語句也被稱為一個事務塊。
注意
某些用戶端庫會自動發出
BEGIN
COMMIT
指令,是以我們可能會在不被告知的情況下得到事務塊的效果。具體請檢視所使用的接口文檔。
也可以利用儲存點來以更細的粒度來控制一個事務中的語句。儲存點允許我們有選擇性地放棄事務的一部分而送出剩下的部分。在使用
SAVEPOINT
定義一個儲存點後,我們可以在必要時利用
ROLLBACK TO
復原到該儲存點。該事務中位于儲存點和復原點之間的資料庫修改都會被放棄,但是早于該儲存點的修改則會被儲存。
在復原到儲存點之後,它的定義依然存在,是以我們可以多次復原到它。反過來,如果确定不再需要復原到特定的儲存點,它可以被釋放以便系統釋放一些資源。記住不管是釋放儲存點還是復原到儲存點都會釋放定義在該儲存點之後的所有其他儲存點。
所有這些都發生在一個事務塊内,是以這些對于其他資料庫會話都不可見。當送出整個事務塊時,被送出的動作将作為一個單元變得對其他會話可見,而被復原的動作則永遠不會變得可見。
記住那個銀行資料庫,假設我們從Alice的賬戶扣款100美元,然後存款到Bob的賬戶,結果直到最後才發現我們應該存到Wally的賬戶。我們可以通過使用儲存點來做這件事:
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;
當然,這個例子是被過度簡化的,但是在一個事務塊中使用儲存點存在很多種控制可能性。此外,
ROLLBACK TO
是唯一的途徑來重新控制一個由于錯誤被系統置為中斷狀态的事務塊,而不是完全復原它并重新啟動。
本文轉自PostgreSQL中文社群,原文連結: