場景
先講下我當時遇到的一個應用場景:
一份含有5萬條數據的表,希望每次都能從中獲取到一條未被使用過的數據,然后再標記該條數據已被使用。
數據庫時mysql,為方便起見,轉化成sql的語義就是:
一張表:
value | status |
v1 | 1 |
v2 | 1 |
... | 1 |
我們需要一次讀取一行數據,再把該行的status字段改成0。它必然涉及到一次select,一次update,那如何保證的事務呢?
特別說明:表結構並不是必須這樣,你可以自己設計。
解決思路
如果你覺得很簡單:直接select這條數據,然后update它即可。那就真的悲劇了。
比如說這樣的讀寫SQL
SELECT value FROM table WHERE status = 1 LIMIT 1 UPDATE table SET status = 0 WHERE value = 'v1'
如果每次請求都如下圖一樣,是依次順序執行,的確是沒有任何問題
但是如果應用的並發量很高時,這樣就會出現問題,比如下圖中,用戶2在用戶1執行完畢select但沒執行完畢update時,執行了select。那么用戶1和用戶2將獲得同一條數據,這明顯不是我們想要的結果。
那又什么好的解決方案呢?相信很多人立馬想到的方案肯定是事務。不錯,用事務的確是種不錯的解決方案
解決方案一:事務
START TRANSACTION; SELECT value FROM table WHERE status = 1 LIMIT 1 UPDATE table SET status = 0 WHERE value = 'v1' COMMIT;或rollback;
但畢竟事務是種很影響性能的方法, 那有沒有可以不用事務的方法呢?
解決方案二:根據UPDATE結果判定是否有效
依據的原理是,在Mysql中執行UPDATE語句時,它會返回執行結果,具體如下:
mysql> UPDATE table SET status = 0 WHERE value = 'v1' AND status = 1; Query OK, 0 rows affected (0.01 sec) Rows matched: 1 Changed: 1 Warnings: 0
這里 Rows matched 表示匹配的數量,Changed表示改動的行數
回到剛到的問題,我們可以用這些信息來判斷是否成功
SELECT value FROM table WHERE status = 1 LIMIT 1 UPDATE table SET status = 0 WHERE value = 'v1' AND status = 1
當返回結果是:
Rows matched: 1 Changed: 1 Warnings: 0
表示這次數據更新是有效的。
當返回結果是:
Rows matched: 0 Changed: 0 Warnings: 0
表示這次數據更新失敗,因為該行數據已經別人占用,需要重試
但這種方法的問題,就是並發量特別高時,很多請求會出現沖突,需要重試。
解決方案三:利用表的自增id屬性
id | value | status |
1 | v1 | 1 |
2 | v2 | 1 |
id 是該表的自增主鍵(auto_increment)
需要另外一張表table2
id | ... |
1 | ... |
2 | ... |
... | ... |
id 也是該表的自增主鍵(auto_increment)
INSERT INTO table2 ( ....) VALUES( .... ); 獲取到table2 中最近的id UPDATE table SET status = 0 WHERE id = id SELECT value FROM table WHERE id = id
由於自增id是唯一性的,所以可以保證最終得到的數據也是唯一性的,但缺點也非常明顯:多需要一張表,而且兩張表的id需要一定的映射關系。
解決方案四:引入requestID字段
表設置為:
value | status | request_id |
v1 | 1 | |
v2 | 1 |
使用SQL:
UPDATE table SET status = 0, request_id = 'xxxxx' WHERE status = 1 LIMIT 1 SELECT value FROM table WHERE request_id = 'xxxxx'
只要保證request_id是唯一性,我們得到的結構也肯定是有效的
希望對大家有所幫助。^v^