什麼是競態問題?併發
假設有一個計數器,首先當前值自增加,而後獲取到自增加以後的當前值。自增加後的值有可能被有些操做用來當作惟一性標識,所以併發的操做不能容許取得相同的值。性能
爲何不能使用使用UPDATE語句更新計數器,而後SELECT語句獲取自增加後的當前值?問題在於併發的操做有可能獲取到相同的計數器值。spa
CREATE TABLE counters ( id INT NOT NULL UNIQUE, -- 計數器ID,多個計數器能夠存在一個表中, value INT -- 計數器當前值 ); -- 初始化計數器1,從10開始計數 INSERT INTO counters VALUES (1, 10); -- 計數器1自增步長1 UPDATE counters SET value = value + 1 WHERE id = 1; -- 獲取計數器1自增加後的當前值 SELECT value FROM counters WHERE id = 1;
如何避免競態問題?code
方法一:使用Transaction和SELECT FOR UPDATEblog
若是一個Transaction中執行SELECT FOR UPDATE,該步操做會鎖住該行記錄。其它對該行記錄的併發操做會被阻塞,直到當前SELECT FOR UPDATE所在Transaction提交或超時。io
START TRANSACTION; -- 鎖定計數器1 SELECT value FROM counters WHERE id = 1 FOR UPDATE; -- 計數器1自增加步長1 UPDATE counters SET value = value + 1 WHERE id = 1; -- 獲取自增加後的當前值 SELECT value FROM counters WHERE id = 1; COMMIT;
方法二:在一個語句中完成UPDATE和SELECTclass
方案一雖然可行而且可靠,可是加上了鎖後必定程度上可能會影響一些性能。幸運的是咱們可使用方案二,在一個語句中完成UPDATE和SELECT,能夠有兩種方法實現。變量
實現1:經過Session變量。方法
-- 計數器1當前值自增加步長1 UPDATE counters SET value = (@newValue := value + 1) WHERE id = 1; -- 獲取自增加以後的值 SELECT @newValue;
實現2:經過MySQL自帶的LAST_INSERT_ID方法di
LAST_INSERT_ID方法經常使用的場景是獲取自增加列最後一次插入的值。它還有另一個用法,當傳入一個值時它會返回傳入的值,而且在下一次調用不含參數的LAST_INSERT_ID()方法時,仍是會返回先前傳入的值。
-- 計數器1自增加步長1,並經過LAST_INSERT_ID(Num)方法記錄插入的值 UPDATE counters SET value = LAST_INSERT_ID(value + 1) WHERE id = 1; -- 獲取最後一次插入的值 (自增加以後的當前值) SELECT LAST_INSERT_ID();