技術分享 | 使用 RAND() 函數過程中發現的詭異 Bug 分析
- 2020 年 3 月 13 日
- 筆記
作者:Agate Li
愛可生研發團隊成員,負責數據庫管理平台相關項目,.Net 技術愛好者,長期潛水於技術圈。
本文來源:原創投稿
*愛可生開源社區出品,原創內容未經授權不得隨意使用,轉載請聯繫小編並註明來源。
背景
MySQL 中的 RAND() 函數是一個隨機數發生器,可以返回一個
>=0
並<1.0
的隨機浮點數。
最近在實際使用過程里遇見了一個主流版本中非常詭異的 Bug,故整理出來,以免大家踩坑。
演示
文中使用的 MySQL 版本是 5.7.25,話不多說,直接上演示:
1. 創建測試表
CREATE TABLE test (`id` INT(3) NOT NULL PRIMARY KEY AUTO_INCREMENT) ENGINE=`InnoDB`;
2. 往表裡插入 10 條記錄
INSERT INTO test VALUES(),(),(),(),(),(),(),(),(),();
3. 關鍵來了,執行幾次下面這條 SQL
SELECT sub.rnd FROM (SELECT FLOOR(RAND()*10) rnd FROM test) sub WHERE sub.rnd<3;

明明指定了篩選內層 sub.rnd
小於 3 的條件,輸出出來的結果卻完全不對。
4. 接下來排查問題的觸發條件
由於直接使用 RAND() 函數輸出出來的結果是隨機的,首先要做的就是指定一枚固定的種子,一是以免干擾後續排查,二是可以讓大家自行精確復現。
首先將種子設定為 100,並多次查詢內層的隨機數

可以看到,符合預期。繼續:

仍然符合預期,看起來不像是 RAND() 函數本身的問題。
5. 為第三步中的 SQL 指定種子:
SELECT sub.rnd FROM (SELECT FLOOR(RAND(100)*10) rnd FROM test) sub WHERE sub.rnd<3;

熟悉的味道出現了,刺激的感覺回來了…… EXPLAIN 一波

6. 去掉第三步中的 test 表再試
SELECT sub.rnd FROM (SELECT FLOOR(RAND(100)*10) rnd) sub WHERE sub.rnd<3;

哈?並沒有問題?再 EXPLAIN 一波

到這裡就有了個懷疑,是不是跟派生表物化相關?
7. 再改改第三步中的 SQL
SELECT sub.rnd FROM (SELECT FLOOR(RAND(100)*10) rnd FROM test LIMIT 10000) sub WHERE sub.rnd<3;

再再 EXPLAIN 一波

嗯,不出所料呢。這回結果對了。
8. 再驗證一次,把第三步中的 SQL 拉平
SELECT FLOOR(RAND(100)*10) rnd FROM test HAVING rnd<3;

再再再 EXPLAIN 一波

沒錯,還是熟悉的味道,還是刺激的感覺。
9. 這時候可以推測,大概率是在派生表未物化的情況下 RAND() 在外層重算了……
拿着推測,去 google 一波,立刻找到了一個相關 Bug:
https://bugs.mysql.com/bug.php?id=86624
嗯,2017 年年中就有人報過的 Bug,再看看 Bug 狀態,噢,「嘻嘻,我們驗證了但不打算修」……
好在官方還是給出了解決方法:
- 對於5.7,跟我們的做法一樣,加上 LIMIT <一個很大的數>;
- 對於8.0,加上 no_merge。
文末例行完結撒花。