【MySQL】用戶自定義變量


用戶自定義變量是一個用來存儲內容的臨時容器,在連接MySQL的整個過程中都存在。可以SETSELECT語句來定義。

SET @one := 1;
SET @min_actor := (SELECT MIN(actor_id) FROM sakila.actor);
SET @last_week := CURRENT_DATE-INTERNAL 1 WEEK;

MySQL 自定義變量的局限性:

  • 使用自定義變量的查詢,無法使用查詢緩存。
  • 不能再使用常量或者標識符的地方使用自定義變量,例如表名、列名和LIMIT子句中。
  • 用戶自定義變量的生命周期是在一個連接中有效,所以不能用它們來做連接間的通信。
  • 如果使用連接池或持久連接,自定義變量會導致你的代碼與交互隔離,這出現的時候可能是代碼的 bug 或連接池的 bug,但是是可能發生的。
  • 在 MySQL 5.0以前的版本中是大小寫敏感的,因此要注意(在 MySQL 5.0以后已經不區分大小寫了)。
  • 不能顯式地聲明自定義變量的類型。確定未定義變量的具體類型的時機在不同MySQL版本中也可能不一樣。如果你希望變量是整數類型,那么最好在初始化的時候就賦值為0,如果希望是浮點型則賦值為0.0,如果希望是字符串則賦值為’’,用戶自定義變量的類型在賦值的時候會改變。MySQL的用戶自定義變量是一個動態類型。
  • MySQL優化器在某項些場景下可能會將這些變量優化掉,這可能導致代碼不按預想的方式運行。
  • 賦值的順序和賦值的時間點並不總是固定的,這依賴於優化器的決定,實際情況可能很讓人困惑。
  • 賦值符號:=的優先級非常低,所以需要注意,賦值表達式應該使用明確的括號。
  • 使用未定義變量不會產生任何語法錯誤,如果沒有意識到這一點,非常容易犯錯。

用戶自定義變量的用法:

  • 查詢運行時計算總數和平均值。
  • 模擬GROUP BY語句中的函數FIRST()和LAST()。
  • 對大量數據做一些數據計算。
  • 計算一個大表的MD5散列值。
  • 編寫一個樣本處理函數,當樣本中的數值超過某個邊界值的時候將其變為0。
  • 模擬讀/寫游標。
  • 在SHOW語句的WHERE子句中加入變量值。

案例:

1、優化排名
mysql> SET @rownum := 0;
mysql> SELECT actor_id, @rownum := @rownum + 1 AS rownum
FROM actor order by actor_id LIMIT 3;
    +----------+--------+
    | actor_id | rownum |
    +----------+--------+
    |        1 |      1 |
    |        2 |      2 |
    |        3 |      3 |
    +----------+--------+
mysql> SET @curr_cnt := 0, @prev_cnt := 0, @rank := 0;
mysql> SELECT actor_id, COUNT(*) as cnt
    -> FROM film_actor
    -> GROUP BY actor_id
    -> ORDER BY cnt DESC
    -> LIMIT 10;
    +----------+-----+
    | actor_id | cnt |
    +----------+-----+
    |      107 |  42 |
    |      102 |  41 |
    |      198 |  40 |
    |      181 |  39 |
    |       23 |  37 |
    |       81 |  36 |
    |       37 |  35 |
    |      106 |  35 |
    |       60 |  35 |
    |       13 |  35 |
    +----------+-----+
mysql> SELECT actor_id,
    -> @curr_cnt := COUNT(*) AS cnt,
    -> @rank     := IF(@prev_cnt <> @curr_cnt, @rank + 1, @rank) AS rank,
    -> @prev_cnt := @curr_cnt AS dummy
    -> FROM film_actor
    -> GROUP BY actor_id
    -> ORDER BY cnt DESC
    -> LIMIT 10;
    +----------+-----+------+-------+
    | actor_id | cnt | rank | dummy |
    +----------+-----+------+-------+
    |      107 |  42 |    0 |     0 |
    |      102 |  41 |    0 |     0 |
    |      198 |  40 |    0 |     0 |
    |      181 |  39 |    0 |     0 |
    |       23 |  37 |    0 |     0 |
    |       81 |  36 |    0 |     0 |
    |      106 |  35 |    0 |     0 |
    |       60 |  35 |    0 |     0 |
    |       13 |  35 |    0 |     0 |
    |       37 |  35 |    0 |     0 |
    +----------+-----+------+-------+
2、避免重復查詢剛剛更新的數據
UPDATE t1 SET lastUpdated = NOW() WHERE id = 1;
SELECT lastUpdated FROM t1 WHERE id = 1;
UPDATE t1 SET lastUpdated = NOW() WHERE id = 1 AND @now := NOW();
SELECT @now;

實際項目的案例,由於User_A關聯的ORGAN_A可以為空,所以每條數據查詢獲取要提前將變量@lxDm置空,避免上一條數據影響。

SELECT (case when LEAST('1',@lxDm:='') IS NOT NULL THEN '2021' ELSE '2021' END) ND,
(SELECT @lxDm:=lxDm FROM ORGAN_A WHERE id = organ_id LIMIT 1) as lxDm,
(SELECT dict_name FROM G_DICT WHERE dict_id = @lxDm) as lxDmStr
FROM User_A 
WHERE ....
3、統計更新和插入的數量
INSERT INTO t1(c1, c2) VALUES(4, 4), (2, 1), (3, 1)
ON DUPLICATE KEY UPDATE
    c1 = VALUES(c1) + (0 * (@x := @x + 1));

當每次由於沖突導致更新時對變量@x自增一次,然后表達式乘以0讓其不影響更新的內容,另外,MySQL的協議會返回被更改的總行數,所以不需要單獨統計。

4、確定取值的順序

使用用戶自定義變量的一個最常見的問題就是沒有注意到在賦值和讀取變量的時候可能是在查詢的不同階段。例如,在SELECT子句中進行賦值然后再WHERE子句中讀取變量,則可能變量取值並不如你所想:

mysql> SET @rownum := 0;
mysql> SELECT actor_id, @rownum := @rownum + 1 AS cnt
    -> FROM actor
    -> WHERE @rownum <= 1;
+----------+------+
| actor_id | cnt  |
+----------+------+
|       58 |    1 |
|       92 |    2 |
+----------+------+

因為WHERE和SELECT是在查詢執行的不同階段被執行的。如果在查詢中再加入ORDER BY的話,結果可能會更不同;

mysql> SET @rownum := 0;
mysql> SELECT actor_id, @rownum := @rownum + 1 AS cnt
    -> FROM actor
    -> WHERE @rownum <= 1
    -> ORDER BY first_name;

這是因為ORDER BY 引入了文件排序,而WHERE條件是在文件排序操作之前取值的,所以這條查詢會返回表中的全部記錄。解決這個問題的辦法是讓變量的賦值和取值發生在執行查詢的同一階段:

mysql> SET @rownum := 0;
mysql> SELECT actor_id, @rownum AS rownum
    -> FROM actor
    -> WHERE (@rownum := @rownum + 1) <= 1;
+----------+--------+
| actor_id | rownum |
+----------+--------+
|       58 |      1 |
+----------+--------+

在不改變排序的情況下賦值。

mysql> SET @rownum := 0;
mysql> SELECT actor_id,first_name,@rownum AS rownum
    -> FROM actor
    -> WHERE @rownum <= 1
    -> ORDER BY first_name,LEAST(0,@rownum := @rownum + 1);
5、編寫偷懶的UNION

假設需要編寫一個UNION查詢,其第一個子查詢作為分支條件先執行,如果找到了匹配的行,則跳過第二個分支。例如先在一個頻繁訪問的表查找熱數據,找不到再去另外一個較少訪問的表查找冷數據。

SELECT id FROM users WHERE id = 123;
UNION ALL
SELECT id FROM users_archived WHERE id = 123;

上面的查詢可以工作,但是無論第一個表找沒找到,都會在第二個表再找一次,如果使用變量的話可以很好地規避這個問題。

SELECT GREATEST(@found := -1, id) AS id, 'users' AS which_tbl
FROM users WHERE id = 1
UNION ALL
    SELECT id, 'users_archived'
    FROM users_archived WHERE id = 1 AND @found IS NULL
UNION ALL   
    SELECT 1, 'reset' FROM DUAL WHERE (@found := NULL) IS NOT NULL;


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM