mysql事務詳解
轉至:https://www.cnblogs.com/tqlin/p/11507508.html
參考:★ MYSQL隔離級別 通俗理解 + MYSQL、ORACLE默認事務隔離級別
mysql事務
- 在 MySQL 中只有使用了 Innodb 數據庫引擎的數據庫或表才支持事務
- 事務處理可以用來維護數據庫的完整性,保證成批的 SQL 語句要么全部執行,要么全部不執行。
- 一般來說,事務需要滿足4個條件(ACID):原子性(Atomicity)、一致性(Consistency)、隔離性(Isolation)、持久性(Durability)
- mysql默認自動提交事務
-
原子性
一個事務(transaction)中的所有操作,要么全部完成,要么全部不完成,不會結束在中間某個環節。事務在執行過程中發生錯誤,會被回滾(Rollback)到事務開始前的狀態,就像這個事務從來沒有執行過一樣。
一致性
在事務開始之前和事務結束以后,數據庫的完整性沒有被破壞。這表示寫入的資料必須完全符合所有的預設規則,這包含資料的精確度、串聯性以及后續數據庫可以自發性地完成預定的工作。
隔離性
數據庫允許多個並發事務同時對其數據進行讀寫和修改的能力,隔離性可以防止多個事務並發執行時由於交叉執行而導致數據的不一致。事務隔離分為不同級別,包括讀未提交(Read uncommitted)、讀提交(read committed)、可重復讀(repeatable read)和串行化(Serializable)。
持久性
事務處理結束后,對數據的修改就是永久的,即便系統故障也不會丟失。
-
事務的隔離級別
隔離級別 臟讀(Dirty Read) 不可重復讀(NonRepeatable Read) 幻讀(Phantom Read) 未提交讀(Read uncommitted) 可能 可能 可能 已提交讀(Read committed) 不可能 可能 可能 可重復讀(Repeatable read) 不可能 不可能 可能 可串行化(Serializable ) 不可能 不可能 不可能 InnoDB默認是可重復讀級別的
- ① 臟讀: 臟讀就是指當一個事務正在訪問數據,並且對數據進行了修改,而這種修改還沒有提交到數據庫中,這時,另外一個事務也訪問這個數據,然后使用了這個數據。
- ② 不可重復讀:是指在一個事務內,多次讀同一數據。在這個事務還沒有結束時,另外一個事務也訪問該同一數據。那么,在第一個事務中的兩次讀數據之間,由於第二個事務的修改,那么第一個事務兩次讀到的的數據可能是不一樣的。這樣就發生了在一個事務內兩次讀到的數據是不一樣的,因此稱為是不可重復讀。
- ③ 幻讀:第一個事務對一個表中的數據進行了修改,這種修改涉及到表中的全部數據行。同時,第二個事務也修改這個表中的數據,這種修改是向表中插入一行新數據。那么,以后就會發生操作第一個事務的用戶發現表中還有沒有修改的數據行,就好象發生了幻覺一樣,幻讀是數據行記錄變多了或者少了。
簡單點總結下他們的區別:臟讀是指讀取了未修改完的記錄,不可重復讀指因為被其它事務修改了記錄導致某事務兩次讀取記錄不一致,而幻讀是指因為其它事務對表做了增刪導致某事務兩次讀取的表記錄數不一致問題。
(1)read uncommitted 未提交讀 所有事務都可以看到沒有提交事務的數據。
eg: 公司發工資了,領導把5000元打到A的賬號(正常工資2千)上,但是該事務並未提交,而A正好去查看賬戶,發現工資已經到賬,是5000元整,非常高興。
但是領導隨后發現給A的工資發多了,於是迅速回滾了事務,修改金額后,將事務提交,最后A實際的工資只有2000元,A空歡喜一場。
剖析:臟讀:“事務A:leader給A發工資”,
“事務B:A查詢工資賬戶”,事務B讀取了事務A尚 uncommitted 的數據。當隔離級別設置為Read uncommitted時,就可能出現臟讀,如何避免臟讀,請看下一個隔離級別。
(2)read committed 提交讀 事務成功提交后才可以被查詢到。
eg: A拿着工資卡去消費,出門前查到卡里是2000元,湊巧緊接着她的老婆作為急用把A工資卡的2000元轉到另一賬戶, 當A准備付款時,卻發現工資卡已經沒有錢,扣款失敗......
剖析:兩個並發的事務,“事務A:A消費”,
“事務B:A的老婆網上轉賬”,事務A事先讀取了數據,緊接着事務B更新了數據,並提交了事務,而事務A再次讀取該數據時,數據已經發生了改變。
當隔離級別設置為Read committed時,避免了臟讀,但是可能會造成不可重復讀。
大多數數據庫的默認級別就是Read committed,比如Sql Server , Oracle。如何解決不可重復讀這一問題,請看下一個隔離級別。
(3)repeatable 重復讀 同一個事務內多次查詢卻返回了不同的數據值,即 可能將未提交的記錄查詢出來,而出現幻讀。注:Mysql的默認隔離級別就是Repeatable read。
前言:當隔離級別設置為Repeatable read 時,可以避免不可重復讀。當A拿着工資卡去消費時,一旦系統開始讀取工資卡信息(即事務開始),A的老婆就不可能對該記錄進行修改,也就是A的老婆不能在此時轉賬。
雖然Repeatable read避免了不可重復讀,但還有可能出現幻讀 。eg1: A的老婆在銀行工作,她可以很方便的查看A的信用卡消費記錄。月末了,她正在查詢A當月的消費情況 (select sum(amount) from transaction where month = ‘本月’)為80元,而A此時正好在某收銀台買單,
消費1000元,即新增了一條1000元的消費記錄(insert transaction ... ),並提交了事務,隨后A的老婆將A當月信用卡消費的明細打印到A4紙上,卻發現消費總額為1080元,A的老婆很詫異,以為出 現了幻覺,幻讀就這樣產生了。
eg2: 在事務A中,讀取到張三的工資為5000,操作沒有完成,事務還沒提交。
與此同時,事務B把張三的工資改為8000,並提交了事務。
隨后,在事務A中,再次讀取張三的工資,此時工資變為8000。在一個事務中前后兩次讀取的結果並不致,導致了不可重復讀。(大部分數據庫缺省的事物隔離級別都不會出現這種狀況)
(4)Serializable可串行化 強制的進行排序,在每個讀讀數據行上添加共享鎖。會導致大量超時現象和鎖競爭。這是花費最高代價但是最可靠的事務隔離級別。事務被處理為順序執行。除了防止臟讀,不可重復讀外,還避免了幻讀。
附:
★ 臟讀 : 讀取了前一事務 未提交 的數據 ;
不可重復讀 : 讀取了前一事務 提交 的數據;
★ 幻讀 與 不可重復讀
common :都是讀取了另一條已經提交的事務(這點與臟讀不同);
differences :
不可重復讀 :查詢的都是同一個數據項
幻讀 :針對的是一批數據整體(比如數據的個數)
不可重復讀eg: <當隔離級別設置為Repeatable read 時,可以避免不可重復讀>
eg2: 在事務A中,讀取到張三的工資為5000,操作沒有完成,事務還沒提交。
與此同時,事務B把張三的工資改為8000,並提交了事務。
隨后,在事務A中,再次讀取張三的工資,此時工資變為8000。在一個事務中前后兩次讀取的結果並不致,導致了不可重復讀。(大部分數據庫缺省的事物隔離級別都不會出現這種狀況)
幻讀eg:
eg1: 目前工資為5000的員工有10人,事務A讀取所有工資為5000的人數為10人。
此時,事務B插入一條工資也為5000的記錄,並且commit了。
這時,事務A再次讀取工資為5000的員工,記錄為11人。此時產生了幻讀。
(大部分數據庫缺省的事物隔離級別都會出現這種狀況,此種事物隔離級別將帶來表級鎖)
eg2: A將db中all學生的score從數字分數改變為ABCDE等級,但是B就在此時插入了一條具體的分數,當A改完后發現還有一條記錄沒有改過來,就好像發生了幻覺一樣.這就叫幻讀.
默認隔離級別
MySQL:
mysql默認的事務處理級別是'REPEATABLE-READ' -- 可重復讀
1.查看當前會話隔離級別
select @@tx_isolation;
2.查看系統當前隔離級別
select @@global.tx_isolation;
3.設置當前會話隔離級別
set session transaction isolatin level repeatable read;
4.設置系統當前隔離級別
set global transaction isolation level repeatable read;
Oracle:
oracle數據庫支持READ COMMITTED 和 SERIALIZABLE這兩種事務隔離級別。
默認系統事務隔離級別是READ COMMITTED--- 讀已提交
1.查看系統默認事務隔離級別,也是當前會話隔離級別
--首先創建一個事務
declare
trans_id Varchar2(100);
begin
trans_id := dbms_transaction.local_transaction_id( TRUE );
end;
--查看事務隔離級別
SELECT s.sid, s.serial#,
CASE BITAND(t.flag, POWER(2, 28))
WHEN 0 THEN 'READ COMMITTED'
ELSE 'SERIALIZABLE'
END AS isolation_level
FROM v$transaction t
JOIN v$session s ON t.addr = s.taddr AND s.sid = sys_context('USERENV', 'SID');