MySQL 的更新語句

本文將和你們分享 MySQL 更新語句的一些小衆語法,及筆者在使用多表關聯更新遇到的一些問題。mysql

先來看單表更新的語法:sql

UPDATE [LOW_PRIORITY] [IGNORE] table_reference
    SET assignment_list
    [WHERE where_condition]
    [ORDER BY ...]
    [LIMIT row_count]

你們可能會以爲奇怪,在更新語句中竟然能用 ORDER BY 子句和 LIMIT 子句。沒錯,ORDER BY 子句用來指定數據行的更新順序,LIMIT 子句限制數據更新的行數。優化

咱們結合例子來看,建立一張 test 表用來演示,它的表結構及數據以下:code

CREATE TABLE `test` (
  `id` int unsigned NOT NULL AUTO_INCREMENT,
  `col1` int DEFAULT NULL,
  `col2` int DEFAULT NULL,
  `col3` varchar(32) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8;


    id    col1    col2  col3    
------  ------  ------  --------
     1       1      10  hello   
     2       1      20  world   
     3       1      30  world   
     4       1      40  nice    
     5       1      50  hello

test 表有 5 行數據,其中 col1 列的值徹底同樣,都是數值 1 。blog

先看 LIMIT 子句的使用。ci

UPDATE 
  test 
SET
  col1 = 2 
LIMIT 2;

---------------------------------------------------
1 queries executed, 1 success, 0 errors, 0 warnings

查詢:update test set col1 = 2 limit 2

共 2 行受到影響

上面的語句將 col2 列的值改成數值 2,可是隻改變其中的兩行。咱們經過觀察執行更新後的 test 表的數據,確實只更新了兩行。文檔

id    col1    col2  col3    
------  ------  ------  --------
     1       2      10  hello   
     2       2      20  world   
     3       1      30  world   
     4       1      40  nice    
     5       1      50  hello

再來看 ORDER BY 子句。get

UPDATE 
  test 
SET
  col1 = 3 
ORDER BY id DESC 
LIMIT 2;

---------------------------------------------------
1 queries executed, 1 success, 0 errors, 0 warnings

查詢:update test set col1 = 3 order by id desc limit 2

共 2 行受到影響

這回咱們指定了按照 id 列的逆序更新 col1 列的值,也只更新兩行,結果和咱們預期的一致。it

id    col1    col2  col3    
------  ------  ------  --------
     1       2      10  hello   
     2       2      20  world   
     3       1      30  world   
     4       3      40  nice    
     5       3      50  hello

不過,須要注意的是,若是更新的行的原來的值和要更新的值一致,那麼 MySQL 並不會真正執行更新操做,但仍會計入受 LIMIT 子句影響的行數。io

好比,咱們重複執行上面的更新語句,但 test 表的數據一點也沒變。

UPDATE 
  test 
SET
  col1 = 3 
ORDER BY id DESC 
LIMIT 2;

---------------------------------------------------
1 queries executed, 1 success, 0 errors, 0 warnings

查詢:update test set col1 = 3 order by id desc limit 2

共 0 行受到影響

另外,ORDER BY 子句和 LIMIT 子句不能用在多表關聯更新語句中。

看下面這個例子,是關於列的更新順序。對於單表的更新,執行順序一般是從左到右。

UPDATE 
  test a 
SET
  col1 = col1 * 10,
  col2 = col1 
WHERE id = 1;

猜猜看,上面這條更新語句,執行以後 id = 1 的行的 col2 字段的值是等於 col1 更新前的值,仍是更新後的值?

答案是後者,即更新後的值。這和標準 SQL 不太同樣。

再來看多表關聯更新的語法:

UPDATE [LOW_PRIORITY] [IGNORE] table_references
    SET assignment_list
    [WHERE where_condition]

注意,若是多表關聯經過 JOIN 來實現,而不是把關聯的條件放到 WHERE 子句中,那麼 JOIN 子句要放在 SET 子句以前。

UPDATE 
  test a 
  INNER JOIN test b 
    ON b.id = a.id SET a.col2 = b.col2 * 10 
WHERE a.col3 = 'hello';

-- 等價於下面的寫法
UPDATE 
  test a,
  test b 
SET
  a.col2 = b.col2 * 10 
WHERE b.id = a.id 
  AND a.col3 = 'hello' ;

有時候執行多表關聯更新時會遇到 ERROR 1093 (HY000): You can't specify target table 'xxx' for update in FROM clause 這個錯誤提示,其實不止更新語句,刪除語句也會有這個問題。

這個問題是怎麼產生的呢?其實是由於要更新的目標表同時存在子查詢裏面,請看下面這個例子。

UPDATE 
  test 
SET
  col1 = col1 * 10 
WHERE id IN 
  (SELECT 
    MIN(id) AS id 
  FROM
    test 
  GROUP BY col3 
  HAVING COUNT(*) = 1);
  
---------------------------------------------------------------
錯誤代碼: 1093
You can't specify target table 'test' for update in FROM clause

這個問題很早就存在了,在 2006 年的時候就有用戶向 MySQL 社區反饋,只是到了如今還沒處理。

好消息是 MariaDB 在 10.3.2 版本開始支持這類更新語句,相信在 MySQL 後續的版本中,也會加入這一支持。

這個問題在現階段怎麼解決呢?官方文檔給出的建議是使用派生表(在 FROM 子句後面可替表明的子查詢稱做派生表)。

方法一:

UPDATE 
  test 
SET
  col1 = col1 * 10 
WHERE id IN 
  (SELECT 
    id 
  FROM
    (SELECT 
      MIN(id) AS id 
    FROM
      test 
    GROUP BY col3 
    HAVING COUNT(*) = 1) t)

這種改寫方式能湊效是由於 MySQL 的優化器將派生表物化了(物化的操做可理解爲將查詢結果存到內部臨時表中),所以更新的目標表和子查詢裏面的表就不是同一個。

方法二:

UPDATE 
  test a,
  (SELECT 
    MIN(id) AS id 
  FROM
    test 
  GROUP BY col3 
  HAVING COUNT(*) = 1) b 
SET
  col1 = col1 * 10 
WHERE b.id = a.id
相關文章
相關標籤/搜索