三种形式

RAND 表达式有以下三种形式:

  1. RAND(非常量表达式),此时 RAND 的值仅和表达式的计算值相关,与其所在的位置无关
  2. RAND(常量),每次查询都相同
  3. RAND(),每次查询都不相同,这种与通常概念上的随机比较接近

我们通过一些例子来实际看看 RAND 的结果:

第一种形式 RAND(非常量表达式)

  1. create table t1(c1 int, c2 varchar(10)) engine innodb;
  2. insert into t1 values(1, '1');
  3. insert into t1 values(1, '1');
  4. select c1, c2, rand(c1), rand(c2), rand(c1) = rand(c2) from t1;
  5. +------+------+---------------------+---------------------+---------------------+
  6. | c1 | c2 | rand(c1) | rand(c2) | rand(c1) = rand(c2) |
  7. +------+------+---------------------+---------------------+---------------------+
  8. | 1 | 1 | 0.40540353712197724 | 0.40540353712197724 | 1 |
  9. | 1 | 1 | 0.40540353712197724 | 0.40540353712197724 | 1 |
  10. +------+------+---------------------+---------------------+---------------------+
  11. 2 rows in set (0.01 sec)

可以看到,不同行,不同列,甚至是不同类型的字段,只要其内容一样,RAND 计算的结果就是一样的。

第二种形式 RAND(常量)

  1. select rand(1), rand(1), rand(2), rand('2') from t1;
  2. +---------------------+---------------------+---------------------+---------------------+
  3. | rand(1) | rand(1) | rand(2) | rand('2') |
  4. +---------------------+---------------------+---------------------+---------------------+
  5. | 0.40540353712197724 | 0.40540353712197724 | 0.6555866465490187 | 0.6555866465490187 |
  6. | 0.8716141803857071 | 0.8716141803857071 | 0.12234661925802624 | 0.12234661925802624 |
  7. +---------------------+---------------------+---------------------+---------------------+
  8. select rand(1), rand(1), rand(2), rand('2') from t1;
  9. +---------------------+---------------------+---------------------+---------------------+
  10. | rand(1) | rand(1) | rand(2) | rand('2') |
  11. +---------------------+---------------------+---------------------+---------------------+
  12. | 0.40540353712197724 | 0.40540353712197724 | 0.6555866465490187 | 0.6555866465490187 |
  13. | 0.8716141803857071 | 0.8716141803857071 | 0.12234661925802624 | 0.12234661925802624 |
  14. +---------------------+---------------------+---------------------+---------------------+

RAND(常量) 在不同行会有不同的结果,可以生成一个随机数序列,但是只要常量相同,那么生成的序列就是一样的,即使一个查询中出现多次,或者多次查询,结果也保持不变。可以利用这个生成稳定的随机数序列。

第三种形式 RAND()

  1. select rand(), rand() from t1;
  2. +---------------------+---------------------+
  3. | rand() | rand() |
  4. +---------------------+---------------------+
  5. | 0.03644291408028725 | 0.07858704782890813 |
  6. | 0.28360652763732386 | 0.18227152543353992 |
  7. +---------------------+---------------------+
  8. 2 rows in set (0.00 sec)
  9. select rand(), rand() from t1;
  10. +----------------------+--------------------+
  11. | rand() | rand() |
  12. +----------------------+--------------------+
  13. | 0.060538074989372935 | 0.75587582937989 |
  14. | 0.597764952664976 | 0.7211973059188549 |
  15. +----------------------+--------------------+
  16. 2 rows in set (0.00 sec)

不带任何参数的 RAND() 表达式,每次取值看起来都是随机的。每一行,每一个 RAND 表达式都会生成不同的值。可以生成随机数序列。

MySQL 实现

  1. void randominit(struct rand_struct *rand_st, ulong seed1,
  2. ulong seed2) { /* For mysql 3.21.# */
  3. rand_st->max_value = 0x3FFFFFFFL;
  4. rand_st->max_value_dbl = (double)rand_st->max_value;
  5. rand_st->seed1 = seed1 % rand_st->max_value;
  6. rand_st->seed2 = seed2 % rand_st->max_value;
  7. }
  8. double my_rnd(struct rand_struct *rand_st) {
  9. rand_st->seed1 = (rand_st->seed1 * 3 + rand_st->seed2) % rand_st->max_value;
  10. rand_st->seed2 = (rand_st->seed1 + rand_st->seed2 + 33) % rand_st->max_value;
  11. return (((double)rand_st->seed1) / rand_st->max_value_dbl);
  12. }
  13. void Item_func_rand::seed_random(Item *arg) {
  14. /*
  15. TODO: do not do reinit 'rand' for every execute of PS/SP if
  16. args[0] is a constant.
  17. */
  18. uint32 tmp = (uint32)arg->val_int();
  19. randominit(rand, (uint32)(tmp * 0x10001L + 55555555L),
  20. (uint32)(tmp * 0x10000001L));
  21. }
  22. double Item_func_rand::val_real() {
  23. DBUG_ASSERT(fixed == 1);
  24. if (arg_count) {
  25. if (!args[0]->const_for_execution())
  26. seed_random(args[0]);
  27. else if (first_eval) {
  28. /*
  29. Constantness of args[0] may be set during JOIN::optimize(), if arg[0]
  30. is a field item of "constant" table. Thus, we have to evaluate
  31. seed_random() for constant arg there but not at the fix_fields method.
  32. */
  33. first_eval = false;
  34. seed_random(args[0]);
  35. }
  36. }
  37. return my_rnd(rand);
  38. }

所有相关的代码都在上面了,代码很少,我们还是根据三种形式的不同来分析一下代码

第一种形式 RAND(非常量表达式)

  1. double Item_func_rand::val_real() {
  2. DBUG_ASSERT(fixed == 1);
  3. if (arg_count) {
  4. if (!args[0]->const_for_execution())
  5. seed_random(args[0]);
  6. }
  7. return my_rnd(rand);
  8. }

经过分支简化,RAND(非常量表达式) 的代码实际上是这样的,我们可以看到,运算过程中唯一的变量就是 args[0]->val_int(),这个 int 取出来后与各种常数进行运算得到最后的结果。因此只要这个非常量表达式转换成 int 之后的值相同,那么 RAND 表达式的结果也相同。

第二种形式 RAND(常量)

  1. double Item_func_rand::val_real() {
  2. DBUG_ASSERT(fixed == 1);
  3. if (arg_count) {
  4. if (first_eval) {
  5. first_eval = false;
  6. seed_random(args[0]);
  7. }
  8. }
  9. return my_rnd(rand);
  10. }

经过分支简化,可以看到第一次调用时,会使用常量 args[0] 初始化 rand_struct 中的 seed1 和 seed2,之后,每次调用仅会调用 my_rnd,更新 seed1 和 seed2,并利用 seed1 和 seed2 计算出一个值。因此只要第一个初始的 args[0] 相同,其后生成的随机数序列也相同。

第三种形式 RAND()

  1. double Item_func_rand::val_real() {
  2. DBUG_ASSERT(fixed == 1);
  3. return my_rnd(rand);
  4. }

没有参数了,每次更新 rand_struct 的 seed1 和 seed2。
而此时的 rand 来自 thd->rand

  1. bool Item_func_rand::fix_fields(THD *thd, Item **ref) {
  2. // ...
  3. if (arg_count) { // Only use argument once in query
  4. // ...
  5. } else {
  6. /*
  7. Save the seed only the first time RAND() is used in the query
  8. Once events are forwarded rather than recreated,
  9. the following can be skipped if inside the slave thread
  10. */
  11. if (!thd->rand_used) {
  12. thd->rand_used = 1;
  13. thd->rand_saved_seed1 = thd->rand.seed1;
  14. thd->rand_saved_seed2 = thd->rand.seed2;
  15. }
  16. rand = &thd->rand;
  17. }
  18. return false;
  19. }

thd->rand 在每次查询开始时更新:

  1. // 实例启动时
  2. randominit(&sql_rand, (ulong)server_start_time, (ulong)server_start_time / 2);
  3. ulong sql_rnd_with_mutex() {
  4. mysql_mutex_lock(&LOCK_sql_rand);
  5. ulong tmp =
  6. (ulong)(my_rnd(&sql_rand) * 0xffffffff); /* make all bits random */
  7. mysql_mutex_unlock(&LOCK_sql_rand);
  8. return tmp;
  9. }
  10. void THD::init(void) {
  11. // ...
  12. ulong tmp;
  13. tmp = sql_rnd_with_mutex();
  14. randominit(&rand, tmp + (ulong)&rand,
  15. tmp + (ulong)::atomic_global_query_id);
  16. // ...
  17. }

实例在启动时,会根据系统的启动时间初始化一个全局的 rand seed,称为 sql_rand,之后每个查询开始时,调用 my_rnd 更新 sql_rand 并生成一个新的随机数,之后,再使用这个随机数,rand 结构体的地址,global query id 生成当前查询的 rand seed。因此,RAND() 表现出的结果更为随机,但之后的序列生成仍然使用常量进行计算,本质上仍为按规则生成的序列。

对并行的影响

PolarDB 在查询加速上做了很多工作,包含 Parallel Query 以及列存加速索引,这些新的实现都利用多核并行获得加速,但在处理 RAND 表达式上,为了实现与 MySQL 行为兼容,需要在某些情况下取消并行执行。我们来分析一下三种形式对并行的影响。

  1. RAND(非常量表达式),此时 RAND 的值仅和表达式的计算值相关,与其所在的位置无关,不存在兼容性问题,可并行执行
  2. RAND(),使用随机 seed 生成序列,每次查询都不相同,这种与通常概念上的随机比较接近,应该也是比较常用的形式,因为是随机的,每次执行结果不同,无法复现,因此也不存在兼容性问题,可并行执行
  3. RAND(常量),使用常量作为 seed 生成序列,每次查询都相同,无法并行执行,存在兼容性问题,这种情况为了兼容,往往需要将并行关闭,顺序地获得 RAND 表达式的结果。

出现在其他位置

刚才我们的大部分分析都是 RAND 表达式出现在 select list 中的情况,RAND 作为通用的表达式,可以出现在任何使用表达式的地方,例如 order by,group by,roll up 等。我们从之前对实现的分析中可以知道,RAND 返回的结果与求值的顺序有关,因此出现在这些位置时,由于求值顺序与可能与想象中有差异,需要仔细测试确认是否满足需求。