https://github.com/angelfan/DayDayUp/blob/master/note/transaction_isolation.mdgit
MVCC的實現方法有兩種:github
1.寫新數據時,把舊數據移到一個單獨的地方,如回滾段中,其餘人讀數據時,從回滾段中把舊的數據讀出來;sql
2.寫數據時,舊數據不刪除,而是把新數據插入。數據庫
PostgreSQL數據庫使用第二種方法,而Oracle數據庫和MySQL中的innodb引擎使用的是第一種方法。ruby
與racle數據庫和MySQL中的innodb引擎相比較,PostgreSQL的MVCC實現方式的優缺點以下。併發
優勢:oracle
1.事務回滾能夠當即完成,不管事務進行了多少操做;spa
2.數據能夠進行不少更新,沒必要像Oracle和MySQL的Innodb引擎那樣須要常常保證回滾段不會被用完,也不會像oracle數據庫那樣常常遇到「ORA-1555」錯誤的困擾;code
缺點:事務
1.舊版本數據須要清理。PostgreSQL清理舊版本的命令成爲Vacuum;
2.舊版本的數據會致使查詢更慢一些,由於舊版本的數據存在於數據文件中,查詢時須要掃描更多的數據塊。
(本段轉自《PostgreSQL修煉之道》)
一個事務讀取了另外一個未提交的並行事務寫的數據。
一個事務從新讀取前面讀取過的數據, 發現該數據已經被另外一個已提交的事務修改過。
一個事務從新執行一個查詢,返回一套符合查詢條件的行, 發現這些行由於其餘最近提交的事務而發生了改變。
BEGIN;
SELECT title FROM books WHERE id = 1; -- 'Old' gap SELECT titoe FROm books WHERE id = 1 -- '???'
UPDATE books SET title 'New' WHERE id = 1
另外一個事務中只要更新的記錄, 當前事務就會讀取到更新的數據 (髒讀)
另外一個事務必須提交, 當前事務纔會讀取到最新數據 (不可髒讀 可是可重複讀)
即便另外一個事務幾條, 當前事務也不會讀取到新的數據 (不可重複讀) 若是另外一事務作插入操做, 當前事務是能夠取得數量上的變化(select count(*) from table)(能夠幻讀)
另外一事務作了插入操做, 當前事務不會取得數量上的變化(select count(*) from table)(不能夠幻讀)
隔離級別 | 髒讀 | 不可重複讀 | 幻讀 |
---|---|---|---|
讀未提交 | 可能 | 可能 | 可能 |
讀已提交 | 不可能 | 可能 | 可能 |
可重複讀 | 不可能 | 不可能 | 可能 |
可串行化 | 不可能 | 不可能 | 不可能 |
當一個事務運行在這個隔離級別時: SELECT查詢(沒有FOR UPDATE/SHARE子句)只能看到查詢開始以前已提交的數據而沒法看到未提交的數據或者在查詢執行期間其它事務已提交的數據 (僅讀當時數據庫快照)。 不過,SELECT看得見其自身所在事務中前面更新執行結果,即便它們還沒有提交。 (注意:在同一個事務裏兩個相鄰的SELECT命令可能看到不一樣的快照,由於其它事務會在第一個SELECT執行期間提交。)
要是同時有兩個事務修改同一行數據會怎麼樣? 這就是事務隔離級別(transaction isolation levels)登場的時候了。 Postgres支持兩個基本的模型來讓你控制應該怎麼處理這樣的狀況。默認狀況下使用讀已提交(READ COMMITTED), 等待初始的事務完成後再讀取行記錄而後執行語句。若是在等待的過程當中記錄被修改了,它就從頭再來一遍。 舉一個例子,當你執行一條帶有WHERE子句的UPDATE時,WHERE子句會在最初的事務被提交後返回命中的記錄結果, 若是這時WHERE子句的條件仍然能獲得知足的話,UPDATE纔會被執行。 在下面這個例子中,兩個事務同時修改同一行記錄,最初的UPDATE 語句致使第二個事務的WHERE不會返回任何記錄,所以第二個事務根本沒有修改到任何記錄
Order.transaction do Order.where(ship_code: '123').update_all(ship_code: '123456') sleep 20 end # 在第一個事務執行完update_all後提交以前執行 Order.transaction do Order.where(ship_code: '123').update_all(ship_code: '1234567') end # ship_code => 123456 # Order.where(ship_code: '123').update_all(ship_code: '1234567') 的時候已經找不到 where(ship_code: '123') Order.transaction do Order.where(ship_code: '123').first.update(ship_code: '1234567') end # ship_code => 1234567 # 這種狀況下會修改記錄是由於 它先執行的是Order.where(ship_code: '123').first # 由於默認的隔離級別是讀已提交, 因此這個時候 # Order.where(ship_code: '123') 是能夠拿到數據的 # 執行的sql語句是 ## 先拿到order#id ## UPDATE "orders" SET "ship_code" = 1234567 WHERE "orders"."id" = order#id # repeatable Order.transaction do old_ship_code = Order.where(id: 2).pluck(:ship_code) sleep 10 new_ship_code = Order.where(id: 2).pluck(:ship_code) p [old_ship_code, new_ship_code] # 數據是不同的 end Order.where(id: 2).update_all(ship_code: 123456123)
ActiveRecord::Base.isolation_level(:repeatable_read) do Order.transaction do old_ship_code = Order.where(id: 2).pluck(:ship_code) sleep 10 new_ship_code = Order.where(id: 2).pluck(:ship_code) p [old_ship_code, new_ship_code] # 數據是同樣的 end end
可串行化級別提供最嚴格的事務隔離。這個級別爲全部已提交事務模擬串行的事務執行, 就好像事務將被一個接着一個那樣串行(而不是並行)的執行。不過,正如可重複讀隔離級別同樣, 使用這個級別的應用必須準備在串行化失敗的時候從新啓動事務。 事實上,該隔離級別和可重複讀但願的徹底同樣,它只是監視這些條件,以全部事務的可能的序列不一致的(一次一個)的方式執行並行的可序列化事務執行的行爲。 這種監測不引入任何阻止可重複讀出現的行爲,但有一些開銷的監測,檢測條件這可能會致使序列化異常 將觸發序列化失敗。
分析:若是你須要更好的「兩個事務同時修改同一行數據」控制這種行爲,你能夠把事務隔離級別設置爲 可串行化(SERIALIZABLE) 。 在這個策略下,上面的場景會直接失敗,由於它遵循這樣的規則:「若是我正在修改的行被其餘事務修改過的話,就再也不嘗試」, 同時 Postgres會返回這樣的錯誤信息:因爲併發修改致使沒法進行串行訪問 。 捕獲這個錯誤而後重試就是你的應用須要去作的事情了,或者不重試直接放棄也行,若是那樣合理的話。
ActiveRecord::Base.isolation_level(:serializable) do Order.transaction do old_ship_code = Order.where(id: 2).update_all(ship_code: 123) sleep 10 end end Order.where(id: 2).update_all(ship_code: 123) # PG::TRSerializationFailure: ERROR: could not serialize access due to concurrent update # ActiveRecord::TransactionIsolationConflict