一、前言

在前段工作中,曾几次收到超级话题积分漏记的用户反馈。通过源码的阅读分析后,发现问题出在高并发分布式场景下的计数器上。计数器的值会影响用户当前行为所获得积分的大小。比如,当用户在某超级话题下连续第n(n即计数器的值)次进行转发帖子时,将会获得与n相关的分数。然而,在第一次改进后问题依然存在。所以,这次在之前的基础上,通过使用MySQL变量的途径来解决该问题。

二、到底MySQL的变量分哪几类?

MySQL变量一共分为两大类:用户自定义变量和系统变量。如下:

  • 用户自定义变量

    • 局部变量
    • 会话变量
  • 系统变量
    • 会话变量
    • 全局变量

本文涉及的内容为用户自定义会话变量,若对其他分类无感,请点击这里

PS:用户定义的会话变量和系统定义的会话变量有什么区别?

局部变量

局部变量一般用于SQL的语句块中,比如存储过程中的begin和end语句块。其作用域仅限于该语句块内。生命周期也仅限于该存储过程的调用期间。

 drop procedure if exists add;
create procedure add
(
in a int,
in b int
)
begin
declare c int default 0;
set c = a + b;
select c as c;
end;

上述存储过程中定义的变量c就是局部变量。

会话变量

会话变量即为服务器为每个客户端连接维护的变量。在客户端连接时,使用相应全局变量的当前值对客户端的回话变量进行初始化。设置会话变量不需要特殊权限,但客户端只能更改自己的会话变量。其作用域与生命周期均限于当前客户端连接。

会话变量的赋值:

 set session var_name = value;
set @@session.var_name = value;
set var_name = value;

会话变量的查询:

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

全局变量

全局变量影响服务器整体操作。当服务器启动时,它将所有全局变量初始化为默认值。这些默认值可以在选项文件中或在命令行中指定的选项进行更改。要想更改全局变量,必须具有SUPER权限。全局变量作用于server的整个生命周期,但是不能跨重启。即重启后所有设置的全局变量均失效。要想让全局变量重启后继续生效,需要更改相应的配置文件。

全局变量的设置:

 set global var_name = value; //注意:此处的global不能省略。根据手册,set命令设置变量时若不指定GLOBAL、SESSION或者LOCAL,默认使用SESSION
set @@global.var_name = value; //同上

全局变量的查询:

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

三、MySQL用户自定义变量详解

你可以利用SQL语句将值存储在用户自定义变量中,然后再利用另一条SQL语句来查询用户自定义变量。这样以来,可以再不同的SQL间传递值。

用户自定义变量的声明方法形如:@var_name,其中变量名称由字母、数字、“.”、“_”和“$”组成。当然,在以字符串或者标识符引用时也可以包含其他字符(例如:@’my-var’,@”my-var”,或者@`my-var`)。

用户自定义变量是会话级别的变量。其变量的作用域仅限于声明其的客户端链接。当这个客户端断开时,其所有的会话变量将会被释放。

用户自定义变量是不区分大小写的。

使用SET语句来声明用户自定义变量:

 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语句中同时赋值并使用同一个用户自定义变量。举个变量自增的例子,下面的是没问题的:

 SET @a = @a + 1;

对于其他语句,比如SELECT,也许会得到期望的效果,但这真心不靠谱。比如下面的语句,也许你自然地会认为MySQL会先执行@a的值,然后再进行赋值操作:

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

然而,用户自定义变量表达式的计算顺序还没有定义呢。

除此之外,还有另一个问题。变量的默认返回类型由语句开始时的类型决定的,正如下面的例子:

 mysql> SET @a='test';
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表达式定义的变量的情况下,该语句将不会得到如期的效果。

 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分。这块的规则,利用二分法写死在程序里面,也做过单元测试,不会有问题。那么,问题就锁定在这个积分行为的次数。

首先来看看积分次数的获取:

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

然后,利用上述find()方法来取得该用户在某超级话题下的某积分行为的累计次数。这是有问题的,在于读于从库,但并不保证从库的值是最新的,所以导致当前获取的积分行为次数并不一定是正确的(小于等于实际的值)。

随后,程序会根据当前的次数计算积分值,并分别更新积分值和该行为的积分行为次数值。

所以,这次利用MySQL的用户自定义会话变量的方式,来解决上述问题。

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

改进后,如上述函数,程序将先进行调用incCounter()函数,将当前的积分行为次数自增,并将值存入当前变量中。随后,立即将其读取并返回给PHP进行积分处理。这样一来,就保证了积分行为次数的正确性。

五、关于MySQL用户自定义变量的结束语

在这次的“填坑”过程中,使用了MySQL变量解决了MySQL主从服务同步延迟的问题。这篇文章也算是对于MySQL用户自定义变量深入学习的记载。

除此之外,仍有个问题,用户自定义的会话变量是存在进程内存中的。但是,是存在客户端进程中还是服务端进程中的呢?


参考文章:

文章来源:胡旭博客 => 深入MySQL用户自定义变量:使用详解及其使用场景案例

转载请注明出处,违者必究!

深入MySQL用户自定义变量:使用详解及其使用场景案例的更多相关文章

  1. mysql的变量信息详解

    mysql的变量详解 执行show variables命令可以查看MySQL服务器的变量 变量名 默认值 说明 对应的配置文件参数 auto_increment_increment 1 自增长类型的初 ...

  2. 【必知必会】手把手教你配置MySQL环境变量——图文详解

    一.先决条件 假设我们已经成功安装MySQL数据库.如果还有小伙伴不知道如何安装MySQL数据库,可以在本文下留言,留言数超20,则出一期"手把手教你安装MySQL数据库--图文详解&quo ...

  3. MySQL系统变量 sql_mode 详解

    转载自:http://tech.it168.com/a2012/0822/1388/000001388401_all.shtml MySQL数据类型:SQL_MODE设置不容忽视 SQL_MODE可能 ...

  4. 深入MySQL用户自定义变量

    一.到底MySQL的变量分哪几类? MySQL变量一共分为两大类:用户自定义变量和系统变量.如下: 用户自定义变量 局部变量 会话变量 系统变量 会话变量 全局变量 本文涉及的内容为用户自定义会话变量 ...

  5. MySQL 语句执行过程详解

    MySQL 原理篇 MySQL 索引机制 MySQL 体系结构及存储引擎 MySQL 语句执行过程详解 MySQL 执行计划详解 MySQL InnoDB 缓冲池 MySQL InnoDB 事务 My ...

  6. PHP变量作用域详解(二)

    学过C的人用PHP的时候一般会相当顺手,而且感到PHP太方便太轻松.但在变量作用域这方面却与C有不同的地方,搞不好会相当郁闷,就找不到错误所在.昨晚就与到这么一个问题,是全局变量在函数中的问题.今天搜 ...

  7. MySQL 执行计划explain详解

    MySQL 执行计划explain详解 2015-08-10 13:56:27 分类: MySQL explain命令是查看查询优化器如何决定执行查询的主要方法.这个功能有局限性,并不总会说出真相,但 ...

  8. mysql学习3:mysql之my.cnf详解

    mysql之my.cnf详解 本文转自:https://www.cnblogs.com/panwenbin-logs/p/8360703.html 以下是 my.cnf 配置文件参数解释: #*** ...

  9. mysql触发器trigger 实例详解

    mysql触发器trigger 实例详解 (转自 https://www.cnblogs.com/phpper/p/7587031.html)   MySQL好像从5.0.2版本就开始支持触发器的功能 ...

随机推荐

  1. git中Bash基本操作命令

    ).cd : 改变目录. ).cd . . 回退到上一个目录,直接cd进入默认目录 ).pwd : 显示当前所在的目录路径. ).ls(ll): 都是列出当前目录中的所有文件,只不过ll(两个ll)列 ...

  2. cuda数组的拷贝

    原文链接 简单描述一下自己要做的事情:(1)CPU三维的vector--->(2)CPU三维数组--->(3)转换到GPU中的三维数组--->(4)转换到CPU中的三维数组,而其中问 ...

  3. apache单ip配置多端口多站点

    1.修改 /etc/httpd/conf/httpd.conf 添加一个Listen,如: Listen 80 Listen 8001 Listen 8002 2.添加一个VirtualHost #v ...

  4. jquery操作元素的位置

    .offset() 在匹配的元素中,获取第一个元素的当前坐标,或设置每一个元素的坐标,坐标相对于文档. .offset() 这个不接受任何参数. var offset = p.offset(); // ...

  5. 一、小程序连接MySql数据库

    前言:我用的是wafer2 node.解决方案 下面连接有环境配置及搭建流程(https://github.com/tencentyun/wafer2-quickstart-nodejs) ,连接是官 ...

  6. Python 遗传算法实现字符串

    Python 遗传算法实现字符串 流程 1. 初始化 2. 适应度函数 3. 选择 4. 交叉 5. 变异 适应度函数计算方法 计算个体间的差:分别计算每个元素与目标元素的差取平方和 种群:计算总体均 ...

  7. kali安装ssh服务

    一. kali安装ssh服务 1.修改源 root@DGG:~# vi /etc/apt/sources.list deb http://http.kali.org/kali kali-rolling ...

  8. Java中的二进制运算出错问题

    问题: 最近在做Java web项目中需要计算金额总和,在这里出现了一个问题是我以前没有关注到的: System.out.println(2.0-1.1); 执行时候的console中打印输出的是 0 ...

  9. PHP审计(一)

    一.php中常见的危险函数和审计要点 危险函数(功能过于强大)    参数是否外部可控,有没有正确的过滤. PHP获取外界传入参数是通过下面几个全局函数的形式,所以审计参数传入经常要和下面几个变量打交 ...

  10. Flask初学者:蓝图Blueprint

    蓝图这个名字好像就是根据单词Blueprint字面意思来,跟平常我们理解的蓝图完全挂不上钩,这里蓝图就是指Blueprint. 使用蓝图的好处是可以将不同功能作用的视图函数/类视图放到不同的模块中,可 ...