目錄html
什麼是事務呢?sql
事務是由一步或幾步數據庫操做序列組成的邏輯執行單元,這系列操做要麼所有執行,要麼所有放棄執行。數據庫
原子性(Atomic),一致性(Consistency),隔離性(Isolation),持續性(Durability),簡稱ACID性。安全
原子性在多線程的時候學習過,一般表示不可再分的操做,表示事務是應用中最小的執行單位。多線程
事務操做先後,數據總量不變。如A像B轉帳500,A得減小500,B得加上500,這樣纔算是保證了數據庫的一致性。若是A減了,B沒加上去,這不是耍流氓莫。併發
一致性是經過原子性來保證的。學習
各個事務的執行互不干擾,任意一個事務的內部操做對其餘併發的事務都是隔離的。線程
當事務提交或回滾後,數據庫將會持久化地保存數據。code
數據庫的語句由下列語句組成:htm
模擬轉帳:
CREATE TABLE account( id INT PRIMARY KEY AUTO_INCREMENT, NAME VARCHAR(10), balance DOUBLE ); INSERT INTO account (NAME,balance) VALUES ('張三',1000),('李四',1000);
轉帳成功後,張三餘額還剩500,李四餘額還剩1500。
-- 張三向李四轉帳500 UPDATE account SET balance = balance - 500 WHERE NAME = '張三'; UPDATE account SET balance = balance + 500 WHERE NAME = '李四';
假設此時,在張三轉帳完畢,餘額減去500以後,數據庫出現了意料以外的異常,致使李四的餘額並無加上500,那麼這個問題就很是嚴重遼,就像下面這樣:
UPDATE account SET balance = balance - 500 WHERE NAME = '張三'; Something IS wrong... -- 不是sql語句,將發生異常 UPDATE account SET balance = balance + 500 WHERE NAME = '李四';
就像是什麼問題呢,就像是充話費,你充了100塊錢,充完以後你一看,沒充上,血虧。
這時就能夠利用事務來處理這個問題:
-- 開啓事務 START TRANSACTION; UPDATE account SET balance = balance - 500 WHERE NAME = '張三'; Something IS wrong... UPDATE account SET balance = balance + 500 WHERE NAME = '李四';
這時轉帳仍是沒有正常執行對吧,經過select*from account;
查看一下,果真仍是一個500,一個1000,可是其實這只是臨時的數據,並無將數據永久修改,能夠再另一個窗口查看,發現數據並無變化,也就是說,當你發現臨時數據不符合預期,就能夠當即進行事務回滾。
-- 開啓事務 START TRANSACTION; UPDATE account SET balance = balance - 500 WHERE NAME = '張三'; Something IS wrong... UPDATE account SET balance = balance + 500 WHERE NAME = '李四'; -- 回滾事務 ROLLBACK;
回滾以後,一切又回到最初的起點,記憶中兩人的餘額都是1000。
至關於設置了個斷點:savepoint a;
。
回滾到該斷點:rollback to a;
既然是臨時數據,那麼如何將他變成永久性的呢,這即是提交任務。
-- 開啓事務 START TRANSACTION; UPDATE account SET balance = balance - 500 WHERE NAME = '張三'; UPDATE account SET balance = balance + 500 WHERE NAME = '李四'; -- 提交任務 COMMIT;
ok,提交以後,表中數據就真正地被修改遼。
當事務所包含的任意一個數據庫操做執行成功或者失敗以後,都應該提交事務,不管是commit仍是rollback。提交方式有兩種,自動提交或者手動提交。
SELECT @@autocommit; -- 1表明自動提交,0表明手動提交
MySQL中事務提交方式默認是自動提交的。
-- 關閉默認提交,即開啓事務。 SET @@autocommit = 0;
須要注意的是,一旦設置將提交方式設置成手動提交,至關於開啓了一次事務,那麼全部的DML語句都須要顯示地使用commit提交事務,或者使用rollback回滾結束事務。
當前會話窗口修改事務提交的方式,對其餘的會話窗口沒有影響。
事務具備隔離性,多個事務之間相互獨立。但多個事務操做同一批數據,將會引起一些問題,可設置不一樣的隔離級別解決問題。
查詢隔離級別
-- 數據庫查詢隔離級別 MySQL默認 repeatable read SELECT @@tx_isolation;
設置隔離級別
-- 設置隔離級別爲read-uncommited(需重啓生效) SET GLOBAL TRANSACTION ISOLATION LEVEL 隔離級別字符串;
髒讀、不可重複讀、幻讀都會發生
演示
-- 設置隔離級別爲read-uncommited(需重啓生效) SET GLOBAL TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
其中事務A開啓並轉帳,可是並無提交,此時臨時讀取到的數據是500,1500:
-- 開啓事務 START TRANSACTION; UPDATE account SET balance = balance - 500 WHERE NAME = '張三'; UPDATE account SET balance = balance + 500 WHERE NAME = '李四';
這時事務B開啓並查詢帳戶,讀取到了剛纔事務A並未提交掉的數據,500,1500:
-- 開啓事務 START TRANSACTION; SELECT * FROM account;
這時就出現了髒讀的狀況,出現了虛晃,事務B覺得事務A轉帳成功,其實並無。
此時,若是A很雞賊,將事務進行回滾rollback,雙方的數據又回到最初的起點,兩個1000,1000。
很明顯,在同一個事務中讀取到了不一樣的數據,也就是出現了不可重複讀的問題。
不可重複讀、幻讀會發生
read committed能夠解決髒讀,也就是說,若是事務A沒有提交事務,事務B讀取的數據仍是原來的數據,只有事務A提交事務了commit,事務B讀取到的數據纔會改變。
但此時,事務B在同一事務中讀取到的兩次數據顯然又是不一樣的,所以不可重複讀的問題依舊存在。
幻讀會發生
repeatable read是MySQL的 默認事務隔離級別,確保同一個事務的多個實例在併發讀取數據時,看到一樣的數據行,解決了不可重複讀和髒讀的問題。可是幻讀現象仍然存在:
舉另一個例子,如今事務A開啓並查詢一個叫王五的人,顯然是查不到的。
-- 開啓事務 START TRANSACTION; SELECT * FROM account WHERE NAME="王五";
此時事務B開啓,並插入王五這我的,並提交:
START TRANSACTION; INSERT INTO account (NAME,balance) VALUES ("王五",1000); COMMIT;
這時事務A仍然認爲表中確實沒有王五這我的,也想往裏面添加。
INSERT * INTO account (NAME,balance) VALUES ("王五",1000);
這時,事務A將沒法插入這條數據,由於事務B已經插入了這條數據,可是在事務A這就跟撞見了鬼同樣,什麼狀況!明明搜了一下沒有這條數據,卻怎麼也插不進去!就像產生了幻覺。
這就是幻讀。
參考:http://www.javashuo.com/article/p-aspakucj-dd.html
解決全部的問題
事務的最高級別,在每一個讀的數據行上加上鎖,強制事務排序,使之不可能相互衝突,從而解決了幻讀問題。可是,將會致使大量的超時現象和鎖競爭,有點相似於線程中的同步鎖,很安全但效率較低。
參考:《瘋狂Java講義》