Mysql自定义变量的使用
用户自定义变量是一个容易被遗忘的MySQL特性,但是如果能用的好,发挥其潜力,在某些场景可以写出非常高效的查询语句。在查询中混合使用过程化和关系化逻辑的时候,自定义变量可能会非常有用。单纯的关系查询将所有的东西都当成无序的数据集合,并且一次性操作它们。MySQL则采用了更加程序化的处理方式。MySQL的这种方式有它的弱点,但如果能够熟练地掌握,则会发现其强大之处,而用户自定义变量也可以给这种方式带来很大的帮助。
用户自定义变量是一个用来存储内容的临时容器,在连接MySQL的整个过程中都存在,可以使用下面的SET和SELECT语句来定义它们:
mysql> SET @one := 1;
mysql> SET @min_actor := (SELECT MIN(actor_id) FROM sakila.actor);
mysql> SET @last_week := CURRENT_DATE - INTERVAL 1 WEEK;
然后可以在任何可以使用表达式的地方使用这些自定义变量:
SELECT ... WHERE col <= @last_week;
在了解自定义变量的强大之前,我们先来看看它自身的一些属性和限制,看看在哪些场景下我们不能使用用户自定义变量:
- 使用自定义变量的查询,无法使用查询缓存
- 不能再使用常量或者标识符的地方使用自定义变量,例如表名、列名和LIMIT子句中。
- 用户自定义变量的生命周期是在一个连接中有效,所以不能用它们来做连接间的通信。
- 如果使用连接池或者持久化连接,自定义变量可能让看起来毫无关系的代码发生交互。
- 自定义变量的类型是一个动态类型。
- MySQL优化器在某些场景下可能会将这些变量优化掉,这可能导致代码不按预想的方式运行。
- 赋值的顺序和赋值的时间点并不总是固定的,这依赖于优化器的决定。
- 赋值符号 :=的优先级非常低,所以需要注意,赋值表达式应该使用明确的括号。
- 使用未定义变量不会产生任何语法错误,如果没有意识到这一点,非常容易犯错。
优化排名语句
使用自定义变量的一个特性是你可以在给一个变量赋值的同时使用这个变量,即“左值”特性。例如:
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 |
+----------+--------+
这个例子的实际意义并不大,它只是实现了一个和该表主键一样的列。不过,我们可以把这当作一个排名。现在我们来看一个更复杂的用法。我们先编写一个查询获取演过最多电影的前10位演员,然后根据他们的出演电影次数做一个排名,如果出演的电影数量一样,则排名相同。我们先编写一个查询,返回每个演员参演电影的数量。
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 |
+----------+-----+
现在我们再把排名加上去,这里看到有四个演员都参演了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 |
+----------+-----+------+-------+
我们发现跟我们设想的不太一样。这里,通过EXPLAIN我们看到将会使用临时表和文件排序,所以可能是由于变量赋值的时间和我们预料的不同。
使用SQL语句生成排名值通常需要做两次计算,例如,需要额外计算一次出演过相同数量电影的演员有哪些。使用变量则可一次完成---这对性能是一个很大的提升。
针对这个案例,另一个简单的方案是在FROM子句中使用子查询生成的一个中间的临时表:
mysql> SELECT actor_id,
-> @curr_cnt := cnt AS cnt,
-> @rank := IF(@prev_cnt <> @curr_cnt, @rank + 1, @rank) AS rank,
-> @prev_cnt := @curr_cnt AS dummy
-> FROM (
-> SELECT actor_id, COUNT(*) AS cnt
-> FROM film_actor
-> GROUP BY actor_id
-> ORDER BY cnt DESC
-> LIMIT 10
-> ) as der;
+----------+-----+------+-------+
| actor_id | cnt | rank | dummy |
+----------+-----+------+-------+
| 107 | 42 | 1 | 42 |
| 102 | 41 | 2 | 41 |
| 198 | 40 | 3 | 40 |
| 181 | 39 | 4 | 39 |
| 23 | 37 | 5 | 37 |
| 81 | 36 | 6 | 36 |
| 37 | 35 | 7 | 35 |
| 106 | 35 | 7 | 35 |
| 60 | 35 | 7 | 35 |
| 13 | 35 | 7 | 35 |
+----------+-----+------+-------+
避免重复查询刚刚更新的数据
如果在更新行的同学又希望获得该行的信息,避免重复查询,可以用变量巧妙的实现。例如,我们的一个客户希望能够更高效地更新一条记录的时间戳,同时希望查询当前记录中存放的时间戳是什么。简单地,可以用下面的代码来实现:
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;
上面看起来仍然需要两个查询,需要两次网络来回,但是这里第二个查询无需访问数据表,所以会快很多。
统计更新和插入的数量
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的协议会返回被更改的总行数,所以不需要单独统计。
确定取值的顺序
使用用户自定义变量的一个最常见的问题就是没有注意到在赋值和读取变量的时候可能是在查询的不同阶段。例如,在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 |
+----------+--------+
编写偷懒的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;
用户自定义变量的其他用处
通过一些实践,可以了解所有用户自定义变量能够做的有趣的事情,例如下面这些用法:
- 查询运行时计算总数和平均值
- 模拟GROUP语句中的函数FIRST()和LAST()
- S对大量数据做一些数据计算
- 计算一个大表的MD5散列值
- 编写一个样本处理函数
- 模拟读/写游标
- 在SHOW语句的WHERE子句中加入变量值
Mysql自定义变量的使用的更多相关文章
- mysql自定义变量
mysql可以实现自定义变量,使用方式非常简单,代码如下: SELECT @i:=@i + 1 // 查询变量,值+1 FROM () i // 声明变量,初始值为0 如果有多条数据,那么这个变量就会 ...
- MySQL数据库(3)----设置和使用自定义变量
MySQL支持定义自己的变量.这些变量可以被设置为查询结果,这使我们可以方便地把一些值存储起来供今后查询使用. ; +-----------------+ | @HisName:= name | +- ...
- mysql查询语句中自定义变量(转)
转:http://blog.sina.com.cn/s/blog_1512521570102wrfl.htmlselect cost,@a:=@a+1 from testone,(select @a: ...
- mysql 自定义函数
原文:http://www.cnblogs.com/zhangminghui/p/4113160.html 引言 MySQL本身提供了内置函数,这些函数的存在给我们日常的开发和数据操作带来了很大的便利 ...
- Mysql 的变量
变量 MySQL是一门编程语言.所以存在变量.流程控制.函数.存储过程.触发器 MySQL分系统变量,与自定义变量 MySQL的某些功能是通过系统变量来实现的.例如:autocommit 查看系统变量 ...
- MySQL的变量分类总结
在MySQL中,my.cnf是参数文件(Option Files),类似于ORACLE数据库中的spfile.pfile参数文件,照理说,参数文件my.cnf中的都是系统参数(这种称呼比较符合思维习惯 ...
- mysql之变量
本文内容: 系统变量 用户变量 局部变量 首发日期:2018-04-18 系统变量: 系统变量就是系统已经提前定义好了的变量 系统变量一般都有其特殊意义.比如某些变量代表字符集.某些变量代表某些mys ...
- Mysql的变量一览
Server System Variables(系统变量) MySQL系统变量(system variables)是指MySQL实例的各种系统变量,实际上是一些系统参数,用于初始化或设定数据库对系统资 ...
- mysql自定义函数并在存储过程中调用,生成一千万条数据
mysql 自定义函数,生成 n 个字符长度的随机字符串 -- sql function delimiter $$ create function rand_str(n int) returns VA ...
随机推荐
- (转) Virtual function
原文地址:http://en.wikipedia.org/wiki/Virtual_function In object-oriented programming, a virtual functio ...
- MYSQL存储过程中-流程控制语句
存储过程中常用的流程控制 复习下存储过程内部的语法 定义存储过程体的局部变量: 定义方法:DECLARE a INT DEFAULT 100或者DECLARE a INT ; SET a=100; ...
- 【转载】solr教程,值得刚接触搜索开发人员一看
转载:http://blog.csdn.net/awj3584/article/details/16963525 Solr调研总结 开发类型 全文检索相关开发 Solr版本 4.2 文件内容 本文介绍 ...
- 1.Perl基础系列之WHAT、WHY、HOW
What? Perl,一种功能丰富的计算机程序语言,运行在超过100种计算机平台上,适用广泛,从大型机到便携设备,从快速原型创建到大规模可扩展开发. Why? Perl追求简洁快速地解决问题,可很方便 ...
- nginx+keepalived+tomcat之具体配置档
前沿知识点: nginx负责负载均衡(反向代理) msm(memcached session manager)负责缓存会话信息,从而实现会话保持 所需包: nginx和memcached采用最新稳定版 ...
- LFS,编译自己的Linux系统 - 包和补丁
建立工作目录 我们先建立一个工作目录,用于存放下载的源代码和对源代码进行编译. sudo mkdir –v /mnt/lfs/sources sudo chmod –v a+wt /mnt/lfs/s ...
- 深入 char * ,char ** ,char a[ ] ,char *a[] 内核
本文来自CSDN博客:daiyutage的文章 来源网页地址:http://blog.csdn.net/daiyutage/article/details/8604720 本人觉得这是一编很有价值的文 ...
- CodeForces 124B Permutations
http://codeforces.com/problemset/problem/124/B Description You are given nk-digit integers. You have ...
- TCLP 第一章 1.5字符输入输出
#include <stdio.h> /* 将输入复制到输出:版本1 */ int main() { int c; /* 注意是int而不是char,除了存储char类型字符,还要存储EO ...
- Java程序员需要学习的知识点
Java是全世界最受欢迎的3大编程语言之一,它可以开发出许多实用的WEB应用程序和桌面应用程序,更重要的一点,Java是跨平台的语言——编写一次,可以再任何地方运行.另外,Java也很容易入门,如果你 ...