現在有個發放啟用碼的系統,php+mysql。
code表:
id(主鍵自增長),code(啟用碼內容,如123abc),status(1代表未被發放,0代表已被發放)。
現在有很多使用者(註冊使用者,能拿到使用者資訊)去抽這些啟用碼,每人每天只能抽1次,這一次是肯定能抽到的。
每當使用者抽一次啟用碼,就找到一行status為1的啟用碼記錄,把這行記錄的status置為0,同時在record表裡添加一行記錄(使用者id和啟用碼id),然後返回啟用碼內容給使用者。
邏輯挺簡單,現在我要解決的問題是,就是高並發情況下可能會有問題:
比如很多使用者同時抽啟用碼,我select一行status為1的啟用碼記錄時,可能多個使用者會select到同一個啟用碼。有沒有什麼辦法當某個請求select一行記錄時,就把這行記錄鎖住,包證其他請求不會拿到這個啟用碼。
希望各位大神幫我分析下我這種邏輯在高並發下可能會出現什麼問題,有什麼解決方案?因為我現在做的項目實際上會遇到高並發的情況,所以必須考慮進去。。。
----------補充----------
謝謝大家的答案, 我會一一參考的。另外可能沒說清楚,啟用碼是提前產生好的,數量和內容都是固定的,提前插入進資料庫。
回複內容:
現在有個發放啟用碼的系統,php+mysql。
code表:
id(主鍵自增長),code(啟用碼內容,如123abc),status(1代表未被發放,0代表已被發放)。
現在有很多使用者(註冊使用者,能拿到使用者資訊)去抽這些啟用碼,每人每天只能抽1次,這一次是肯定能抽到的。
每當使用者抽一次啟用碼,就找到一行status為1的啟用碼記錄,把這行記錄的status置為0,同時在record表裡添加一行記錄(使用者id和啟用碼id),然後返回啟用碼內容給使用者。
邏輯挺簡單,現在我要解決的問題是,就是高並發情況下可能會有問題:
比如很多使用者同時抽啟用碼,我select一行status為1的啟用碼記錄時,可能多個使用者會select到同一個啟用碼。有沒有什麼辦法當某個請求select一行記錄時,就把這行記錄鎖住,包證其他請求不會拿到這個啟用碼。
希望各位大神幫我分析下我這種邏輯在高並發下可能會出現什麼問題,有什麼解決方案?因為我現在做的項目實際上會遇到高並發的情況,所以必須考慮進去。。。
----------補充----------
謝謝大家的答案, 我會一一參考的。另外可能沒說清楚,啟用碼是提前產生好的,數量和內容都是固定的,提前插入進資料庫。
建議使用redis,速度也快,每天定時再將資料落地到資料庫
Redis 本身提供的所有 API 都是原子操作
1、select id, code from XXX where status = 0 limit 1;然後更新的時候記得帶上條件,update XXX set status = 1 where id = #id# and status = 0;如果update失敗,那麼重新select,然後update,如此迴圈。
2、一種方式用隊列,取一條記錄的同時等於刪除了這條記錄,redis可以實現。
在項目裡面建一個檔案,
- 先判斷這個檔案是否被鎖
- 鎖定的話就等待,沒被鎖定的話就給檔案加鎖
- 拿到鎖之後讀資料庫
- 執行完操作後給檔案解鎖
這樣做只解決了互斥的問題,但是並沒解決高並發的問題,建議題主試試redis隊列
這個需要樓主把code表的儲存引擎設定為Innodb的,然後搜尋下mysql innodb 交易隔離等級。
推薦一篇文章
http://imysql.cn/2008_07_10_innodb_tx_isolation_and_lock_mode
一般不推薦直接在DB上做這種事務隔離處理,一般是在應用中處理的。
InnoDB 行鎖 + 事務,
Start Transaction; Select * From `xxx` Where `id` = 123456 for Update;Update `xxx` Set status = 0 Where `id` = 123456;Commit;
這裡id必須為主鍵。否則會導致全表鎖死
也可以後端邏輯裡用cache做一個隊列,非同步傳達結果
參考樓上的 InnoDB 行鎖 就夠了 可以不用事務吧
判斷一下 update set 的傳回值 也就知道是否有更新成功了 如果沒有影響任何記錄行 就知道更新無效了
正常來說
@白菜哥永遠都是大白菜 的答案應該滿足需求了。
不過我仔細想了下,樓主的需求在查詢的時候應該是不知道主鍵的,sql應該更像是
select * from xxx where status=1 limit 1;這種。
所以高並發的事務select的應該都是同一行。只有等第一個事務完成update後才會全部select到下一行。
所以效果實際上等同表鎖?哈,我也不太懂細節,這個問題比較有意思,我決定作為一個實驗列在todo list裡面。
回到樓主的問題,我更喜歡在代碼層面上做個生產消費隊列。
除了樓上各位說的InnoDB行鎖外,從資料庫中select一條啟用碼時可以根據使用者ID簡單的做一下處理:
offset = user_id % 30;
SELECT * FROM table WHERE XXX LIMIT offset,1;
這樣並發取同一條啟用碼的可能性就更低了,減少了等待鎖的時間。