多線程並發訪問資料庫中不同記錄時應該採用什麼辦法?

我做了一個模擬ATM的伺服器,接受客戶端請求,最終處理選擇用資料庫處理,問題來了!

當多個客戶端同時訪問伺服器時,我用多線程分別處理每個客戶端的請求。

然後每個客戶端都要讀寫數據到資料庫,那麼我應該並發訪問資料庫,那麼應該如何防止多個線程訪問同一條記錄

如果暴力地用互斥量,豈不是多個線程間會阻塞,和單線程沒有區別了。

這種情況的標準解決方法是什麼啊。。。知乎大神很多。。。求大神用簡單的表述方法說一下。。。百度了好多有點不知所云的感覺。。。


ATM問題是資料庫使用(注意不是設計和實現)學習中會遇到的經典問題,很多教程都是以此引入事務功能的學習。

簡單的說就是鎖行然後開個事務,或者開個事務然後校驗。


關係型資料庫基本已經解決你的問題了。

資料庫有行級鎖。

注意以下都是一個事務內包含了多個線程並發訪問時候的一些建議,多線程程序一般都是開在同一個事務點上,所以才有下面的一點建議。

======================================================

但設計的程序的時候還是很有難度的。

一個事務內的多個線程,盡量別交叉執行,分兩部分執行,部分1 讀資料庫 部分2 寫資料庫。

先讀後寫,如果業務允許的話。這樣可以有效避免很多障礙,甚至很解決不了的坑。

(比如一個寫線程還沒提交,一個大的讀線程恰巧導致寫線程無法提交,等待讀線程完成才會提交,會照成很長時間的鎖等待,一堵塞,就很容易產生資料庫假死。)

對於一個事務內的多個線程,有時候臟讀是合理的情況下,一定要合理利用,這樣可以避免無法分成讀寫兩部分執行而產生等待的問題,比如可以提前在寫隊列裡面提前commit,那麼讀的部分完全可以非同步,首先要確認業務是否可行,要不就會出錯數據,這地方是個鋼絲繩。

也就是,題主不要認為多線程可以完全非同步,那樣成本和設計難度是幾何上升的(關係型資料庫幾乎不太可能)。

非同步和同步混用,可以考慮採用隊列等數據結構來進行多線程式控制制順序。


關係資料庫系統本來都幫你處理好這些事情了。你自己在自己的系統裡面再搞一次,反而化神奇為腐朽,好好地把性能給做爛了。如果你不相信的話,你可以寫一個小系統,背後用mongodb,然後自己的系統裡面把transaction的事情給做了,吃吃屎,你就明白了


基本的伺服器程序都要多進程同時運行,這時候就早已超越線程同步互斥量的問題了。

作為對軟狗深惡痛絕的匿名用戶甲表示請跪讀 vczh 答案並抄寫 20 遍,然後

以解決問題為目的:去跪讀你選用資料庫的 transaction 相關文檔。如果選用的資料庫沒有 transaction 請換一個資料庫,比如換 PostgreSQL。

以學習知識為目的:找本經典的講資料庫基礎的書,把 ACID 的部分,transaction 使用的部分讀完,然後是 OS 進程、線程同步、POSIX 文件鎖、IPC 等一坨東西讀完……然後試著寫一個最基本的 transaction server 吧。


把資料庫各種級別的鎖了解幾遍


瀉藥。

我對資料庫不熟悉。但是無論如何,邏輯上總是這樣的:在同一時間,你只能有一個進程/線程在寫。所以,它自然會成為瓶頸。

這種鎖機制,很有可能資料庫引擎本身已經做進去了:你就算關門放一堆線程/進程寫同一個記錄,人家資料庫引擎也會同時只放一個去寫。

當然,這與你的業務邏輯密切相關。比如你需要同時寫幾個記錄作為一個完整的業務操作,那你有可能需要人工地先鎖上,等都寫完了再放開,以防有誰讀到寫了一半的狀態。

但是,仍然要注意,資料庫引擎本身應當提供了這種功能,甚至很有可能已經提供了完整事務的功能。你應當仔細閱讀資料庫引擎的說明,並且仔細考慮你的業務邏輯需要怎麼樣的鎖。

無論如何,簡單地使用線程鎖是不行的。你的讀寫衝突很有可能來自另一個進程,你這個線程的鎖壓根管不到。


這種系統標準的架構是 通過MQ接收請求和返回應答,業務處理邏輯不需要額外的同步機制,靠資料庫事務解決就可以了。

優化性能的關鍵是控制業務處理的進程或線程的並發數量。

如果僅是個實驗性質的項目,可以不用MQ,用信號量或者線程池來控制並發程度。

補充說明一下:

其實ATM系統的難點不在技術層面上,其交易流程設計才是關鍵,涉及交易的可靠性和安全性,需要考慮各種異常情況下的處理機制。

假如一個人在ATM上取款的交易在伺服器端的賬戶系統中端扣款成功,但最終沒有成功的取到鈔票。那麼可能發生了哪些異常?針對每種異常該怎麼處理才能保證客戶的資金最終不會損失?

相對簡單的情況是同行本地取款,既ATM所屬單位和你的資金賬戶是同一個城市/地區的同一家銀行。更複雜的情況是本地跨行取款、異地同行取款、異地跨行取款。你平時在ATM上取款時有沒有注意到機器上面的「銀聯」、「金融聯」、"深銀聯"等標誌,有沒有想去了解其背後的運作機制?

ATM取款其背後的系統沒那麼簡單,拿ATM取款當面試題去考一個程序員,這個面試官也不見得對ATM取款有多少了解。


這個要麼業務層加鎖,要麼使用版本號,加一個欄位,或者使用資料庫事物功能。資料庫本身的行級鎖沒用。行級鎖只能保證更新該行的時候不會有其他線程操作或者讀取該行。考慮一下以下情況,有個賬號同時在atm取款(別告訴我一個賬號就一張卡),假設都取了1000,那麼會有如下sql

update deposit set balance = balance -1000 where id=xxx

如果是這樣,會出現balance為負數的情況,2台機器讀取餘額,發現都是1100,這時候2台同時更新,就會出現-900,存款是不會負的,就變成幾億了,好久以前的新聞就有這樣的報道。

當然改成以下sql就可以

update deposit set balance =balance -1000 where id=xxx and balance&>=1000

更新成功之後再吐錢,如果失敗,要重新檢查餘額。

當然有的更新不是數字,這時候就要加版本號,讀取的時候讀版本號,更新的時候版本號加1,如

update members set config=json(config),ver=ver+1 where id=xxx and ver=ver

假設config欄位用來存放一些用戶配置,如果2個同時配置,如果不加版本號,可能就會衝突,導致一個配置失效。

config=jsondecodr(row.config)

config.name=」xxx」

另外一個線程

config.phone=13677889900


樂觀鎖能幫你解決問題。


lock in share mode 或者for update可以嗎 innodb行鎖


怎麼那麼像我們學校的某個課程的課堂作業


推薦閱讀:

什麼情況下,需要使用分散式資料庫?
產品運營,如何做出一份優秀數據報表?
一台普通計算機能承擔伺服器嗎?
如何評判基於中間件的分散式mysql與 雲資料庫?
誰能用最簡單的語言或者例子說下 Mysql、SQLite、Mongo的區別呢?

TAG:資料庫 | C編程語言 | Linux開發 | 多線程 | 並發控制 |