三种形式
RAND 表达式有以下三种形式:
- RAND(非常量表达式),此时 RAND 的值仅和表达式的计算值相关,与其所在的位置无关
- RAND(常量),每次查询都相同
- RAND(),每次查询都不相同,这种与通常概念上的随机比较接近
我们通过一些例子来实际看看 RAND 的结果:
第一种形式 RAND(非常量表达式)
create table t1(c1 int, c2 varchar(10)) engine innodb;
insert into t1 values(1, '1');
insert into t1 values(1, '1');
select c1, c2, rand(c1), rand(c2), rand(c1) = rand(c2) from t1;
+------+------+---------------------+---------------------+---------------------+
| c1 | c2 | rand(c1) | rand(c2) | rand(c1) = rand(c2) |
+------+------+---------------------+---------------------+---------------------+
| 1 | 1 | 0.40540353712197724 | 0.40540353712197724 | 1 |
| 1 | 1 | 0.40540353712197724 | 0.40540353712197724 | 1 |
+------+------+---------------------+---------------------+---------------------+
2 rows in set (0.01 sec)
可以看到,不同行,不同列,甚至是不同类型的字段,只要其内容一样,RAND 计算的结果就是一样的。
第二种形式 RAND(常量)
select rand(1), rand(1), rand(2), rand('2') from t1;
+---------------------+---------------------+---------------------+---------------------+
| rand(1) | rand(1) | rand(2) | rand('2') |
+---------------------+---------------------+---------------------+---------------------+
| 0.40540353712197724 | 0.40540353712197724 | 0.6555866465490187 | 0.6555866465490187 |
| 0.8716141803857071 | 0.8716141803857071 | 0.12234661925802624 | 0.12234661925802624 |
+---------------------+---------------------+---------------------+---------------------+
select rand(1), rand(1), rand(2), rand('2') from t1;
+---------------------+---------------------+---------------------+---------------------+
| rand(1) | rand(1) | rand(2) | rand('2') |
+---------------------+---------------------+---------------------+---------------------+
| 0.40540353712197724 | 0.40540353712197724 | 0.6555866465490187 | 0.6555866465490187 |
| 0.8716141803857071 | 0.8716141803857071 | 0.12234661925802624 | 0.12234661925802624 |
+---------------------+---------------------+---------------------+---------------------+
RAND(常量) 在不同行会有不同的结果,可以生成一个随机数序列,但是只要常量相同,那么生成的序列就是一样的,即使一个查询中出现多次,或者多次查询,结果也保持不变。可以利用这个生成稳定的随机数序列。
第三种形式 RAND()
select rand(), rand() from t1;
+---------------------+---------------------+
| rand() | rand() |
+---------------------+---------------------+
| 0.03644291408028725 | 0.07858704782890813 |
| 0.28360652763732386 | 0.18227152543353992 |
+---------------------+---------------------+
2 rows in set (0.00 sec)
select rand(), rand() from t1;
+----------------------+--------------------+
| rand() | rand() |
+----------------------+--------------------+
| 0.060538074989372935 | 0.75587582937989 |
| 0.597764952664976 | 0.7211973059188549 |
+----------------------+--------------------+
2 rows in set (0.00 sec)
不带任何参数的 RAND() 表达式,每次取值看起来都是随机的。每一行,每一个 RAND 表达式都会生成不同的值。可以生成随机数序列。
MySQL 实现
void randominit(struct rand_struct *rand_st, ulong seed1,
ulong seed2) { /* For mysql 3.21.# */
rand_st->max_value = 0x3FFFFFFFL;
rand_st->max_value_dbl = (double)rand_st->max_value;
rand_st->seed1 = seed1 % rand_st->max_value;
rand_st->seed2 = seed2 % rand_st->max_value;
}
double my_rnd(struct rand_struct *rand_st) {
rand_st->seed1 = (rand_st->seed1 * 3 + rand_st->seed2) % rand_st->max_value;
rand_st->seed2 = (rand_st->seed1 + rand_st->seed2 + 33) % rand_st->max_value;
return (((double)rand_st->seed1) / rand_st->max_value_dbl);
}
void Item_func_rand::seed_random(Item *arg) {
/*
TODO: do not do reinit 'rand' for every execute of PS/SP if
args[0] is a constant.
*/
uint32 tmp = (uint32)arg->val_int();
randominit(rand, (uint32)(tmp * 0x10001L + 55555555L),
(uint32)(tmp * 0x10000001L));
}
double Item_func_rand::val_real() {
DBUG_ASSERT(fixed == 1);
if (arg_count) {
if (!args[0]->const_for_execution())
seed_random(args[0]);
else if (first_eval) {
/*
Constantness of args[0] may be set during JOIN::optimize(), if arg[0]
is a field item of "constant" table. Thus, we have to evaluate
seed_random() for constant arg there but not at the fix_fields method.
*/
first_eval = false;
seed_random(args[0]);
}
}
return my_rnd(rand);
}
所有相关的代码都在上面了,代码很少,我们还是根据三种形式的不同来分析一下代码
第一种形式 RAND(非常量表达式)
double Item_func_rand::val_real() {
DBUG_ASSERT(fixed == 1);
if (arg_count) {
if (!args[0]->const_for_execution())
seed_random(args[0]);
}
return my_rnd(rand);
}
经过分支简化,RAND(非常量表达式) 的代码实际上是这样的,我们可以看到,运算过程中唯一的变量就是 args[0]->val_int(),这个 int 取出来后与各种常数进行运算得到最后的结果。因此只要这个非常量表达式转换成 int 之后的值相同,那么 RAND 表达式的结果也相同。
第二种形式 RAND(常量)
double Item_func_rand::val_real() {
DBUG_ASSERT(fixed == 1);
if (arg_count) {
if (first_eval) {
first_eval = false;
seed_random(args[0]);
}
}
return my_rnd(rand);
}
经过分支简化,可以看到第一次调用时,会使用常量 args[0] 初始化 rand_struct 中的 seed1 和 seed2,之后,每次调用仅会调用 my_rnd,更新 seed1 和 seed2,并利用 seed1 和 seed2 计算出一个值。因此只要第一个初始的 args[0] 相同,其后生成的随机数序列也相同。
第三种形式 RAND()
double Item_func_rand::val_real() {
DBUG_ASSERT(fixed == 1);
return my_rnd(rand);
}
没有参数了,每次更新 rand_struct 的 seed1 和 seed2。
而此时的 rand 来自 thd->rand
bool Item_func_rand::fix_fields(THD *thd, Item **ref) {
// ...
if (arg_count) { // Only use argument once in query
// ...
} else {
/*
Save the seed only the first time RAND() is used in the query
Once events are forwarded rather than recreated,
the following can be skipped if inside the slave thread
*/
if (!thd->rand_used) {
thd->rand_used = 1;
thd->rand_saved_seed1 = thd->rand.seed1;
thd->rand_saved_seed2 = thd->rand.seed2;
}
rand = &thd->rand;
}
return false;
}
thd->rand 在每次查询开始时更新:
// 实例启动时
randominit(&sql_rand, (ulong)server_start_time, (ulong)server_start_time / 2);
ulong sql_rnd_with_mutex() {
mysql_mutex_lock(&LOCK_sql_rand);
ulong tmp =
(ulong)(my_rnd(&sql_rand) * 0xffffffff); /* make all bits random */
mysql_mutex_unlock(&LOCK_sql_rand);
return tmp;
}
void THD::init(void) {
// ...
ulong tmp;
tmp = sql_rnd_with_mutex();
randominit(&rand, tmp + (ulong)&rand,
tmp + (ulong)::atomic_global_query_id);
// ...
}
实例在启动时,会根据系统的启动时间初始化一个全局的 rand seed,称为 sql_rand,之后每个查询开始时,调用 my_rnd 更新 sql_rand 并生成一个新的随机数,之后,再使用这个随机数,rand 结构体的地址,global query id 生成当前查询的 rand seed。因此,RAND() 表现出的结果更为随机,但之后的序列生成仍然使用常量进行计算,本质上仍为按规则生成的序列。
对并行的影响
PolarDB 在查询加速上做了很多工作,包含 Parallel Query 以及列存加速索引,这些新的实现都利用多核并行获得加速,但在处理 RAND 表达式上,为了实现与 MySQL 行为兼容,需要在某些情况下取消并行执行。我们来分析一下三种形式对并行的影响。
- RAND(非常量表达式),此时 RAND 的值仅和表达式的计算值相关,与其所在的位置无关,不存在兼容性问题,可并行执行
- RAND(),使用随机 seed 生成序列,每次查询都不相同,这种与通常概念上的随机比较接近,应该也是比较常用的形式,因为是随机的,每次执行结果不同,无法复现,因此也不存在兼容性问题,可并行执行
- RAND(常量),使用常量作为 seed 生成序列,每次查询都相同,无法并行执行,存在兼容性问题,这种情况为了兼容,往往需要将并行关闭,顺序地获得 RAND 表达式的结果。
出现在其他位置
刚才我们的大部分分析都是 RAND 表达式出现在 select list 中的情况,RAND 作为通用的表达式,可以出现在任何使用表达式的地方,例如 order by,group by,roll up 等。我们从之前对实现的分析中可以知道,RAND 返回的结果与求值的顺序有关,因此出现在这些位置时,由于求值顺序与可能与想象中有差异,需要仔细测试确认是否满足需求。