MySQL巧用自定義函數進行查詢優化


用戶自定義變量是一個很容易被遺忘的MySQL特性,但是用的好,發揮其潛力,在很多場景都可以寫出非常高效的查詢語句。


一. 實現一個按照actorid排序的列

 1 mysql> set @rownum :=0;
 2 Query OK, 0 rows affected (0.00 sec)
 3 
 4 mysql> select actor_id ,@rownum :=@rownum + 1 as rownum
 5 -> from sakila.actor limit 3;
 6 +----------+--------+
 7 | actor_id | rownum |
 8 +----------+--------+
 9 | 58 | 1 |
10 | 92 | 2 |
11 | 182 | 3 |
12 +----------+--------+
13 3 rows in set (0.00 sec)

 


二. 擴展一下,現在需要獲取演過最多電影的前十位,針對數量作一個排名,如果數量一樣,則排名相同

 1 mysql> set @curr_cnt :=0 ,@pre_cnt :=0 ,@rank :=0;
 2 Query OK, 0 rows affected (0.00 sec)
 3 
 4 mysql> select actor_id, 
 5 -> @prev_cnt :=@curr_cnt as dummy,
 6 -> @curr_cnt := cnt as cnt,
 7 -> @rank := IF(@prev_cnt <> @curr_cnt,@rank+1,@rank) as rank
 8 -> FROM(
 9 -> SELECT actor_id ,count(*) as cnt
10 -> FROM sakila.film_actor
11 -> GROUP BY actor_id
12 -> ORDER BY cnt DESC
13 -> LIMIT 10
14 -> )as der;
15 +----------+-------+-----+------+
16 | actor_id | dummy | cnt | rank |
17 +----------+-------+-----+------+
18 | 107 | 0 | 42 | 1 |
19 | 102 | 42 | 41 | 2 |
20 | 198 | 41 | 40 | 3 |
21 | 181 | 40 | 39 | 4 |
22 | 23 | 39 | 37 | 5 |
23 | 81 | 37 | 36 | 6 |
24 | 158 | 36 | 35 | 7 |
25 | 144 | 35 | 35 | 7 |
26 | 37 | 35 | 35 | 7 |
27 | 106 | 35 | 35 | 7 |
28 +----------+-------+-----+------+
29 10 rows in set (0.00 sec)

 

三. 避免重復查詢剛更新的數據

如果想要高效的更新一條記錄的時間戳 ,又想返回更新的數據

 1 mysql> create table t2 (id int,lastUpdated datetime);
 2 Query OK, 0 rows affected (0.03 sec)
 3 
 4 mysql> insert into t2 (id ,lastupdated)values(1,sysdate());
 5 Query OK, 1 row affected (0.02 sec)
 6 
 7 mysql> select * from t2;
 8 +------+---------------------+
 9 | id | lastUpdated |
10 +------+---------------------+
11 | 1 | 2017-07-24 16:03:34 |
12 +------+---------------------+
13 1 row in set (0.01 sec)
14 
15 mysql> update t2 set lastUpdated=NOW() WHERE id =1 and @now :=Now();
16 Query OK, 1 row affected (0.02 sec)
17 Rows matched: 1 Changed: 1 Warnings: 0
18 
19 
20 mysql> select @now, sysdate();
21 +---------------------+---------------------+
22 | @now | sysdate() |
23 +---------------------+---------------------+
24 | 2017-07-24 16:05:42 | 2017-07-24 16:06:06 |
25 +---------------------+---------------------+
26 1 row in set (0.00 sec)

 

四. 統計更新和插入的數量
使用 INSERT ON DUPLICATE KEY UPDATE 時,查詢插入成功的條數,沖突的條數

 

 1 mysql> set @x :=0;
 2 Query OK, 0 rows affected (0.00 sec)
 3 
 4 mysql> INSERT INTO t3(c1,c2) values(1,2),(1,3),(2,2)
 5 -> ON DUPLICATE KEY UPDATE
 6 -> c2=VALUES(c2)+(0*(@x:=@x+1));
 7 Query OK, 4 rows affected (0.01 sec)
 8 Records: 3 Duplicates: 1 Warnings: 0
 9 
10 mysql> select @x;
11 +------+
12 | @x |
13 +------+
14 | 1 |
15 +------+
16 1 row in set (0.00 sec)
17 
18 mysql> select * from t3;
19 +----+------+
20 | c1 | c2 |
21 +----+------+
22 | 1 | 3 |
23 | 2 | 2 |
24 +----+------+
25 2 rows in set (0.00 sec)

 

五. 確定取值的順序
想要獲取sakila.actor中的一個結果

錯誤的查詢一:
下面的查詢看起來好像只返回一個結果,實際呢:

 1 mysql> set @row_num :=0;
 2 Query OK, 0 rows affected (0.00 sec)
 3 
 4 mysql> SELECT actor_id,@row_num :=@row_num+1 AS cnt
 5 -> FROM sakila.actor 
 6 -> WHERE @row_num <=1 
 7 -> ;
 8 +----------+------+
 9 | actor_id | cnt |
10 +----------+------+
11 | 58 | 1 |
12 | 92 | 2 |
13 +----------+------+
14 2 rows in set (0.00 sec)
15 
16 看一下執行計划:
17 +----+-------------+-------+-------+---------------+---------------------+---------+------+------+--------------------------+
18 | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
19 +----+-------------+-------+-------+---------------+---------------------+---------+------+------+--------------------------+
20 | 1 | SIMPLE | actor | index | NULL | idx_actor_last_name | 137 | NULL | 200 | Using where; Using index |
21 +----+-------------+-------+-------+---------------+---------------------+---------+------+------+--------------------------+
22 1 row in set (0.00 sec)

這是因為where 和 select 是在 查詢的不同階段執行的造成的。

錯誤的查詢二:
如果加上按照 first_name 排序呢 :

 1 mysql> set @row_num :=0;
 2 Query OK, 0 rows affected (0.00 sec)
 3 
 4 mysql> SELECT actor_id,@row_num :=@row_num+1 AS cnt 
 5 -> FROM sakila.actor 
 6 -> WHERE @row_num <=1 
 7 -> order by first_name;
 8 +----------+------+
 9 | actor_id | cnt |
10 +----------+------+
11 | 71 | 1 |
12 | 132 | 2 |
13 | 165 | 3 |
14 | 173 | 4 |
15 | 125 | 5 |
16 | 146 | 6 |
17 | 29 | 7 |
18 | 65 | 8 |
19 | 144 | 9 |
20 | 76 | 10 |
21 | 49 | 11 |
22 | 34 | 12 |
23 | 190 | 13 |
24 | 196 | 14 |
25 | 83 | 15 |
26 .. ...
27 返回了所有行,再看下查詢計划:
28 
29 +----+-------------+-------+------+---------------+------+---------+------+------+-----------------------------+
30 | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
31 +----+-------------+-------+------+---------------+------+---------+------+------+-----------------------------+
32 | 1 | SIMPLE | actor | ALL | NULL | NULL | NULL | NULL | 200 | Using where; Using filesort |
33 +----+-------------+-------+------+---------------+------+---------+------+------+-----------------------------+
34 1 row in set (0.00 sec)

 

可以看出原因是 Using where 是在排序操作之前取值的,所以輸出了全部的行。

解決這個問題的方法是:讓變量的賦值和取值發生在執行查詢的統一階段:

正確的查詢:

 1 mysql> set @row_num :=0;
 2 Query OK, 0 rows affected (0.00 sec)
 3 
 4 mysql> SELECT actor_id,@row_num AS cnt
 5 -> FROM sakila.actor 
 6 -> WHERE (@row_num :=@row_num+1) <=1 
 7 -> ;
 8 +----------+------+
 9 | actor_id | cnt |
10 +----------+------+
11 | 58 | 1 |
12 +----------+------+
13 1 row in set (0.00 sec)
14 
15 看一下執行計划
16 
17 +----+-------------+-------+-------+---------------+---------------------+---------+------+------+--------------------------+
18 | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
19 +----+-------------+-------+-------+---------------+---------------------+---------+------+------+--------------------------+
20 | 1 | SIMPLE | actor | index | NULL | idx_actor_last_name | 137 | NULL | 200 | Using where; Using index |
21 +----+-------------+-------+-------+---------------+---------------------+---------+------+------+--------------------------+
22 1 row in set (0.00 sec)

 

想一想 如果加上ORDER BY 該怎么寫?

 1 mysql> set @row_num :=0;
 2 Query OK, 0 rows affected (0.00 sec)
 3 
 4 mysql> SELECT actor_id,first_name ,@row_num AS row_num
 5 -> FROM sakila.actor 
 6 -> WHERE @row_num<=1
 7 -> ORDER BY first_name , least(0, @row_num :=@row_num+1) 
 8 -> ;
 9 
10 +----------+------------+---------+
11 | actor_id | first_name | row_num |
12 +----------+------------+---------+
13 | 2 | NICK | 2 |
14 | 1 | PENELOPE | 1 |
15 +----------+------------+---------+
16 2 rows in set (0.00 sec)
17 
18 
19 mysql> select @row_num;
20 +----------+
21 | @row_num |
22 +----------+
23 | 2 |
24 +----------+
25 1 row in set (0.00 sec)
26 
27 看一下執行計划:
28 
29 +----+-------------+-------+------+---------------+------+---------+------+------+----------------------------------------------+
30 | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
31 +----+-------------+-------+------+---------------+------+---------+------+------+----------------------------------------------+
32 | 1 | SIMPLE | actor | ALL | NULL | NULL | NULL | NULL | 200 | Using where; Using temporary; Using filesort |
33 +----+-------------+-------+------+---------------+------+---------+------+------+----------------------------------------------+
34 1 row in set (0.00 sec)
35 
36 SELECT actor_id,first_name ,@row_num:=@row_num+1 AS row_num 
37 FROM sakila.actor 
38 WHERE @row_num<=1 
39 ORDER BY first_name , least(0, @row_num :=@row_num+1)

 

六. UNION的巧妙改寫

假設有兩張用戶表,一張主用戶表,存放着活躍用戶;一些歸檔用戶表,存放着長期不活躍的用戶。現在需要查找id 為123的客戶。
先看下這個語句

1 select id from users where id= 123
2 union all
3 select id from users_archived where id =123

上面的語句是可以執行的,但是效率不好,因為兩張表都必須查詢一次


引入自定義變量的改寫:

1 SELECT GREATEST(@found:=-1,id) AS id ,'users' AS which_tbl 
2 FROM users WHERE id =123
3 UNION ALL
4 SEELCT id,'users_archived' FROM users_archived WHERE id = 123 AND @found IS NULL
5 UNION ALL
6 SELECT 1,'reset' FROM DUAL WHERE (@found:=NULL) IS NOT NULL

 



上面的改寫非常巧妙:
第一段,如果在users查詢到記錄,則為@found賦值,也不會查詢第二段;如果沒有查詢到記錄,@found 為 null ,執行第二段。
第三段沒有輸出 ,只是簡單的重置@found 為null。另外 GREATEST(@found:=-1,id) 也不會影響輸出!



免責聲明!

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



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