使用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秒)