修正
2020-04-23 之前是剛參加工作的理解 很多問題,現在回來重新梳理
事物的特性
原子性
表示一個最小的邏輯單元,要么都執行 要么都不執行
一致性
事物處理前與處理后的狀態的要是一致的(a賬戶有200元 b賬戶有300元 共計500元 a賬戶給b賬戶轉賬100元。事物處理后2個賬戶總額也為500元)
隔離性
每個事物都有自己的數據空間,使事物的處理結果不會被別的事物所影響
持久性
事物提交 數據就永久的保存下來了
事物的隔離級別
Read uncommitted(讀未提交)
會導致臟讀,我們舉例一個下單場景:
1.商品編號:a1的庫存是10。a用戶下單購買了10個商品,庫存變成了10
2.a用戶因為地址拍錯執行退貨操作:退貨分為 1.開啟事物 2.訂單狀態改為取消,3.庫存還原 4.執行其他業務邏輯 5.提交事物
3.在執行 2.4的其他業務邏輯的時候 b.用戶發現庫存有10 執行購買 並提交事物,后續a用戶退貨操作因為某些操作失敗 事物回滾,導致超賣
扣除庫存代碼
public boolean reduceStock(Long id,Long purchaseCount){ ProductStock productStock=ProductStockDao.findById(id); if(productStock.getCount()<purchaseCount){ throw new BusinessException(500,"庫存不足"); } //庫存充足 productStock.setCount(productStock.getCount()-purchaseCount); ProductStockDao.update(productStock); return true; }
Read committed 讀取已經提交的
sqlserver默認的隔離級別,因為當前事物只能讀取其他數據已經提交的數據,所以我們上面例子 第3步會校驗庫存不足
其實這里並發還是會超賣
比如10個用戶都下單10 並發都同時走到 if(productStock.getCount()<purchaseCount){ 這個時候庫存都是10 判斷庫存充足下單成功
建議通過加鎖,或者sql控制如:
public boolean reduceStock(Long id,Long purchaseCount){ int updated= productStockDao.execute("update product_stock set count=count-:purchaseCount WGERE count>=0 and id=:id",purchaseCount,id); return updated>0; }
版本號樂觀鎖
public boolean reduceStock(Long id,Long purchaseCount){ ProductStock productStock=ProductStockDao.findById(id); String newVersion=productStock.getVersion()+1; int updated= productStockDao.execute("update product_stock set count=count-:purchaseCount,version:newVersion where id=:id and version=:version",purchaseCount,newVersion,id,productStock.getVersion()); return true; }
第一種適合數值的扣除,第二種適合數據修改(體驗不是很好)
此隔離級別,可以解決我們的臟讀但是會出現不可重復讀
如:
1.查詢用戶性別
2.如果性別是null 則update set為男
3.再次查詢並返回用戶性別
在第三這個步驟時,用戶性別被其他用戶改成了女 返回女
Repeatable read 可重復讀
可以解決重復讀問題,因為這個隔離級別讀取的是快照,同時update的時候會加x 事物沒提交前別的事物不能修改 但是會出現幻讀
如:
查詢a用戶的消費總額
select sum(money) from pay_log where userId=a 第二次查詢的時候 log表又增加了一條數據,導致幻讀
注:網上都是這樣說 我的理解因為有gap鎖其他事物也不能添加 我覺得這個隔離級別也可以一定程度避免幻讀
Serializable 序列化
這個隔離級別最高 能夠避免 臟讀 不可重復讀 幻影讀 但是效率低(當進行當前讀的時候 是鎖表 性能差)
解決可重復讀和幻讀 都是通過加鎖來解決的具體可看:《mysql deadlock、Lock wait timeout解決和分析》