?
Dieses Dokument verwendet PHP-Handbuch für chinesische Websites Freigeben
SQL標(biāo)準(zhǔn)用三個(gè)必須在并發(fā)的事務(wù)之間避免的現(xiàn)像定義了四個(gè)級(jí)別的事務(wù)隔離。 這些不希望發(fā)生的現(xiàn)像是:
一個(gè)事務(wù)讀取了另一個(gè)未提交事務(wù)寫入的數(shù)據(jù)。
一個(gè)事務(wù)重新讀取前面讀取過(guò)的數(shù)據(jù),發(fā)現(xiàn)該數(shù)據(jù)已經(jīng)被另一個(gè)已提交事務(wù)修改。
一個(gè)事務(wù)重新執(zhí)行一個(gè)查詢,返回一套符合查詢條件的行, 發(fā)現(xiàn)這些行因?yàn)槠渌罱峤坏氖聞?wù)而發(fā)生了改變。
這四種隔離級(jí)別和對(duì)應(yīng)的行為在Table 13-1里描述。
Table 13-1. SQL事務(wù)隔離級(jí)別
隔離級(jí)別 | 臟讀 | 不可重復(fù)讀 | 幻讀 |
---|---|---|---|
讀未提交 | 可能 | 可能 | 可能 |
讀已提交 | 不可能 | 可能 | 可能 |
重復(fù)讀 | 不可能 | 不可能 | 可能 |
可串行化 | 不可能 | 不可能 | 不可能 |
在PostgreSQL里,你可以請(qǐng)求四種可能的事務(wù)隔離級(jí)別中的任意一種。 但是在內(nèi)部,實(shí)際上只有兩種獨(dú)立的隔離級(jí)別,分別對(duì)應(yīng)讀已提交和可串行化。 如果你選擇了讀未提交的級(jí)別,實(shí)際上你用的是讀已提交,在你選擇可重復(fù)讀級(jí)別的時(shí)候, 實(shí)際上你用的是可串行化,所以實(shí)際的隔離級(jí)別可能比你選擇的更嚴(yán)格。這是 SQL 標(biāo)準(zhǔn)允許的: 四種隔離級(jí)別只定義了哪種現(xiàn)像不能發(fā)生,但是沒(méi)有定義那種現(xiàn)像一定發(fā)生。 PostgreSQL只提供兩種隔離級(jí)別的原因是, 這是把標(biāo)準(zhǔn)的隔離級(jí)別與多版本并發(fā)控制架構(gòu)映射相關(guān)的唯一合理方法。 可用的隔離級(jí)別的行為在下面小節(jié)里描述。
要設(shè)置一個(gè)事務(wù)的隔離級(jí)別,使用SET TRANSACTION命令。
讀已提交是 PostgreSQL 里的缺省隔離級(jí)別。 當(dāng)一個(gè)事務(wù)運(yùn)行在這個(gè)隔離級(jí)別時(shí), SELECT查詢(沒(méi)有FOR UPDATE/SHARE的子句)只能看到查詢開(kāi)始之前已提交的數(shù)據(jù) 而無(wú)法看到未提交的數(shù)據(jù)或在查詢執(zhí)行期間其它事務(wù)已提交的數(shù)據(jù)。 不過(guò)SELECT看得見(jiàn)其自身所在事務(wù)中前面尚未提交的更新結(jié)果。 實(shí)際上,SELECT 查詢看到一個(gè)在查詢開(kāi)始運(yùn)行的瞬間該數(shù)據(jù)庫(kù)的一個(gè)快照。 請(qǐng)注意,在同一個(gè)事務(wù)里兩個(gè)相鄰的SELECT命令可能看到不同的快照, 因?yàn)槠渌聞?wù)會(huì)在第一個(gè)SELECT執(zhí)行期間提交。
UPDATE,DELETE,SELECT FOR UPDATE和SELECT FOR SHARE命令在搜索目標(biāo)行時(shí)的行為和 SELECT 一樣: 它們只能找到在命令開(kāi)始的時(shí)候已經(jīng)提交的行。 不過(guò),找到這樣的目標(biāo)行的時(shí)候可能已經(jīng)被其它并發(fā)事務(wù)更新、刪除、鎖住。在這種情況下, 即將進(jìn)行的更新將等待第一個(gè)事務(wù)提交或者回滾(如果它還在處理)。 如果第一個(gè)事務(wù)回滾,那么它的作用將忽略,而第二個(gè)事務(wù)將繼續(xù)更新最初發(fā)現(xiàn)的行。 如果第一個(gè)事務(wù)提交,那么如果第一個(gè)事務(wù)刪除了該行,則第二個(gè)事務(wù)將忽略該行, 否則它將試圖在該行的已更新的版本上施加它的操作。系統(tǒng)將重新計(jì)算命令搜索條件(WHERE子句), 看看該行已更新的版本是否仍然符合搜索條件。如果符合,則第二個(gè)事務(wù)從該行的已更新版本開(kāi)始繼續(xù)其操作。 如果是SELECT FOR UPDATE和SELECT FOR SHARE 則意味著把已更新的行版本鎖住并返回給客戶端。
: 因?yàn)樯厦娴囊?guī)則,正在更新的命令可能會(huì)看到不一致的快照: 它們可以看到影響它們更新的并發(fā)命令的效果,但是卻看不到那些命令對(duì)數(shù)據(jù)庫(kù)里其它行的作用。 這樣的行為令讀已提交模式不適合用于哪種涉及復(fù)雜搜索條件的命令。 不過(guò),它對(duì)于簡(jiǎn)單的情況而言是正確的。 比如,假設(shè)我們用類似下面這樣的命令更新銀行余額:
BEGIN; UPDATE accounts SET balance = balance + 100.00 WHERE acctnum = 12345; UPDATE accounts SET balance = balance - 100.00 WHERE acctnum = 7534; COMMIT;
如果兩個(gè)并發(fā)事務(wù)試圖同時(shí)修改帳號(hào)12345的余額,那我們很明顯希望第二個(gè)事務(wù)是從已更新過(guò)的行版本上進(jìn)行更新。 因?yàn)槊總€(gè)命令只是影響一個(gè)已經(jīng)決定了的行,因此讓它看到更新后的版本不會(huì)導(dǎo)致任何不一致的問(wèn)題。
在讀取已提交模式中,更復(fù)雜的使用可能產(chǎn)生不需要的結(jié)果。比如: 考慮刪除命令操作正在數(shù)據(jù)中添加和刪除其都限制條件由另一個(gè)命令。 website是個(gè)有兩行數(shù)據(jù)表。website.hits分別為9和 10:
BEGIN; UPDATE website SET hits = hits + 1; -- run from another session: DELETE FROM website WHERE hits = 10; COMMIT;
DELETE不會(huì)影響在UPDATE前后website.hits = 10的行。 這是因?yàn)榘l(fā)生更新前值9的行跳過(guò),當(dāng)UPDATE完成,DELETE獲取一個(gè)鎖。 新行值不再是10而是11,沒(méi)有匹配的條件。
因?yàn)樵谧x已提交模式里,每個(gè)命令都是從一個(gè)新的快照開(kāi)始的,而這個(gè)快照包含所有到該時(shí)刻為止已提交的事務(wù), 因此同一事務(wù)中后面的命令將看到任何已提交的其它事務(wù)的效果。 這里關(guān)心的問(wèn)題是在單個(gè)命令里是否看到數(shù)據(jù)庫(kù)里絕對(duì)一致的視圖。
讀已提交模式提供的部分事務(wù)隔離對(duì)于許多應(yīng)用而言是足夠的,并且這個(gè)模式速度快,使用簡(jiǎn)單。 不過(guò),對(duì)于做復(fù)雜查詢和更新的應(yīng)用,可能需要保證數(shù)據(jù)庫(kù)有比讀已提交模式更加嚴(yán)格的一致性視圖。
可串行化級(jí)別提供最嚴(yán)格的事務(wù)隔離。 這個(gè)級(jí)別模擬串行的事務(wù)執(zhí)行,就好像事務(wù)將被一個(gè)接著一個(gè)那樣串行(而不是并行)的執(zhí)行。 不過(guò),使用這個(gè)級(jí)別的應(yīng)用必須準(zhǔn)備在串行化失敗的時(shí)候重新啟動(dòng)事務(wù)。
當(dāng)一個(gè)事務(wù)處于可串行化級(jí)別時(shí), SELECT查詢只能看到在事務(wù)開(kāi)始之前已提交的數(shù)據(jù)而看不到未提交的數(shù)據(jù)或事務(wù)執(zhí)行期間其它并發(fā)事務(wù)已提交的修改。 不過(guò),SELECT 看得見(jiàn)其自身所在事務(wù)中前面尚未提交的更新結(jié)果。 可串行化的SELECT看到的是該事務(wù)開(kāi)始時(shí)的快照,而不是該事務(wù)內(nèi)部當(dāng)前查詢開(kāi)始時(shí)的快照,這個(gè)行為和讀已提交級(jí)別是不一樣的。 這樣,同一個(gè)事務(wù)內(nèi)部后面的SELECT命令總是看到同樣的數(shù)據(jù)。 它們看不到其它事務(wù)所提交在它們自己的事務(wù)開(kāi)始后所做的更改。 (這種行為可以報(bào)表應(yīng)用程序的理想選擇。)
UPDATE,DELETE,SELECT FOR UPDATE, 和SELECT FOR SHARE在搜索目標(biāo)行時(shí)的行為和 SELECT 一樣: 它們將只尋找在事務(wù)開(kāi)始的時(shí)候已經(jīng)提交的目標(biāo)行。 但是,這樣的目標(biāo)行在被發(fā)現(xiàn)的時(shí)候可能已經(jīng)被另外一個(gè)并發(fā)的事務(wù)更新、刪除、鎖住。在這種情況下, 可串行化的事務(wù)將等待第一個(gè)正在更新的事務(wù)提交或者回滾(如果它仍然在處理中)。 如果第一個(gè)事務(wù)回滾,那么它的影響將被忽略,而這個(gè)可串行化的就可以繼續(xù)更新它最初發(fā)現(xiàn)的行。 但是如果第一個(gè)事務(wù)被提交了(并且實(shí)際上更新或者刪除了該行,而不只是鎖住它)那么可串行化事務(wù)將回滾,并返回下面信息:
ERROR: could not serialize access due to concurrent update
因?yàn)橐粋€(gè)可串行化的事務(wù)在開(kāi)始之后不能更改或者鎖住被其它事務(wù)更改過(guò)的行。
當(dāng)應(yīng)用收到這樣的錯(cuò)誤信息時(shí),它應(yīng)該退出當(dāng)前的事務(wù)然后從新開(kāi)始進(jìn)行整個(gè)事務(wù)。 第二次運(yùn)行時(shí),該事務(wù)看到的快照將包含前一次已提交的修改,所以不會(huì)有邏輯沖突。
請(qǐng)注意只有更新事務(wù)才需要重試,只讀事務(wù)從來(lái)沒(méi)有串行化沖突。
可串行化事務(wù)級(jí)別提供了嚴(yán)格的保證:每個(gè)事務(wù)都看到一個(gè)完全一致的數(shù)據(jù)庫(kù)視圖。 不過(guò),如果并發(fā)更新令數(shù)據(jù)庫(kù)不能維持串行執(zhí)行的樣子,那么應(yīng)用必須準(zhǔn)備重試事務(wù)。 因?yàn)橹刈鰪?fù)雜事務(wù)的開(kāi)銷可能是非??捎^的, 所以我們只建議當(dāng)更新命令中包含復(fù)雜邏輯并且在讀已提交級(jí)別中可能導(dǎo)致錯(cuò)誤結(jié)果的時(shí)候才使用可串行化級(jí)別。 可串行化模式只是在這樣的情況下是必要的: 一個(gè)事務(wù)連續(xù)做若干個(gè)命令,而這幾個(gè)命令必須看到完全相同的數(shù)據(jù)庫(kù)視圖。
執(zhí)行的"可串行化"的直觀含義(以及數(shù)學(xué)定義)是兩個(gè)成功提交的并發(fā)事務(wù)將顯得好像嚴(yán)格地串行執(zhí)行一樣, 一個(gè)跟著一個(gè)(盡管我們可能無(wú)法預(yù)期哪個(gè)首先執(zhí)行)。 但是我們必須明白,禁止那些在表12-1里面列出的行為并不能保證真正的可串行化, 并且,實(shí)際上PostgreSQL的可串行化模式并不保證在這種含義下的可串行化。 舉例來(lái)說(shuō),假設(shè)一個(gè)表mytab,最初包含:
class | value -------+------- 1 | 10 1 | 20 2 | 100 2 | 200
假設(shè)可串行化事務(wù) A 計(jì)算:
SELECT SUM(value) FROM mytab WHERE class = 1;
然后把結(jié)果(30)作為value字段值插入到表中,并令新行的class= 2。 同時(shí),另一個(gè)并發(fā)的可串行化的事務(wù)B進(jìn)行下面計(jì)算:
SELECT SUM(value) FROM mytab WHERE class = 2;
然后把結(jié)果(300)作為 class 字段值插入到表中,并令新行的class= 1。 然后兩個(gè)事務(wù)都提交。所有列出的禁止行為都不會(huì)發(fā)生,但是我們拿到的結(jié)果是不可能在任何一種串行執(zhí)行下看到的。 如果 A 在 B 之前執(zhí)行,B 應(yīng)該計(jì)算出總和 330 ,而不是 300 ,如果 B 在 A 之前執(zhí)行,那么 A 計(jì)算出的總和也會(huì)不同。
為了保證真正數(shù)學(xué)上的可串行化,有必要對(duì)一個(gè)數(shù)據(jù)庫(kù)系統(tǒng)強(qiáng)制謂詞鎖定,這就意味著一個(gè)事務(wù)不能插入或者更改這樣的數(shù)據(jù)行: 這個(gè)數(shù)據(jù)行的數(shù)據(jù)匹配另外一個(gè)并發(fā)事務(wù)的 WHERE 條件。 比如,一旦事務(wù) A 執(zhí)行了查詢SELECT ... WHERE class = 1, 那么一個(gè)謂詞鎖定系統(tǒng)將禁止事務(wù)B插入任何 class 為 1 的新行,直到 A 提交。 [1] 這樣的鎖系統(tǒng)實(shí)現(xiàn)起來(lái)非常復(fù)雜,并且執(zhí)行起來(lái)代價(jià)高昂,因?yàn)槊總€(gè)會(huì)話都必須要知道每個(gè)并發(fā)事務(wù)的每個(gè)查詢的執(zhí)行細(xì)節(jié)。 并且這樣大量的開(kāi)銷在大部分情況下都是不必要的浪費(fèi),因?yàn)樵趯?shí)際情況下大部分應(yīng)用都不做這種制造麻煩的事情。 當(dāng)然,上面的例子是精心設(shè)計(jì)的,不能代表真實(shí)的軟件。 因此,PostgreSQL并未實(shí)現(xiàn)謂詞鎖定。
在那些非串行化執(zhí)行真的可能有危險(xiǎn)的場(chǎng)合,可以通過(guò)使用明確的鎖定來(lái)避免問(wèn)題的發(fā)生。 更多的討論在下面的小節(jié)進(jìn)行
[1] | 實(shí)際上,一個(gè)謂詞鎖定系統(tǒng)避免了幻讀,方法是約束寫入的東西, 而 MVCC 避免幻讀的方法是約束它讀取的東西。 |