本文將和你們分享 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