一、 什麼是事務
事務就是一段sql 語句的批處理,但是這個批處理是一個atom(原子) ,不可分割,要麼都執行,要麼回滾(rollback)都不執行。
二、爲什麼出現這種技術
爲什麼要使用事務這個技術呢? 現在的很多軟件都是多用戶,多程序,多線程的,對同一個表可能同時有很多人在用,爲保持數據的一致性,所以提出了事務的概念。這樣很抽象,舉個例子:
A 給B 要劃錢,A 的賬戶-1000元, B 的賬戶就要+1000元,這兩個update 語句必須作爲一個整體來執行,不然A 扣錢了,B 沒有加錢這種情況很難處理(找出原因)。
三、如何在MYSQL 中使用事務
1、誰可以使用
只有InnoDB /BDB 的之類的transaction_safe table 才能支持。
默認的engine MyISAM 是不支持事務的,show engine 可以看到支持的和默認的engine。可以在[mysqld] 加入: default_storage_engine=InnoDB; InnoDB 就是建立表的默認引擎。
建立InnoDB 表:Create table .... type=InnoDB; Alter table table_name type=InnoDB;(如何查看已有表的類型: show create table table_name)
這樣我們就可以在InnoDB 表上進行事務操作了!
2、如何使用
啓動事務的方法:
認爲分爲兩種:
1、begin ,rollback,commit .當然有的人用begin /begin work .推薦用START TRANSACTION 是SQL-99標準啓動一個事務。
start transaction;
update from account set money=money-100 where name='a';
update from account set money=money+100 where name='b';
commit;
解釋: 這樣start transaction 手動開啓事務,commit 手動關閉事務。
2、默認的時候autocommit=1 自動提交是開啓的,所以你可以理解爲每條語句一輸入到mysql就commit 了。當你 set autocommit=0 時候,你可以這樣:
update from account set money=money-100 where name='a';
update from account set money=money+100 where name='b';
commit;
// 默認都不提交,只有手動鍵入commit 時候才上述都提交。
綜述:一般使用1 方法。
四、舉例
mysql> select * from employee;
+------------+------------+------------+--------------+
| employeeID | name | job | departmentID |
+------------+------------+------------+--------------+
| 6651 | Ajay Patel | Programmer | 128 |
| 7513 | Nora Edwar | Programmer | 128 |
| 9006 | Candy Burn | Systems Ad | 128 |
| 9842 | Ben Smith | DBA | 42 |
| 9843 | Pert Park | DBA | 42 |
| 9845 | Ben Patel | DBA | 128 |
| 9846 | Red Right | x | 128 |
| 9847 | Run Wild | x | 128 |
| 9848 | Rip This J | x | 128 |
| 9849 | Rip This J | x | 128 |
| 9850 | Reader U | x | 128 |
set auotcommit =0;
insert into employee values(null,"test1",null,128);
savepoint s1;
insert into employee values(null,"test2",null,128);
savepoint s2;
insert into employee values(null,"test3",null,128);
savepoint s3;
執行完三個插入語句,select * from employee 可以看到三條。如果你想回滾到最初rollback 就是最初什麼都沒有做的狀態。 如果你想回到savepoint s1 的狀態(也就是插入一條test1 的那裏) rollback to savpoint s1 . 同理什麼都可以做了。
附錄: 事務的ACID(Atomicity \Consistency \Isolation \Durablility)
A: 事務必須是原子(不可分割),要麼執行成功進入下一個狀態,要麼失敗rollback 到最初狀態。
C:在事務開始之前和事務結束以後,數據庫的完整性約束沒有被破壞。 這個一般通過外鍵來約束。
I:一個事務不能知道另外一個事務的執行情況(中間狀態)
D:在事務完成以後,該事務所對數據庫所作的更改便持久的保存在數據庫之中,並不會被回滾。
mysql 自己的MyISAM 沒有通過acid 測試,但是InnoDB 可以做到。
在分佈式的系統中,通常會有多個線程連接到數據庫中同時對一個表進行操作(這裏的同時並不表示同一個時間點,而是同時競爭cpu的資源,至於如何調度,就要看線程和操作系統如何進行調度了),這種情況下如果會話的事物設置不當,就會導致數據混亂,常常會出現以下三種情況(假設現在系統中有兩個會話A和B,同時對錶T_Test操作):
1.髒讀:如果有A向B 做了這個操作:update account set money=money+100 where name='B';在沒有commit 之前B 查詢:select money from account where name='B';找到了沒有提交的money ,之後A在此時有rollback ,B 再查詢,100 不見了。爲了避免提高級別:read committed 。就是隻能讀取提交後的東東。
2.不可重複讀:1中說明的就是我們不能讀取一個事務的中間狀態。 而重複讀是指我們每次讀取到的結果都要一直。 這個也是mysql 默認的級別。
mysql> select @@tx_isolation ;
+-----------------+
| @@tx_isolation |
+-----------------+
| REPEATABLE-READ |
+-----------------+
3.幻讀:在一個事務內讀取到了別的事務插入的數據,導致前後讀取不一致。和不可重複讀的區別是:不可重複讀是讀取到了別人對錶中的某一條記錄進行了修改,導致前後讀取的數據不一致。 虛讀是前後讀取到表中的記錄總數不一樣,讀取到了其它事務插入的數據。比如現在有 A 和 B 兩個應用程序,他們併發訪問了數據庫中的某一張表,假設表中有 3 條記錄,B 執行查詢操作, 第一次查詢表得到了 3 條記錄。此時 A 對錶進行了修改,增加了一條記錄,當 B 再次查詢表的時候,發現多了一條數據。這種情況就造成了 B 的虛讀。但是虛讀是不一定每次都發生的,這種情況是不確定的。爲了避免虛讀,我們可以將事物隔離級別設置爲 serializable 如果設置成了這種級別,那麼數據庫就變成了單線程訪問的數據庫,導致性能降低很多。
summary:
(1)Serializable:可避免髒讀、不可重複讀、虛讀情況的發生。
(2)Repeatable read:可避免髒讀、不可重複讀情況的發生。(可重複讀,是 mysql 默認的事務隔離級別)
(3)Read committed:可避免髒讀情況發生。(讀取已提交的數據)
(4)Read uncommitted:最低級別,以上情況均無法保證。(讀取到了未提交的數據)
當我們將數據庫的隔離級別設置爲:Serializable 的時候,雖然可以避免所有併發訪問的問題,但是 Serializable 採用的是單線程來解決併發訪問的問題,也就是說在某一段時間內,只能有一個用戶對數據庫進行操作,導致其它用戶阻塞。導致數據庫的訪問性能很差。
1.讀未提交(Read Uncommitted):這種隔離級別可以讓當前事務讀取到其它事物還沒有提交的數據。這種讀取應該是在回滾段中完成的。通過上面的分析,這種隔離級別是最低的,會導致引發髒讀,不可重複讀,和幻讀。
2.讀已提交(Read Committed):這種隔離級別可以讓當前事務讀取到其它事物已經提交的數據。通過上面的分析,這種隔離級別會導致引發不可重複讀,和幻讀。
3.可重複讀取(Repeatable Read):這種隔離級別可以保證在一個事物中多次讀取特定記錄的時候都是一樣的。通過上面的分析,這種隔離級別會導致引發幻讀。
4.串行(Serializable):這種隔離級別將事物放在一個隊列中,每個事物開始之後,別的事物被掛起。同一個時間點只能有一個事物能操作數據庫對象。這種隔離級別對於數據的完整性是最高的,但是同時大大降低了系統的可併發性。
五、非InnoDB怎麼辦?
媽的,肯定有人會說那我mysql 的默認MyISAM 怎麼辦? 沒有事務這樣的事情怎麼處理呢? 這個要用到另外一種技術叫做LOCK ! 實際上實現上邊那4 個安全級別的所用的技術就是LOCK !
我怎麼在處理鎖的問題上,經常聽到:共享鎖、排它鎖、悲觀鎖、樂觀鎖、行級鎖、表級鎖。
共享鎖: 就是在讀取數據的時候,給數據添加一個共享鎖。共享和共享直接是不衝突的,但是和排他鎖是衝突的。
排他鎖: 更新數據的時候,安裝排他鎖,禁止其他一切行爲。
場景:老公去在 ATM 上取錢,老婆在櫃檯存錢,假設這個賬戶中有 1000 元。老公首先執行查詢操作,查詢到賬戶餘額爲 1000 此時程序將 1000 拿到內存中,老公取了 200 元,程序就執行了更新操作將賬戶餘額改爲 800,但是當老公的程序沒有 commit 的時候,老婆查詢賬戶,此時賬戶餘額還是 1000 元,老婆存入 200 元,程序執行了更新操作將賬戶餘額改爲 1200,然後老公將更新語句提交,接着老婆也將更新語句提交。最後導致的結果就是該賬戶的餘額爲 1200,這就是更新丟失的問題。引發更新丟失的根源就是查詢上,因爲雙方都是根據從數據庫查詢到的數據再對數據庫中的數據進行更新的。解決更新丟失有三個方案:(1) 將事務隔離級別設置爲最高,採用死鎖策略。(2) 採用悲觀鎖,悲觀鎖不是數據庫中真正的鎖,是人們看待事務的態度。(3) 採用樂觀鎖,樂觀鎖也不是數據庫中真正的鎖。
如果我們採用的是第一個方案時,老公進行查詢操作,數據庫爲表增加了共享鎖,老婆進行查詢操作時數據庫也增加了一個共享鎖。但是當老公進行更新數據庫操作 時,由於老婆拿着共享鎖,導致老公不能增加排它鎖,老婆進行更新操作時,因爲老公拿着共享鎖,導致老婆也拿不到排它鎖,這就發生了死鎖現象,你等我,我等 你。在 mysql 中,處理死鎖的方案是釋放掉一方的鎖。這樣就保證了一方更新成功,但是這種性能極低,因爲數據庫頻繁在解決死鎖問題。
悲觀鎖(更新多,查詢少時用)
如果我們採用的是第二個方案時,即採用悲觀鎖。就是我們在操作數據庫時採用悲觀的態度,認爲別人會在此時併發訪問數據庫。我們在查詢語句中 select * from account where name='aaa' for update; 等於加了排它鎖。當老公查詢餘額的時候,select money from account where name='aaa' for update; 增加了排它鎖,老婆查詢賬戶餘額的時候, select money from account where name='aaa' for update;也要求對數據庫加排它鎖,因爲老公已經拿到了排它鎖,導致老婆不能加鎖,所以老婆只有等待老公執行完畢,釋放掉鎖以後才能繼續操作。
樂觀鎖(更新少,查詢多時用)
如果我們採用的是第三個方案時,即採用樂觀鎖,就是我們在操作數據庫的時候會認爲沒有其它用戶併發訪問,但是樂觀鎖也不是完全樂觀的,樂觀鎖是採用版本號 的方式進行控制的。在數據庫表中有一列版本號。從數據庫中查詢的時候,將版本號也查詢過來,在進行更新操作的時候,將版本號加1,查詢條件的版本號還是查詢過來的版本號。比如,老公執行查詢操作的時候,select money,version from account where name='aaa'; 假設此時查詢到的版本號爲 0,老公在進行更新操作的時候 update account set money=money+100,version=version+1 where name='aaa' and version=0; 未提交時老婆來查詢,查詢到的版本號依然是 0,老婆也執行更新操作 update account set money=money+100,version=version+1 where name='aaa' and version=0; 現在老公提交了事務,老婆再提交事務的時候發現版本號爲 0 的記錄沒有了,所以就避免了數據丟失的問題。不過這種情況也導致了多個用戶更新操作時,只有一個用戶的更新被執行。
行級別的鎖:
select * from employee where employeeID=9857 for update; where 後邊是索引列
不是索引列那麼就爲表級別的鎖