MySQL 如何在一個語句中更新一個數值後返回該值 -- 自增加種子競態問題處理

什麼是競態問題?併發

  假設有一個計數器,首先當前值自增加,而後獲取到自增加以後的當前值。自增加後的值有可能被有些操做用來當作惟一性標識,所以併發的操做不能容許取得相同的值。性能

  爲何不能使用使用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();
相關文章
相關標籤/搜索