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

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

时间:2023-06-15 11:07:22浏览次数:44  
标签:rand 确定性 MySQL ------ NULL --------- id select

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


的MySQL> 选择 * 从 sbtest1 其中 ID = ROUND(RAND()* 10000,0);

+ ------ + -------- + --------------------------------- -------------------------------------------------- -------------------------------------- + ----------- -------------------------------------------------- +
| 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`  int(11)NOT  NULL  AUTO_INCREMENT,
  `k`  INT(11)NOT  NULL  DEFAULT  '0' ,
  `c`  char(120)NOT  NULL  DEFAULT  '',
  `pad`  char(60)NOT  NULL  DEFAULT  '',
  PRIMARY  KEY(`id`),
  KEY  `k_1`(`k`)
)ENGINE = InnoDB  AUTO_INCREMENT = 1000001  DEFAULT  CHARSET = latin1

的MySQL> 解释 选择 * 从 sbtest1 其中 ID = ROUND(RAND()* 10000,0);

+ ---- + ------------- + --------- + ------------ + ------ + --------------- + ------ + --------- + ------ + -------- +  - --------- ------------- + +
| id | select_type | 表    | 分区 | 类型| possible_keys | 关键   | key_len | ref | 行| 过滤| 额外的|
+ ---- + ------------- + --------- + ------------ + ------ + --------------- + ------ + --------- + ------ + -------- +  - --------- ------------- + +
|  1 | 简单| sbtest1 | NULL        | 所有   | NULL           | NULL | NULL     | NULL | 986400 |    10.00 | 使用 何处 |
+ ---- + ------------- + --------- + ------------ + ------ + --------------- + ------ + --------- + ------ + -------- +  - --------- ------------- + +

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


确定性与非确定性功能

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


读取第1行,获取id的值,评估RAND()的值,比较

继续使用与剩余行相同的算法。

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


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


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


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


的MySQL> 设置 @id = ROUND(RAND()* 10000,0); 选择 @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秒)

这符合我们的期望。


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


标量函数中使用的rand()返回多行

使用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      随机(0)       00   r [ 3 ] = func(r [ 0 ])
3      施放            3      69     0                     00   亲和力(r [ 3 ])
4      功能       0 0      3      2      abs(1)          01   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
        table:NULL
   分区:NULL
         type:NULL
possible_keys:NULL
          key:NULL
      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()* 10000,0);
结束$
DELIMITER ;
mysql> 解释 select * from t2 ,其中 id = myrand2()\ G.
*************************** 1。 排 ******************** *******
           id:1
  select_type:SIMPLE
        表:t2
   分区:NULL
         类型:全部
possible_keys:NULL
          key:NULL
      key_len:NULL
          ref:NULL
         行:262208
     过滤:10.00

       额外:使用 在哪里

1  行 中 集合,1个警告(0.00秒)


标签:rand,确定性,MySQL,------,NULL,---------,id,select
From: https://blog.51cto.com/u_16145034/6485303

相关文章

  • MySQL数据库运维实录--通过MySQL Shell Dump/Load 实现数据库对象的逻辑备份与恢复
    [mysql@node01~]$mysqlshMySQLJS>\connectroot@node01:3306MySQLnode01:3306sslJS>util.dumpInstance('/home/mysql/backup')[mysql@node01~]$scp-rbackup/*node02:/home/mysql/backup/util.dumpInstance的关键特性1,多线程备份。并发线程数由thr......
  • mysql8.0创建新用户
     查看当前用户usemysql;selectcurrent_user();或者:selectUser,authentication_string,Hostfromuser;查看权限showgrantsfor'user'@'ip';或者showgrants;创建一个给新用户的数据库createdatabasetestDatabase;创建新用户同时配置给该用户testData......
  • MySQL8.0设置外网访问
    1.登进MySQL之后mysql-uroot-p***2,输入以下语句,进入mysql库:usemysql3,更新域属性,'%'表示允许外部访问:updateusersethost='%'whereuser='root';4,执行以上语句之后再执行:FLUSHPRIVILEGES;5,再执行授权语句:GRANTALLPRIVILEGESON*.*TO'root'@'%......
  • DataX在Windows上实现Mysql到Mysql同步数据以及配置多个job/多个表同步定时执行bat
    场景DataX-阿里开源离线同步工具在Windows上实现Sqlserver到Mysql全量同步和增量同步:https://blog.csdn.net/BADAO_LIUMANG_QIZHI/article/details/130330353DataX-在Windows上实现postgresql同步数据到mysql:https://blog.csdn.net/BADAO_LIUMANG_QIZHI/article/details/130......
  • 解决MySQL8.0报错:Unknown system variable 'validate_password_policy'
    解决MySQL8.0报错:Unknownsystemvariable'validate_password_policy'解决MySQL8.0报错:Unknownsystemvariable'validate_password_policy'一、问题描述1.通过yum安装好mysql8.0,通过生成的临时密码登录后操作mysql时会报如下错误:mysql>showdatabases;ERROR1820......
  • CentOS yum升级MySQL 5.6到5.7.42
    注意:升级前一定要做好备份升级前请将mysql5.6小版本升级到最高升级时可将my.cnf配置文件备份,保留最基本的配置,避免因配置问题造成异常,升级完成后在逐步还原安装mysql5.7yum源如果之前已经安装了5.6的yum源,需要先卸载后在安装rpm-Uvhhttps://dev.mysql.com/get/mysql......
  • SpringBoot+druid+dynamic+clickhouse+mysql实现读写分离
    背景:clickhouse+mysql实现读写分离1.配置mysql2.安装clickhouse3.使用可视化工具连接clickhouse4.创建clickhouse的数据库并连接mysql5.SpringBoot+druid+dynamic配置多数据源实现读写分离一.背景由于系统数据量过大,查询条件自定义过多,mysql在查询时响应太慢,所以使用cli......
  • Postgresql中的表结构和数据同步/数据传输到Mysql
    场景Postgresql中的某个表,需要连同表结构以及表数据同步一次到Mysql数据库中。一种方式是在Postgresql中将表sql导出,然后修改sql文件语法,再导入到mysql中。但是这种方式过于复杂,可以借助于Navicat等工具。如果是使用Navicat进行结构同步和数据同步时,需要两边是同类型的数据库......
  • Mysql查看连接数(连接总数、活跃数、最大并发数)
    查看最大连接数mysql>showvariableslike'%max_connection%';+-----------------------+-------+|Variable_name|Value|+-----------------------+-------+|extra_max_connections|||max_connections|2512|+----------------......
  • 在 Istio 服务网格内连接外部 MySQL 数据库
    为了方便理解,以Istio官方提供的Bookinfo应用示例为例,利用ratings服务外部MySQL数据库。Bookinfo应用的架构图如下:其中,包含四个单独的微服务:productpage:调用details和reviews两个服务,用来生成页面。details:包含了书籍的信息。reviews:包含了书籍相关的评论。它还......