在實際對數據庫的使用中,會出現多個用戶同時對某一張表進行操作,當多個用戶在同一時間對同一張數據表進行讀取或者修改操作時,若處理不當就有可能發生衝突問題。爲了解決這樣的問題,就需要使用事務的控制和管理機制。
事務
單個邏輯工作單元執行操作的集合,也可以看作是多條語句封裝的結果。通過事務可以保證數據表中數據的一致性。
事務的特性
原子性
是指事務中所有的執行操作,要麼全部成功,要麼不執行。如在商場購物中,管理員同時對用戶進行充值操作。
-
修改賬戶A中的現金數。
-
修改賬戶A中的現金數
如果在執行第一個SQL語句之後,第二個語句之前,突然斷電了該如何辦?
一致性
所謂的事務的一致性,是指一個事務操作執行完周,數據庫中的數據必須處於合法一致的狀態。例如如果賬戶A給賬戶B轉賬1000元后,那麼賬戶A應該減少1000,賬戶B應該增加1000,但是兩人的錢數總和還是一致的,應該處於合法的狀態中。
隔離性
就是事務看到的數據庫中的數據要麼是這個事務被修改之前的狀態,要麼是被修改之後的狀態。
持久性
如果一個事務被成功地修改,其結果在數據庫中不會因爲軟件,硬件等故障而改變,數據會永久的保留下來
控制事務的流程
START TRANSACTION(開始事務)、COMMIT(提交)、ROLLBACK(事務回滾)
顯示開始一個事務
使用START TRANSACTION或者BEGIN語句可以顯示開始一個新的事務
語法規則:
START TRANSACTION{事務名}
隱式開始一個事務
在SQL語句中的第一條語句開始就表示隱式開始了一個新的事務
提交事務
顯示提交:COMMIT[事務名]
- start transaction --開始事務
- insert into accounttable values('A',5000)
- update accounttable set cashvalue=cashvalue+1000
- where accountuser ='A'
- commit --提交事務
(這裏把兩條語句封裝到了一起,一個是插入語句,一個是更新語句)
隱式提交
是指通過使用SQL語句就可以完成事務的提交,如果執行了CREATE TABLE,DROP TABLE等
操作就會自動提交事務。
自動提交
自動提交指通過設置AUTOCOMMIT命令完成事務的提交。
自動提交語句
SET AUTOCOMMIT=1
SET AUTOCOMMIT ON
關閉自動提交方式
SET AUTOCOMMIT=0
SET AUTOCOMMIT OFF
回滾事務
是表示當事務執行失敗時,數據庫恢復到該事務操作之前的那一個合法狀態中,並撤銷對該表的一些操作。同時在數據庫中還可以設置保存點,可以當發生意外時,回滾到保存點狀態。
- begin try
- begin transaction
- --使用try語句進行捕捉錯誤
- insert into accounttable values(5000,'A')
- save tran a1
- insert into accounttable values(5000,'C')
- commit
- end try
- begin catch
- --當發生錯誤時,進行回滾
- rollback tran a1
- end catch
(這段SQL語句,分別設置了2個保存點,分別是A1,A2,並使用rollback回滾機制)
併發事務的工作流程
-
用戶user1使用SELECT語句查詢到accounttable賬戶中有5000元,但是由於某些原因,用戶user1並沒有提取現金
-
用戶2也通過SELECT語句查詢到accounttable賬戶中有5000元,此時他開始事務,從賬戶中提取出了1000元,但是並沒有提交事務。此時用戶user1查到的還是5000元
-
在用戶user1查詢到賬戶餘額爲5000後,希望全部提取出來,但是由於user2的事務並沒有提交,所以用戶1並不能提取
-
用戶2提取1000後,提交了事務,此時剩餘4000元
-
用戶1終於可以執行提取5000元操作了,可是此時只有4000元,操作將被撤銷,回滾到他之前的操作狀態
-
如果用戶1還想全部取出的話,就必須開始一個新的事務
通過以上的敘述我們發現事物併發處理中存在的問題
-
讀髒數據
是指那些已經更改但還是沒有被提交的數據。如用戶2取走1000後,用戶1提取5000時,會發現賬戶的餘額不足。
-
不能重複讀
同一個事物中在多次執行時,由於其他事務對其做的修改或者刪除等更新操作時,使得每次查詢時返回的數據結果都不相同。如上例中,用戶2已經取出1000元,而用戶1查詢到的仍然是5000元
-
幻想讀
是指讀取了其他事務中執行完插入或者更新操作後的錯誤數據。
------------------------------------------------------------------------
事務的隔離級別
1.READUNCOMMITTED:未提交讀。正如上面敘述的那樣,由於用戶2沒有提交,所以用戶1還是讀出了5000元。在READ UNCOMMITED隔離級別下,會隔離UPDATE語句,但不隔離SELECT語句。它的隔離級別最低。
2.READ COMMITTED:提交讀。給隔離級別在讀取數據時對其加共享鎖,可以避免讀髒數據,但是在READ COMMITTED隔離級別下,事務在結束前更改可以更改數據,因此不能避免不能重複讀或者幻想讀。
3.REPEATABLE READ:可重複讀。在該隔離級別中會將查詢中使用的所有數據鎖定,防止其他用戶對改數據進行操作,可以避免產生不能重複讀。
4.SERIALLZABLE :可串行化。該隔離級別在事務提交之前,會鎖定整個數據表,防止其他用戶對數據進行增加、刪除和修改等更新操作。
下面是有關四種隔離級別允許不同的類型的行爲
隔離級別 |
髒讀 |
不可重複讀取 |
幻象 |
未提交讀 |
是 |
是 |
是 |
提交讀 |
否 |
是 |
是 |
可重複讀 |
否 |
否 |
是 |
可串行讀 |
否 |
否 |
否 |
牛刀小試
下面的例子均以下表爲例
Table |
|
Money |
Int |
Name |
Char(4) |
髒讀操作
由上面的表格可知,髒讀操作發生在未提交數據時,正如我們的例子中,用戶1查到了5000的情況。如下操作
第一個連接語句
- <span style="font-family:SimSun;font-size:18px;">begin tran
- update table set money=103 where name='A'
- waitfor delay '00:00:10' --等待10秒
- update table set money=104 where name='A'
- commit tran</span>
接着馬上執行第二個連接語句
- <span style="font-family:SimSun;font-size:18px;">set transaction isolation level read uncommitted
- begin tran
- select money from table where name='A'
- commit tran</span>
最終結果是103,而不是104,這就是髒數據,可知如果我們把第二個連接中的事務隔離級別設置爲 READ MOMMITED、REPEATABLE READ或者SERIALLZABLE就可以避免這種情況發生。
非重複讀操作
第一個連接語句
- <span style="font-family:SimSun;font-size:18px;">set transaction isolation level read committed
- --或者是set transaction isolation level read uncommitted
- begin tran
- select money from table where name='A'
- waitfor delay '00:00:10' --等待10秒
- select money from table where name='A'
- commit tran
- </span>
接着馬上執行第二個連接語句
- <span style="font-family:SimSun;font-size:18px;">begin tran
- update table set money=10 where name='A'
- commit tran</span>
我們發現第一個連接中兩次返回賬號的餘額不一樣,第一次是100,第二次是10,這就是典型的“非重複讀”的問題
根據上表所示,如果把事務的隔離級別設置爲REPEATABLE READ或者SERIALLZABLE可以防止此類問題
幻象讀
根據上表所示,當事務的隔離級別爲READ COMMITTED、READ UNCOMMITTED、REPEATABLE READ時就會發生幻象
先看下面的例子(賬戶餘額爲100)
第一個連接語句
- <span style="font-family:SimSun;font-size:18px;">begin tran
- select * from table
- waitfor delay '00:00:10'--等待10秒
- select * from table
- commit tran</span>
接着馬上執行第二個連接語句
- <span style="font-family:SimSun;font-size:18px;">begin tran
- insert into table values(300,'a')
- commit tran</span>
我們發現兩次查詢的結果不一樣,這就是典型的”幻象讀”問題,可知解決方法爲把隔離級別設置爲SERIALLZABLE即可。
小結:在實際應用的時候,採用何種隔離級別應視具體情況而定。