MySQL中的非确定性函数(即rand)可能会让您感到惊讶

使用sysbench处理测试用例,我遇到了这个问题:

的MySQL> 选择 *  sbtest1 其中 ID = ROUND(RAND()* 100000);
+ ------ + -------- + --------------------------------- -------------------------------------------------- -------------------------------------- + ----------- -------------------------------------------------- +
| id | k | c | 垫|
+ ------ + -------- + --------------------------------- -------------------------------------------------- -------------------------------------- + ----------- -------------------------------------------------- +
|  179 | 499871 | 09833083632 - 34593445843 - 98203182724 - 77632394229 - 31240034691 - 22855093589 - 98577647071 - 95962909368 - 34814236148 - 76937610370 | 62233363025 - 41327474153 - 95482195752 - 11204169522 - 13131828192 |
| 1606 | 502031 | 81212399253 - 12831141664 - 41940957498 - 63947990218 - 16408477860 - 15124776228 - 42269003436 - 07293216458 - 45216889819 - 75452278174 | 25423822623 - 32136209218 - 60113604068 - 17409951653 - 00581045257 |
+ ------ + -------- + --------------------------------- -------------------------------------------------- -------------------------------------- + ----------- -------------------------------------------------- +
2 0.30秒)

我真的很惊讶。首先,最重要的是,id是主键,rand()函数应该只生成一个值。怎么回来两行?其次,为什么响应时间为0.30秒?对于主键访问而言,这似乎非常高。

进一步观察:

CREATE  TABLE  `sbtest1`
  `id`  int11NOT  NULL  AUTO_INCREMENT
  `k`  INT11NOT  NULL  DEFAULT  '0' 
  `c`  char120NOT  NULL  DEFAULT  ''
  `pad`  char60NOT  NULL  DEFAULT  ''
  PRIMARY  KEY`id`),
  KEY  `k_1``k`
ENGINE = InnoDB  AUTO_INCREMENT = 1000001  DEFAULT  CHARSET = latin1
的MySQL> 解释 选择 *  sbtest1 其中 ID = ROUND(RAND()* 100000);
+ ---- + ------------- + --------- + ------------ + ------ + --------------- + ------ + --------- + ------ + -------- +  - --------- ------------- + +
| id | select_type |     | 分区 | 类型| possible_keys | 关键   | key_len | ref | 行| 过滤| 额外的|
+ ---- + ------------- + --------- + ------------ + ------ + --------------- + ------ + --------- + ------ + -------- +  - --------- ------------- + +
|  1 | 简单| sbtest1 | NULL        | 所有   | NULL           | NULL | NULL     | NULL | 986400 |    10.00 | 使用 何处 |
+ ---- + ------------- + --------- + ------------ + ------ + --------------- + ------ + --------- + ------ + -------- +  - --------- ------------- + +

所以它是一个主键,但MySQL不使用索引,它返回两行。这是一个错误吗?

确定性与非确定性功能

原来它根本不是一个bug。这是MySQL非常合乎逻辑的行为,但它不是我们所期望的。首先,为什么全表扫描?好吧,rand()是非确定性函数。这意味着我们不知道它将提前返回什么,实际上,这正是rand()的目的 - 返回一个随机值。在这种情况下,每次评估每行的函数并比较结果是合乎逻辑。即在我们的情况下:

  1. 读取第1行,获取id的值,评估RAND()的值,比较
  2. 继续使用与剩余行相同的算法。

换句话说,由于rand()的值事先未知(未评估),因此我们不能使用索引。

在这种情况下 - rand()函数 - 我们有另一个有趣的结果。对于具有auto_increment主键的较大表,匹配rand()值和auto_increment值的概率较高,因此我们可以返回多行。事实上,如果我们从头开始阅读整个表并继续将auto_inc序列与“掷骰子”进行比较,我们可以获得许多行。

这种行为完全违反直觉。然而,对我而言,这也是唯一正确的行为。

我们希望运行查询之前评估rand()函数。这实际上可以通过将rand()赋给变量来实现:

的MySQL> 设置 @id = ROUND(RAND()* 100000); 选择 @id ;  sbtest1 选择 * 其中 id = @id ;
查询正常,0行受影响(0.00秒)
+ ------ +
| @id   |
+ ------ +
| 6068 |
+ ------ +
1    集合0.00秒)
+ ------ + -------- + --------------------------------- -------------------------------------------------- -------------------------------------- + ----------- -------------------------------------------------- +
| id | k | c | 垫|
+ ------ + -------- + --------------------------------- -------------------------------------------------- -------------------------------------- + ----------- -------------------------------------------------- +
| 6068 | 502782 | 84971025350 - 12845068791 - 61736600622 - 38249796467 - 85136778555 - 74134284808 - 24438972515 - 17848828748 - 86869270666 - 01547789681 | 17507194006 - 70651503059 - 23792945260 - 94159543806 - 65683812344 |
+ ------ + -------- + --------------------------------- -------------------------------------------------- -------------------------------------- + ----------- -------------------------------------------------- +
1    集合0.00秒)
mysql> 解释 select * from sbtest1 ,其中 id = @id ;
+ ---- + ------------- + --------- + ------------ + ------- + --------------- + --------- + --------- + ------- + ----- -  + ---------- + ------- +
| id | select_type |     | 分区 | 类型| possible_keys | 关键      | key_len | ref | 行| 过滤| 额外的|
+ ---- + ------------- + --------- + ------------ + ------- + --------------- + --------- + --------- + ------- + ----- -  + ---------- + ------- +
|  1 | 简单| sbtest1 | NULL        | const | 主要        | 主要 | 4        | const |    1 |   100.00 | NULL   |
+ ---- + ------------- + --------- + ------------ + ------- + --------------- + --------- + --------- + ------- + ----- -  + ---------- + ------- +
1    集合1个警告(0.01秒)

这符合我们的期望。

提交了(至少)两个错误报告,讨论非常有趣:

  1. 标量函数中使用的rand()返回多行
  2. 使用ROUND(RAND())在PK上选择错误

其他数据库

我想看看它在其他SQL数据库中是如何工作的。在PostgreSQL中,行为与MySQL完全相同:

postgres = #select * from t2 where id = cast(random()* 10000 as int);
  id | C
------ + ---------
 4093 | asdasda
 9378 | asdasda
2排)
postgres = #select * from t2 where id = cast(random()* 10000 as int);
  id | C
------ + ---------
 5988 | asdasda
 6674 | asdasda
2排)
postgres = #explain select * from t2 where id = cast(random()* 10000 as int);
                             查询计划
-------------------------------------------------- ------------------
SEQ扫描 T2(成本= 0.00。0.159837 0.60行数= 1米宽度= 12
   过滤器:(id =((random()* '10000' :: double precision)):: integer)
2排)

而SQLite似乎有所不同,事先评估random()函数:

sqlite> select * from t2 where id = cast(abs(CAST(random()AS  REAL))/ 92233720368547 as  int);
16239 | asdsadasdsa
sqlite> select * from t2 where id = cast(abs(CAST(random()AS  REAL))/ 92233720368547 as  int);
32910 | asdsadasdsa
sqlite> select * from t2 where id = cast(abs(CAST(random()AS  REAL))/ 92233720368547 as  int);
58658 | asdsadasdsa
sqlite> 解释 select * from t2 where id = cast(abs(CAST(random()AS  REAL))/ 92233720368547 as  int);
addr操作码p1 p2 p3 p4 p5   评论
---- ------------- ---- ---- ---- -------------   -  ------ -------
0      初始化            0      12     0                     00   开始  12
1      OpenRead        0      30182   0      2               00   root = 30182 iDb = 0 ; T2
2      功能       0 0      0      3      随机(000   r [ 3 ] = func(r [ 0 ])
3      施放            3      69     0                     00   亲和力(r [ 3 ])
4      功能       0 0      3      2      abs(101   r [ 2 ] = func(r [ 3 ])
5      除以          4      2      1                     00   r [ 1 ] = r [ 2 ] / r [ 4 ]
6      施放            1      68     0                     00   亲和力(r [ 1 ])
7      SeekRowid       0      11     1                     00   intkey = r [ 1 ]; PK
8      复制            1      5      0                     00   r [ 5 ] = r [ 1 ]
9               0      1      6                     00   r [ 6 ] = t2 .c
10     ResultRow       5      2      0                     00   输出= R [ 5. 0.6 ]
11     Halt            0      0      0                     00
12     交易    0      0      2      0               01使用   StmtJournal = 0
13     Int64           0      4      0      92233720368547   00 r [ 4 ] = 92233720368547
14     转到            0      1      0                     00

结论

在“where”条件下使用MySQL非确定性函数时要小心 - rand()是最有趣的例子 - 因为它们的行为可能让你感到惊讶。许多人认为这是一个应该修复的错误。请在评论中告诉我:您认为这是一个错误(以及为什么)?我也有兴趣知道它在其他非开源数据库(Microsoft SQL Server,Oracle等)中的工作原理

PS:  最后,我有一个“聪明”的想法 - 如果我通过使用deterministic关键字“欺骗”MySQL会怎么样?

MySQL存储函数:确定性与非确定性

所以,我想知道如果它们被分配了“确定性”和“不确定性”关键字,它如何与MySQL存储函数一起工作。首先,我想“欺骗”mysql并将确定性传递给存储的函数,但在里面使用rand()。好吧,这不是你真正想要做的!

DELIMITER $
CREATE FUNCTION myrand()RETURNS INT
    确定性
开始
RETURN轮(RAND()* 10000,0);
结束$
DELIMITER;

从MySQL关于MySQL存储例程的手册我们可以读到:

对例程性质的评估基于创建者的“诚实”:MySQL不检查声明的例程DETERMINISTIC是否没有产生非确定性结果的语句。但是,错误地说明例程可能会影响结果或影响性能。声明非确定性例程DETERMINISTIC可能会导致优化程序选择错误的执行计划,从而导致意外结果。声明确定性例程NONDETERMINISTIC可能会导致不使用可用的优化,从而降低性能。

结果很有趣:

mysql> select myrand();
+ ---------- +
| myrand()|
+ ---------- +
|     4202 |
+ ---------- +
1    集合0.00秒)
mysql> select myrand();
+ ---------- +
| myrand()|
+ ---------- +
|     7548 |
+ ---------- +
1    集合0.00秒)
mysql> 解释 select * from t2 ,其中 id = myrand()\ G.
*************************** 1。  ******************** *******
           id:1
  select_type:SIMPLE
        tableNULL
   分区NULL
         type:NULL
possible_keys:NULL
          keyNULL
      key_len:NULL
          ref:NULL
         行:NULL
     已过滤:NULL
        附加:不可能WHERE发现阅读常数
1    集合1个警告(0.00秒)
mysql> show  warnings ;
+ ------- + ------ + ---------------------------------- ---------------------------------------------- +
| 等级 | 代码 | 消息|
+ ------- + ------ + ---------------------------------- ---------------------------------------------- +
| 注意| 1003 | / *选择#1 * /  选择 '2745'  AS  'id` 'asasdas'  AS  `C`   `test``t2`  其中 0 |
+ ------- + ------ + ---------------------------------- ---------------------------------------------- +
1    集合0.00秒)
mysql> select * from t2 where id = 4202 ;
+ ------ + --------- +
| id | c |
+ ------ + --------- +
| 4202 | asasdas |
+ ------ + --------- +
1    集合0.00秒)
mysql> select * from t2 ,其中 id = 2745 ;
+ ------ + --------- +
| id | c |
+ ------ + --------- +
| 2745 | asasdas |
+ ------ + --------- +
1    集合0.00秒)

所以MySQL优化器检测到了这个问题(不知何故)。

如果我使用NOT DETERMINISTIC关键字,那么MySQL的工作方式与使用rand()函数时相同:

DELIMITER $
CREATE  FUNCTION myrand2()RETURNS  INT
   不是 决定性的
开始
 RETURN轮(RAND()* 100000);
结束$
DELIMITER ;
mysql> 解释 select * from t2 ,其中 id = myrand2()\ G.
*************************** 1。  ******************** *******
           id:1
  select_type:SIMPLE
        :t2
   分区NULL
         类型:全部
possible_keys:NULL
          keyNULL
      key_len:NULL
          ref:NULL
         行:262208
     过滤:10.00
        额外:使用 在哪里
1    集合1个警告(0.00秒)

猜你喜欢

转载自blog.csdn.net/Tybyqi/article/details/85265259
今日推荐