mysql锁争用_关于MYSQL条件竞争与锁的问题
最近在整理關(guān)支付安全的內(nèi)容,其中就是涉及到了一個(gè)在支付過程中的條件競爭問題。以下都是基于mysql的與php的架構(gòu)來描述該問題,大佬勿噴。
0x01. 條件競爭
什么是條件競爭:
競爭條件?發(fā)生在多個(gè)線程同時(shí)訪問同一個(gè)共享代碼、變量、文件等沒有進(jìn)行鎖操作或者同步操作的場景中。【W(wǎng)ikipedia-computer_science】
一個(gè)簡單的購買的業(yè)務(wù):
后臺(tái)代碼實(shí)現(xiàn)如下:
以上是一個(gè)購買商品的流程,看似并沒有什么問題。
如果每次請(qǐng)求都是一個(gè)單線程的請(qǐng)求是沒有什么問題的,但是如果采用多線程的并發(fā)請(qǐng)求就會(huì)出現(xiàn)問題。
因?yàn)槊看蔚馁徺I流程都是需要一定的時(shí)間去按照購買的流程執(zhí)行,如果我們?cè)诘谝淮钨徺I的流程3還沒有結(jié)束時(shí),就再去執(zhí)行這一遍流程時(shí),那么在第流程2時(shí)查詢用戶的余額就還是初始的余額,這是因?yàn)榈谝淮钨徺I的流程3還沒有結(jié)束,沒有結(jié)束也就意味著余額沒有扣除,所以金額就是初始的值。
在上面的百年青花瓷購買的案例中,如果我們只有1000塊錢,但是我們?cè)诙嗑€程的情況下去購買青花瓷的時(shí)候就可能購買到10件以上的數(shù)目。
0x02. 實(shí)際測試
在數(shù)據(jù)庫查看用戶數(shù)據(jù)
2.打開購買頁面
打開burp攔截購買商品的數(shù)據(jù)包,點(diǎn)擊購買。
設(shè)置intruder發(fā)送50個(gè)數(shù)據(jù)包,線程調(diào)到25后發(fā)包
并發(fā)請(qǐng)求后查看購買頁面,已擁有17件,余額成了-700。
我們調(diào)出mysql的查詢?nèi)罩尽?/p>
從日志中可以看到我們的請(qǐng)求是并發(fā)的執(zhí)行的,在一次查詢還沒有結(jié)束時(shí)就進(jìn)行了下一次的查詢,所以這也很容易產(chǎn)生兩次查詢的余額是相同的。
所以當(dāng)我只剩100塊的只能購買一件商品的時(shí)候,但是有可能兩次查詢余額都100,是符合購買流程的操作的,后面也會(huì)對(duì)余額100進(jìn)行兩次扣除操作,所以最后余額變成了負(fù)數(shù),購買的數(shù)量也大于10。
0x03. Solve the problem
mysql事務(wù)
在網(wǎng)上看到一篇關(guān)于mysql與php的條件競爭的分析中的解決方案是這樣的:
這種解決的方案的意思是給mysql查詢進(jìn)行一個(gè)事務(wù)的處理,在mysql的查詢前添加一個(gè)BEGIN,開始一個(gè)事務(wù),在結(jié)束時(shí)添加一個(gè)COMMIT提交一個(gè)事務(wù),完成一個(gè)查詢操作。
本地測試一下
設(shè)置好線程再次并發(fā)購買一次,結(jié)果發(fā)現(xiàn)還是失敗了。并沒有解決條件競爭帶來的問題,所以解決方案是不行的。
什么是mysql的事務(wù)
事務(wù)是一組原子性sql查詢語句,被當(dāng)作一個(gè)工作單元。若mysql對(duì)改事務(wù)單元內(nèi)的所有sql語句都正常的執(zhí)行完,則事務(wù)操作視為成功,所有的sql語句才對(duì)數(shù)據(jù)生效,若sql中任意不能執(zhí)行或出錯(cuò)則事務(wù)操作失敗,所有對(duì)數(shù)據(jù)的操作則無效(通過回滾恢復(fù)數(shù)據(jù))。
通過上面一句話差不多就知道了原因,只有在查詢語句不能執(zhí)行或出錯(cuò)則事務(wù)操作失敗, 所以我們添加事務(wù)并不能解決mysql競爭的問題,因?yàn)槲覀兊牟樵兪遣粫?huì)錯(cuò)誤的,既然不會(huì)出錯(cuò)也就會(huì)照樣執(zhí)行并發(fā)的請(qǐng)求。
0x04. mysql鎖
悲觀鎖
悲觀并發(fā)控制(又名“悲觀鎖”,Pessimistic Concurrency Control,縮寫“PCC”)是一種并發(fā)控制的方法。它可以阻止一個(gè)事務(wù)以影響其他用戶的方式來修改數(shù)據(jù)。如果一個(gè)事務(wù)執(zhí)行的操作對(duì)某行數(shù)據(jù)應(yīng)用了鎖,那只有當(dāng)這個(gè)事務(wù)把鎖釋放,其他事務(wù)才能夠執(zhí)行與該鎖沖突的操作。 悲觀并發(fā)控制主要用于數(shù)據(jù)爭用激烈的環(huán)境,以及發(fā)生并發(fā)沖突時(shí)使用鎖保護(hù)數(shù)據(jù)的成本要低于回滾事務(wù)的成本的環(huán)境中。
當(dāng)我們查詢的數(shù)據(jù)隨時(shí)可能會(huì)被其他操作修改時(shí),我們對(duì)這個(gè)數(shù)據(jù)進(jìn)添加一個(gè)悲觀鎖,如果想再次對(duì)這個(gè)數(shù)據(jù)進(jìn)行操作時(shí),只有這條查詢的操作結(jié)束后釋放這個(gè)悲觀鎖,其他查詢才可以對(duì)這條數(shù)據(jù)進(jìn)行操作,如果鎖定沒有結(jié)束時(shí),其他查詢會(huì)一直進(jìn)行一個(gè)等待的狀態(tài)。
mysql 悲觀鎖的實(shí)現(xiàn)
select * from goods where id = 1 for update;
for update僅適用于InnoDB引擎,且必須在事務(wù)塊(BEGIN/COMMIT)中才能生效。在進(jìn)行事務(wù)操作時(shí),通過“for update”語句,MySQL會(huì)對(duì)查詢結(jié)果集中每行數(shù)據(jù)都添加排他鎖,其他線程對(duì)該記錄的更新與刪除操作都會(huì)阻塞。排他鎖包含行鎖、表鎖。
使用悲觀鎖解決上述并發(fā)問題:
并發(fā)測試:
經(jīng)過多次測試后發(fā)現(xiàn)商品購買正常。沒有出現(xiàn)條件競爭的問題
我們這邊來看下后端mysql查詢的日志
我們吧mysql的查詢分成了11組
前七組都沒條件競爭的問題,所有操作都是有序執(zhí)行的,但是在第八組的時(shí)候開始出現(xiàn)問題
在第八條數(shù)據(jù)查詢的事務(wù)還沒有結(jié)束時(shí)就開始查詢第九條的數(shù)據(jù)了
但是由于我們使用了for update(悲觀鎖),對(duì)select語句進(jìn)行鎖定,所以在執(zhí)行到第九條的時(shí)候發(fā)現(xiàn)第八條的事務(wù)還沒有結(jié)束,所以他就只能等待第八條的更新完庫存之后執(zhí)行commit(提交事務(wù))操作,第八條查詢的鎖才會(huì)進(jìn)行釋放,然后第九條查詢才能獲取到用戶的余額進(jìn)行下一步操作。所以通過悲觀鎖解決了條件競爭帶來的問題。
但是悲觀鎖的弊端在每次查詢都會(huì)對(duì)數(shù)據(jù)進(jìn)行鎖定,在高并發(fā)的請(qǐng)請(qǐng)求下會(huì)變得很慢。所以高并發(fā)的請(qǐng)求不建議使用悲觀鎖。
樂觀鎖
時(shí)間有限,下次再講
在這個(gè)寒冷的時(shí)節(jié)里
因?yàn)橛心愕年P(guān)注
而變得溫暖
總結(jié)
以上是生活随笔為你收集整理的mysql锁争用_关于MYSQL条件竞争与锁的问题的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: mysql 在字段中计算_整数在MySQ
- 下一篇: python中单双三引号区别_pytho