程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 數據庫知識 >> MYSQL數據庫 >> MySQL綜合教程 >> 深入MySQL用戶自定義變量:使用詳解及其使用場景案例,mysql使用詳解

深入MySQL用戶自定義變量:使用詳解及其使用場景案例,mysql使用詳解

編輯:MySQL綜合教程

深入MySQL用戶自定義變量:使用詳解及其使用場景案例,mysql使用詳解


一、前言

在前段工作中,曾幾次收到超級話題積分漏記的用戶反饋。通過源碼的閱讀分析後,發現問題出在高並發分布式場景下的計數器上。計數器的值會影響用戶當前行為所獲得積分的大小。比如,當用戶在某超級話題下連續第n(n即計數器的值)次進行轉發帖子時,將會獲得與n相關的分數。然而,在第一次改進後問題依然存在。所以,這次在之前的基礎上,通過使用MySQL變量的途徑來解決該問題。

二、到底MySQL的變量分哪幾類?

MySQL變量一共分為兩大類:用戶自定義變量和系統變量。如下:

  • 用戶自定義變量
    • 局部變量
    • 會話變量
  • 系統變量
    • 會話變量
    • 全局變量

本文涉及的內容為用戶自定義會話變量,若對其他分類無感,請點擊這裡。

PS:用戶定義的會話變量和系統定義的會話變量有什麼區別?

局部變量

局部變量一般用於SQL的語句塊中,比如存儲過程中的begin和end語句塊。其作用域僅限於該語句塊內。生命周期也僅限於該存儲過程的調用期間。

 1 drop procedure if exists add;
 2 create procedure add
 3 (
 4     in a int,
 5     in b int
 6 )
 7 begin
 8     declare c int default 0;
 9     set c = a + b;
10     select c as c;
11 end;

上述存儲過程中定義的變量c就是局部變量。

會話變量

會話變量即為服務器為每個客戶端連接維護的變量。在客戶端連接時,使用相應全局變量的當前值對客戶端的回話變量進行初始化。設置會話變量不需要特殊權限,但客戶端只能更改自己的會話變量。其作用域與生命周期均限於當前客戶端連接。

會話變量的賦值:

1 set session var_name = value;
2 set @@session.var_name = value;
3 set var_name = value;

會話變量的查詢:

1 select @@var_name;
2 select @@session.var_name;
3 show session variables like "%var%";

全局變量

全局變量影響服務器整體操作。當服務器啟動時,它將所有全局變量初始化為默認值。這些默認值可以在選項文件中或在命令行中指定的選項進行更改。要想更改全局變量,必須具有SUPER權限。全局變量作用於server的整個生命周期,但是不能跨重啟。即重啟後所有設置的全局變量均失效。要想讓全局變量重啟後繼續生效,需要更改相應的配置文件。

全局變量的設置:

1 set global var_name = value; //注意:此處的global不能省略。根據手冊,set命令設置變量時若不指定GLOBAL、SESSION或者LOCAL,默認使用SESSION
2 set @@global.var_name = value; //同上

全局變量的查詢:

1 select @@global.var_name;
2 show global variables like "%var%";

三、MySQL用戶自定義變量詳解

你可以利用SQL語句將值存儲在用戶自定義變量中,然後再利用另一條SQL語句來查詢用戶自定義變量。這樣以來,可以再不同的SQL間傳遞值。

用戶自定義變量的聲明方法形如:@var_name,其中變量名稱由字母、數字、“.”、“_”和“$”組成。當然,在以字符串或者標識符引用時也可以包含其他字符(例如:@’my-var’,@”my-var”,或者@`my-var`)。

用戶自定義變量是會話級別的變量。其變量的作用域僅限於聲明其的客戶端鏈接。當這個客戶端斷開時,其所有的會話變量將會被釋放。

用戶自定義變量是不區分大小寫的。

使用SET語句來聲明用戶自定義變量:

1 SET @var_name = expr[, @var_name = expr] ...

在使用SET設置變量時,可以使用“=”或者“:=”操作符進行賦值。

當然,除了SET語句還有其他賦值的方式。比如下面這個例子,但是賦值操作符只能使用“:=”。因為“=”操作符將會被認為是比較操作符。

mysql> SET @t1=1, @t2=2, @t3:=4;
mysql> SELECT @t1, @t2, @t3, @t4 := @t1+@t2+@t3;
+------+------+------+--------------------+
| @t1  | @t2  | @t3  | @t4 := @t1+@t2+@t3 |
+------+------+------+--------------------+
|    1 |    2 |    4 |                  7 |
+------+------+------+--------------------+

用戶變量的類型僅限於:整形、浮點型、二進制與非二進制串和NULL。在賦值浮點數時,系統不會保留精度。其他類型的值將會被轉成相應的上述類型。比如:一個包含時間或者空間數據類型(temporal or spatial data type)的值將會轉換成一個二進制串。

如果用戶自定義變量的值以結果集形式返回,系統會將其轉換成字符串形式。

如果查詢一個沒有初始化的變量,將會以字符串類型返回NULL。

不要在同一個非SET語句中同時賦值並使用同一個用戶自定義變量

用戶自定義變量可以用於很多上下文中。但是目前並不包括那些顯式使用常量的表達式中,比如SELECT中的LIMIT子句,或者LOAD DATA中的IGNORE N LINES的字句中。

通常來說,除了在SET語句中,不要再同一個SQL語句中同時賦值並使用同一個用戶自定義變量。舉個變量自增的例子,下面的是沒問題的:

1 SET @a = @a + 1;

對於其他語句,比如SELECT,也許會得到期望的效果,但這真心不靠譜。比如下面的語句,也許你自然地會認為MySQL會先執行@a的值,然後再進行賦值操作:

1 SELECT @a, @a:=@a+1, ...;

然而,用戶自定義變量表達式的計算順序還沒有定義呢。

除此之外,還有另一個問題。變量的默認返回類型由語句開始時的類型決定的,正如下面的例子:

1 mysql> SET @a='test';
2 mysql> SELECT @a,(@a:=20) FROM tbl_name;

上述的SELECT語句中,MySQL會報告給客戶端第一列的字段類型為字符串,同時將所有對@a變量的使用均轉換為字符串處理,盡管在SELECT語句中將@a變量設置為數字類型。在SELECT語句執行後,@a變量才會在下一個語句中識別為數字類型。

為了避免上述問題的發生,要麼不在同一個語句中同時賦值並使用變量,要麼在使用之前,將變量設置為0,0.0,或者”,以確定它的數據類型。

變量的值是在SQL發送到客戶端後才計算的

在SELECT語句中,在每一個select表達式被發送給客戶端後,才會進行計算。這就意味著,在形如HAVING,GROUP BY和ORDER BY只句中有使用在當前select表達式定義的變量的情況下,該語句將不會得到如期的效果。

1 mysql> SELECT (@aa:=id) AS a, (@aa+3) AS b FROM tbl_name HAVING b=5;

上述在HAVING只句中使用了在當前的select列表中定義的別名b,其使用了變量@aa。這條語句並不會得到如期的效果:@aa變量為上一次SQL語句執行的結果集中的ID值,並非當前的。

四、MySQL用戶自定義變量的實際應用舉例

項目

超級話題積分系統

術語

積分行為:如轉發、評論超級話題下的帖子、簽到某超級話題或者帖子被其他人回復等行為。

積分行為次數:產生積分行為的累計次數。

業務場景

用戶在某超級話題下,第N次產生累計積分的行為,如轉發微博,會增加該用戶在該超級話題下的積分總數。具體的積分規則見長文章。

問題

曾有用戶反饋說超級話題積分有漏記的情況:為什麼我評論了卻沒有加分;為什麼轉發了超級話題帖子沒有加分等等。隨後,我們立即通過查詢後台的積分記錄發現,會看到轉發行為在第5次時,積分的增加卻為0。這顯然是不正常的。

首先,排除了根據積分行為的次數來計算積分值的問題。比如第5次轉發微博應增加6分。這塊的規則,利用二分法寫死在程序裡面,也做過單元測試,不會有問題。那麼,問題就鎖定在這個積分行為的次數。

首先來看看積分次數的獲取:

1 public static function find($uid, $aid, $status) {  
2     $sql = 'SELECT * FROM '.self::table($aid).' WHERE uid = ? AND aid = ? AND status = ?';
3     return Comm_Db::d(Comm_Db::DB_BASIC)->fetchRow($sql, array($uid, $aid, $status));
4 }

然後,利用上述find()方法來取得該用戶在某超級話題下的某積分行為的累計次數。這是有問題的,在於讀於從庫,但並不保證從庫的值是最新的,所以導致當前獲取的積分行為次數並不一定是正確的(小於等於實際的值)。

隨後,程序會根據當前的次數計算積分值,並分別更新積分值和該行為的積分行為次數值。

所以,這次利用MySQL的用戶自定義會話變量的方式,來解決上述問題。

1 public static function incCounter($uid, $aid, $status) {
2     $db = Comm_Db::d(Comm_Db::DB_BASIC);
3     $sql = "UPDATE ". self::table($aid) ." SET `ctn_counter`=@ctn_counter:=`ctn_counter`+1 WHERE `uid` = ? AND `aid` = ? AND `status` = ?";
4     $db->execute($sql, array($uid, $aid, $status));
5     $sql = "SELECT @ctn_counter";
6     $rs = $db->fetchOne($sql, null, true);
7     return $rs;
8 }

改進後,如上述函數,程序將先進行調用incCounter()函數,將當前的積分行為次數自增,並將值存入當前變量中。隨後,立即將其讀取並返回給PHP進行積分處理。這樣一來,就保證了積分行為次數的正確性。

五、關於MySQL用戶自定義變量的結束語

在這次的“填坑”過程中,使用了MySQL變量解決了MySQL主從服務同步延遲的問題。這篇文章也算是對於MySQL用戶自定義變量深入學習的記載。

除此之外,仍有個問題,用戶自定義的會話變量是存在進程內存中的。但是,是存在客戶端進程中還是服務端進程中的呢?


參考文章:

  • https://my.oschina.net/guanyue/blog/211706
  • http://dev.mysql.com/doc/refman/5.6/en/user-variables.html
  • http://www.uuboku.com/392.html

文章來源:胡旭博客 => 深入MySQL用戶自定義變量:使用詳解及其使用場景案例

轉載請注明出處,違者必究!

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved