程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 數據庫知識 >> MYSQL數據庫 >> MySQL綜合教程 >> MySQL詳解(13)------------事務

MySQL詳解(13)------------事務

編輯:MySQL綜合教程

MySQL詳解(13)------------事務


一、 什麼是事務

事務就是一段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 後邊是索引列

不是索引列那麼就為表級別的鎖

 

 

 

MySQL的事務支持不是綁定在MySQL服務器本身,而是與存儲引擎相關1.MyISAM:不支持事務,用於只讀程序提高性能 2.InnoDB:支持ACID事務、行級鎖、並發 3.Berkeley DB:支持事務

一個事務是一個連續的一組數據庫操作,就好像它是一個單一的工作單元進行。換言之,永遠不會是完整的事務,除非該組內的每個單獨的操作是成功的。如果在事務的任何操作失敗,則整個事務將失敗。

實際上,會俱樂部許多SQL查詢到一個組中,將執行所有的人都一起作為事務的一部分。

事務的特性:
事務有以下四個標准屬性的縮寫ACID,通常被稱為:

原子性: 確保工作單元內的所有操作都成功完成,否則事務將被中止在故障點,和以前的操作將回滾到以前的狀態。

一致性: 確保數據庫正確地改變狀態後,成功提交的事務。

隔離性: 使事務操作彼此獨立的和透明的。

持久性: 確保提交的事務的結果或效果的系統出現故障的情況下仍然存在。

在MySQL中,事務開始使用COMMIT或ROLLBACK語句開始工作和結束。開始和結束語句的SQL命令之間形成了大量的事務。

COMMIT & ROLLBACK:
這兩個關鍵字提交和回滾主要用於MySQL的事務。

當一個成功的事務完成後,發出COMMIT命令應使所有參與表的更改才會生效。

如果發生故障時,應發出一個ROLLBACK命令返回的事務中引用的每一個表到以前的狀態。

可以控制的事務行為稱為AUTOCOMMIT設置會話變量。如果AUTOCOMMIT設置為1(默認值),然後每一個SQL語句(在事務與否)被認為是一個完整的事務,並承諾在默認情況下,當它完成。 AUTOCOMMIT設置為0時,發出SET AUTOCOMMIT =0命令,在隨後的一系列語句的作用就像一個事務,直到一個明確的COMMIT語句時,沒有活動的提交。

可以通過使用mysql_query()函數在PHP中執行這些SQL命令。

通用事務例子
這一系列事件是獨立於所使用的編程語言,可以建立在任何使用的語言來創建應用程序的邏輯路徑。
可以通過使用mysql_query()函數在PHP中執行這些SQL命令。


BEGIN WORK開始事務發出SQL命令

發出一個或多個SQL命令,如SELECT,INSERT,UPDATE或DELETE

檢查是否有任何錯誤,一切都依據的需要。

如果有任何錯誤,那麼問題ROLLBACK命令,否則發出COMMIT命令。

在MySQL中的事務安全表類型:

如果打算使用MySQL事務編程,那麼就需要一種特殊的方式創建表。有很多支持事務但最流行的是InnoDB表類型。

從源代碼編譯MySQL時,InnoDB表支持需要特定的編譯參數。如果MySQL版本沒有InnoDB支持,請互聯網服務提供商建立一個版本的MySQL支持InnoDB表類型,或者下載並安裝Windows或Linux/UNIX的MySQL-Max二進制分發和使用的表類型在開發環境中。
如果MySQL安裝支持InnoDB表,只需添加一個的TYPE=InnoDB 定義表創建語句。例如,下面的代碼創建InnoDB表tcount_tbl:

root@host# mysql -u root -p password;
Enter password:*******
mysql> use TUTORIALS;
Database changed
mysql> create table tcount_tbl
-> (
-> tutorial_author varchar(40) NOT NULL,
-> tutorial_count INT
-> ) TYPE=InnoDB;
Query OK, 0 rows affected (0.05 sec)

可以使用其他GEMINI或BDB表類型,但它取決於您的安裝,如果它支持這兩種類型。


由於項目設計裡面,牽扯到了金錢的轉移,於是就要用到MYSQL的事務處理,來保證一組處理結果的正確性。用了事務,就不可避免的要犧牲一部分速度,來保證數據的正確性。
只有InnoDB支持事務

事務 ACID Atomicity(原子性)、Consistency(穩定性)、Isolation(隔離性)、Durability(可靠性)

1、事務的原子性
一組事務,要麼成功;要麼撤回。

2、穩定性
有非法數據(外鍵約束之類),事務撤回。

3、隔離性
事務獨立運行。
一個事務處理後的結果,影響了其他事務,那麼其他事務會撤回。
事務的100%隔離,需要犧牲速度。

4、可靠性
軟、硬件崩潰後,InnoDB數據表驅動會利用日志文件重構修改。
可靠性和高速度不可兼得, innodb_flush_log_at_trx_commit選項 決定什麼時候吧事務保存到日志裡。
開啟事務
START TRANSACTION 或 BEGIN

提交事務(關閉事務)
COMMIT

放棄事務(關閉事務)
ROLLBACK

折返點
SAVEPOINT adqoo_1
ROLLBACK TO SAVEPOINT adqoo_1
發生在折返點 adqoo_1 之前的事務被提交,之後的被忽略

事務的終止

設置“自動提交”模式
SET AUTOCOMMIT = 0
每條SQL都是同一個事務的不同命令,之間由 COMMIT 或 ROLLBACK隔開
掉線後,沒有 COMMIT 的事務都被放棄

事務鎖定模式

系統默認: 不需要等待某事務結束,可直接查詢到結果,但不能再進行修改、刪除。
缺點:查詢到的結果,可能是已經過期的。
優點:不需要等待某事務結束,可直接查詢到結果。

需要用以下模式來設定鎖定模式

1、SELECT …… LOCK IN SHARE MODE(共享鎖)
查詢到的數據,就是數據庫在這一時刻的數據(其他已commit事務的結果,已經反應到這裡了)
SELECT 必須等待,某個事務結束後才能執行

2、SELECT …… FOR UPDATE(排它鎖)
例如 SELECT * FROM tablename WHERE id<200
那麼id<200的數據,被查詢到的數據,都將不能再進行修改、刪除、SELECT …… LOCK IN SHARE MODE操作
一直到此事務結束

共享鎖 和 排它鎖 的區別:在於是否阻斷其他客戶發出的 SELECT …… LOCK IN SHARE MODE命令

3、INSERT / UPDATE / DELETE
所有關聯數據都會被鎖定,加上排它鎖

4、防插入鎖
例如 SELECT * FROM tablename WHERE id>200
那麼id>200的記錄無法被插入

5、死鎖
自動識別死鎖
先進來的進程被執行,後來的進程收到出錯消息,並按ROLLBACK方式回滾
innodb_lock_wait_timeout = n 來設置最長等待時間,默認是50秒

事務隔離模式

SET [SESSION|GLOBAL] TRANSACTION ISOLATION LEVEL
READ UNCOMMITTED | READ COMMITTED | REPEATABLE READ | SERIALIZABLE
1、不帶SESSION、GLOBAL的SET命令
只對下一個事務有效
2、SET SESSION
為當前會話設置隔離模式
3、SET GLOBAL
為以後新建的所有MYSQL連接設置隔離模式(當前連接不包括在內)

隔離模式

READ UNCOMMITTED
不隔離SELECT
其他事務未完成的修改(未COMMIT),其結果也考慮在內

READ COMMITTED
把其他事務的 COMMIT 修改考慮在內
同一個事務中,同一 SELECT 可能返回不同結果

REPEATABLE READ(默認)
不把其他事務的修改考慮在內,無論其他事務是否用COMMIT命令提交過
同一個事務中,同一 SELECT 返回同一結果(前提是本事務,不修改)

SERIALIZABLE
和REPEATABLE READ類似,給所有的SELECT都加上了 共享鎖

出錯處理
根據出錯信息,執行相應的處理


mysql事物處理實例MYSQL的事務處理主要有兩種方法
1.用begin,rollback,commit來實現
begin開始一個事務
rollback事務回滾
commit 事務確認
2.直接用set來改變mysql的自動提交模式
mysql默認是自動提交的,也就是你提交一個query,就直接執行!可以通過
set autocommit = 0 禁止自動提交
set autocommit = 1 開啟自動提交
來實現事務的處理。
但要注意當用set autocommit = 0 的時候,你以後所有的sql都將作為事務處理,直到你用commit確認或 rollback結束,注意當你結束這個事務的同時也開啟了新的事務!按第一種方法只將當前的做為一個事務!
MYSQL只有 INNODB和BDB類型的數據表才支持事務處理,其他的類型是不支持的!
MYSQL5.0 WINXP下測試通過~ ^_^
 

mysql> use test;
Database changed
mysql> CREATE TABLE `dbtest`(
-> id int(4)
-> ) TYPE=INNODB;
Query OK, 0 rows affected, 1 warning (0.05 sec)

mysql> select * from dbtest
-> ;
Empty set (0.01 sec)

mysql> begin;
Query OK, 0 rows affected (0.00 sec)

mysql> insert into dbtest values(5);
Query OK, 1 row affected (0.00 sec)

mysql> insert into dbtest value(6);
Query OK, 1 row affected (0.00 sec)

mysql> commit;
Query OK, 0 rows affected (0.00 sec)

mysql> select * from dbtest;
+------+
| id |
+------+
| 5 |
| 6 |
+------+
2 rows in set (0.00 sec)

mysql> begin;
Query OK, 0 rows affected (0.00 sec)

mysql> insert into dbtest values(7);
Query OK, 1 row affected (0.00 sec)

mysql> rollback;
Query OK, 0 rows affected (0.00 sec)

mysql> select * from dbtest;
+------+
| id |
+------+
| 5 |
| 6 |
+------+
2 rows in set (0.00 sec)

mysql> mysql事務處理php代碼實現事務的處理可以通過PHP預定義類mysqli的以下方法實現。
autocommit(boolean):該方法用於限定查詢結果是否自動提交,如果該方法的參數為true則自動提交,如果參數為false則關閉自動提交。MySQL數據庫默認為自動提交。
rollback():利用mysqli類中的該方法可以實現事務的回滾。
commit():利用該方法可以實現提交所有查詢。
 

include_once("conn.php");

$id=$_GET[id];
$conn->autocommit(false);
if(!$conn->query("delete from tb_sco where id='".$id."'"))
{
$conn->rollback();
}
if(!$conn->query("delete from tb_stu where id='".$id."'"))
{
$conn->rollback();
}
$conn->commit();
$conn->autocommit(true);
echo "ok"
?>


列二

require('connectDB.php'); //建立數據庫連接
mssql_query("BEGIN TRANSACTION DEPS02_DEL"); //開始事務
$delete_dep_sql="DELETE FROM TBLDEPARTMENT WHERE DEPTID='{$_GET[deptid]}'";
// echo $delete_dep_sql."
";
mssql_query($delete_dep_sql); //操作數據庫
// var_dump($del_result);
$delete_result = mssql_query("select @@ROWCOUNT as id");
$delete_info = mssql_fetch_array($delete_result);
$delete_rows = $delete_info[0];
// var_dump($delete_rows);
mssql_free_result($delete_result);
echo "<script language=javascript>";
if(true){ //判斷是否回滾提交
mssql_query("COMMIT TRANSACTION DEPS02_DEL"); //提交事務
echo "alert('delete success!');";
}else{
mssql_query("ROLLBACK TRANSACTION DEPS02_DEL"); //回滾事務
echo "alert('delete faile!');";
}
echo "</script>";mssql_close(); ?>

例3MySQL的事務處理在處理實際問題中有著廣泛且重要的應用,最常見的應用如銀行轉賬業務、電子商務支付業務等等。但是,值得注意的是,MySQL的事務處理功能在MYSIAM存儲引擎中是不支持的,在InnoDB存儲引擎中是支持的。現在上傳一段代碼,作為引導認識MySQL事務處理的開始,簡單的實例,但融匯思想,相信會有很大的幫助。
 

$conn=mysql_connect('localhost','root','yourpassword')or die(mysql_error());
mysql_select_db('transaction',$conn);
mysql_query('set names utf8');

//創建事務
mysql_query('START TRANSACTION') or die(mysql_error());
$sqlA="update A set account=account-1";
if(!mysql_query($sqlA)){
    mysql_query('ROLLBACK') or exit(mysql_error());//判斷當執行失敗時回滾
  exit();
}
$sqlB="update B set account=account+1";
if(!mysql_query($sqlB)){
    mysql_query('ROLLBACK') or exit(mysql_error());//判斷當執行失敗時回滾
  exit();
}
mysql_query('COMMIT')or die(mysql_error());//執行事務
mysql_close($conn);
?>

以上代碼可以作為模擬銀行轉賬業務的事務流程。以表A、B分別表示兩個已在銀行開戶的賬戶,當賬戶A執行轉出1元給賬戶B的操作時,如果操作執行失敗,轉出將會回滾至原始狀態,不繼續向下執行動作。反之,如果操作執行成功,則賬戶B可用余額將增加1元,否則事務回滾至原始狀態。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved