MySQL中的非確定性函式(即rand)可能會讓您感到驚訝
使用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秒)
這符合我們的期望。
提交了(至少)兩個錯誤報告,討論非常有趣:
其他資料庫
我想看看它在其他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秒)