一個設計良好的數據庫可以幫我們保證事務具有四大特性(ACID):
原子性:原子性是指事務是一個不可分割的工作單位,事務中的操作要么都發生,要么都不發生。
一致性:如果事務執行之前數據庫是一個完整的狀態,那么事務結束后,無論事務是否執行成功,數據庫仍然是一個完整的狀態。
數據庫的完整狀態:當一個數據庫中的所有的數據都符合數據庫中所定義的所有約束,此時可以稱數據庫是一個完整的狀態。
隔離型:多個用戶並發訪問數據庫時,一個用戶的事務不能被其他用戶的事務所干擾,多個並發事務之間數據要相互隔離。
持久性:指一個事務一旦被提交,他對數據庫的影響是永久性的。
數據庫可以保證原子性,一致性,持久性。但是隔離級別需要根據並發情況進行設置,
將數據庫設計成單線程,可以防止所有的線程安全問題,自然也就保證了隔離型的問題,但是如果這樣,那么效率就會極其低下。
如果兩個線程並發修改,一定會相互搗亂,這時必須利用鎖機制防止多個線程的並發修改
如果兩個線程並發查詢,沒有線程安全問題。
如果兩個線程一個修改,一個查詢:
四大隔離級別:
Read uncommitted:---不防止任何隔離型問題,不能防止臟讀/不可重復讀/幻讀問題。
Read commit:---可以防止臟讀問題,但是不能防止 不可重復讀/幻讀問題。(oracle默認隔離級別)
Repeatable read:---可以防止臟讀/不可重復讀,不能防止幻讀。(mysql默認隔離級別)
Serializable:--數據庫被設計為單線程,可以防止上述所有問題
臟讀:一個事務讀取到另一個事務未提交的數據。
場景:a賬戶1000,b賬戶1000,a到b的網店里買一百塊錢的鞋子。。。。
a:start transaction
update table set money=money-100 where name='a';//a賬戶扣除100
update table set money=money+100 where name='b';//b賬戶增加100
此時a還沒有提交事務,對b說付款成功
b:start transaction;//b開始查詢自己的賬戶余額
select money form table where name='b';
b:commit;
如果讀取到了a沒有提交的數據,那么b查出來的就會是1100,此時b開始發貨
此時a執行回滾
a:rollback;
那么b再去查詢賬戶余額會發現自己的賬戶還是原先的100塊
不可重復讀:在當前事務中,讀取到了另一事務提交的更新和刪除的數據。
場景:如果a有活期資金1000,定期1000,固定資產1000
a: 1000 1000 1000
b: 銀行職員,銀行讓職員b算出a客戶的總共資產
---------
b:start transaction;
select 活期存款 from account where name='a'; ---- 活期存款:1000
select 定期存款 from account where name='a'; ---- 定期存款:1000
select 固定資產 from account where name='a'; ---- 固定資產:1000
-------此時a客戶取走了自己活期的1000元
a:
start transaction;
update accounset set 活期=活期-1000 where name='a';
commit;
-------
select 活期+定期+固定 from account where name='a'; --- 總資產:2000,按照之前查出來的應該是3000元,但是現在是2000元
commit;
----------
但是雖然在這種場景下,出現了不可重復讀,但是如果在另一場景下,如果你和你的伙伴共用一個銀行賬戶共100萬,你的伙伴如果中途取走了50萬,而你此時讀取到剩下的50萬,就符合該場景。
幻讀:在當前事務中,讀取到了另一事務提交的插入的數據
場景:如果銀行現在有a、b兩位客戶存款各1000,銀行老板讓職員b去算平均每個客戶的平均存款,而又有新增客戶c存入了4000元。
a: 1000
b: 1000
d: 銀行業務人員
-----------
d:
start transaction;
select sum(money) from account; --- 2000 元
select count(name) from account; --- 2 個
------
c:
start transaction;
insert into account values(c,4000);
commit;
------
select sum(money)/count(name) from account; --- 平均:2000元/個,結果應該是平均存款1000,而實際結果確實2000
commit;
------------