PHP事務處理

MySQL5.X都已經發布好久了,但是還有很多人認爲MySQL是不支持事務處理的,這不得不怪他們是孤陋寡聞的,其實,只要你的MySQL版本支持BDB或InnoDB表類型,那麼你的MySQL就具有事務處理的能力。這裏面,又以InnoDB表類型用的最多,雖然後來發生了諸如Oracle收購InnoDB等令MySQL不爽的事情,但那些商業上的鬥爭與技術無關,下面以InnoDB表類型爲例簡單說一下MySQL中的事務。 先來明確一下事務涉及的相關知識: 事務都應該具備ACID特徵。所謂ACID是Atomic(原子性),Consistent(一致性),Isolated(隔離性),Durable(持續性)四個詞的首字母所寫,下面以“銀行轉帳”爲例來分別說明一下它們的含義: 原子性:組成事務處理的語句形成了一個邏輯單元,不能只執行其中的一部分。換句話說,事務是不可分割的最小單元。比如:銀行轉帳過程中,必須同時從一個帳戶減去轉帳金額,並加到另一個帳戶中,只改變一個帳戶是不合理的。 一致性:在事務處理執行前後,數據庫是一致的。也就是說,事務應該正確的轉換系統狀態。比如:銀行轉帳過程中,要麼轉帳金額從一個帳戶轉入另一個帳戶,要麼兩個帳戶都不變,沒有其他的情況。 隔離性:一個事務處理對另一個事務處理沒有影響。就是說任何事務都不可能看到一個處在不完整狀態下的事務。比如說,銀行轉帳過程中,在轉帳事務沒有提交之前,另一個轉帳事務只能處於等待狀態。 持續性:事務處理的效果能夠被永久保存下來。反過來說,事務應當能夠承受所有的失敗,包括服務器、進程、通信以及媒體失敗等等。比如:銀行轉帳過程中,轉帳後帳戶的狀態要能被保存下來。 再來看看哪些問題會用到事務處理: 這裏不說“銀行轉帳”的例子了,說一個大家實際更容易遇到的“網上購書”的例子。先假設一下問題的背景:網上購書,某書(數據庫編號爲123)只剩最後一本,而這個時候,兩個用戶對這本書幾乎同時發出了購買請求,讓我們看看整個過程: 在具體分析之前,先來看看數據表的定義: ------------------------------------------------------------------------------- create table book ( book_id unsigned int(10) not null auto_increment, book_name varchar(100) not null, book_price float(5, 2) not null, #我假設每本書的價格不會超過999.99元 book_number int(10) not null, primary key (book_id) ) type = innodb; #engine = innodb也行 ------------------------------------------------------------------------------- 對於用戶甲來說,他的動作稍微比乙快一點點,其購買過程所觸發的動作大致是這樣的: ------------------------------------------------------------------------------- 1. SELECT book_number FROM book WHERE book_id = 123; book_number大於零,確認購買行爲並更新book_number 2. UPDATE book SET book_number = book_number - 1 WHERE book_id = 123; 購書成功 ------------------------------------------------------------------------------- 而對於用戶乙來說,他的動作稍微比甲慢一點點,其購買過程所觸發的動作和甲相同: ------------------------------------------------------------------------------- 1. SELECT book_number FROM book WHERE book_id = 123; 這個時候,甲剛剛進行完第一步的操作,還沒來得及做第二步操作,所以book_number一定大於零 2. UPDATE book SET book_number = book_number - 1 WHERE book_id = 123; 購書成功 ------------------------------------------------------------------------------- 表面上看甲乙的操作都成功了,他們都買到了書,但是庫存只有一本,他們怎麼可能都成功呢?再看看數據表裏book_number的內容,已經變成“-1”了,這當然是不能允許的(實際上,聲明這樣的列類型應該加上unsigned的屬性,以保證其不能爲負,這裏是爲了說明問題所以沒有這樣設置) 好了,問題陳述清楚了,再來看看怎麼利用事務來解決這個問題,打開MySQL手冊,可以看到想用事務來保護你的SQL正確執行其實很簡單,基本就是三個語句:開始,提交,回滾。 ------------------------------------------------------------------------------- 開始:START TRANSACTION或BEGIN語句可以開始一項新的事務 提交:COMMIT可以提交當前事務,是變更成爲永久變更 回滾:ROLLBACK可以回滾當前事務,取消其變更 此外,SET AUTOCOMMIT = {0 | 1}可以禁用或啓用默認的autocommit模式,用於當前連接。 ------------------------------------------------------------------------------- 那是不是隻要用事務語句包一下我們的SQL語句就能保證正確了呢?比如下面代碼: ------------------------------------------------------------------------------- BEGIN; SELECT book_number FROM book WHERE book_id = 123; // ... UPDATE book SET book_number = book_number - 1 WHERE book_id = 123; COMMIT; ------------------------------------------------------------------------------- 答案是否定了,這樣依然不能避免問題的發生,如果想避免這樣的情況,實際應該如下: ------------------------------------------------------------------------------- BEGIN; SELECT book_number FROM book WHERE book_id = 123 FOR UPDATE; // ... UPDATE book SET book_number = book_number - 1 WHERE book_id = 123; COMMIT; ------------------------------------------------------------------------------- 由於加入了FOR UPDATE,所以會在此條記錄上加上一個行鎖,如果此事務沒有完全結束,那麼其他的事務在使用SELECT ... FOR UPDATE請求的時候就會處於等待狀態,直到上一個事務結束,它才能繼續,從而避免了問題的發生,需要注意的是,如果你其他的事務使用的是不帶FOR UPDATE的SELECT語句,將得不到這種保護。 最後看看PHP + MySQL事務操作的代碼演示: 實際LAMP應用中,一般PHP使用AdoDB操作MySQL,下面給出AdoDB相應的代碼方便大家查閱: -------------------------------------------------------------------------------

startTrans(); //實際,getOne所調用的查詢也可以直接放到rowLock來進行,這裏只是爲了演示效果能更明顯些。 $adodb->rowLock('book', 'book_id = 123'); $bookNumber = $adodb->getOne("SELECT book_number FROM book WHERE book_id = 123"); $adodb->execute("UPDATE book SET book_number = book_number - 1 WHERE book_id = 123"); $adodb->completeTrans(); // ... ?> ------------------------------------------------------------------------------- 其中,rowLock的方法就是調用的FOR UPDATE來實現的行鎖,你可能會想把“FOR UPDATE”直接寫到$adodb->getOne()調用的那條SQL語句裏面去實現行鎖的功能,不錯,那樣確實可以,但是並不是所有的數據庫都使用“FOR UPDATE”語法來實現行鎖功能,比如Sybase使用“HOLDLOCK”的語法來實現行鎖功能,所以爲了你的數據庫抽象層保持可移植性,我還是勸你用rowLock來實現行鎖功能,至於可移植性就交給AdoDB好了,嗯,有點扯遠了,今兒就說到這裏了。 ------------------------------------------------------------------------------- 附: AdoDB中存在一個setTransactionMode()方法,能夠設置事務的隔離級別,如下: SetTransactionMode allows you to pass in the transaction mode to use for all subsequent transactions for that connection session. Note: if you have persistent connections and using mysql or mssql, you might have to explicitly reset your transaction mode at the beginning of each page request. This is only supported in postgresql, mssql, mysql with InnoDB and oci8 currently. For example: $db->SetTransactionMode("SERIALIZABLE"); $db->BeginTrans(); $db->Execute(...); $db->Execute(...); $db->CommiTrans(); $db->SetTransactionMode(""); // restore to default $db->StartTrans(); $db->Execute(...); $db->Execute(...); $db->CompleteTrans(); Supported values to pass in: * READ UNCOMMITTED (allows dirty reads, but fastest) * READ COMMITTED (default postgres, mssql and oci8) * REPEATABLE READ (default mysql) * SERIALIZABLE (slowest and most restrictive) You can also pass in database specific values such as 'SNAPSHOT' for mssql or 'READ ONLY' for oci8/postgres.

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章