愛可生研發團隊成員,負責數據庫管理平臺相關項目,.Net 技術愛好者,長期潛水於技術圈。
背景
MySQL 中的 RAND() 函數是一個隨機數發生器,可以返回一個 >=0 並 <1.0 的隨機浮點數。
最近在實際使用過程裡遇見了一個主流版本中非常詭異的 Bug,故整理出來,以免大家踩坑。
演示
文中使用的 MySQL 版本是 5.7.25,話不多說,直接上演示:
1. 創建測試表
<code>CREATE TABLE test (`id` INT(3) NOT NULL PRIMARY KEY AUTO_INCREMENT) ENGINE=`InnoDB`;/<code>
2. 往表裡插入 10 條記錄
<code>INSERT INTO test VALUES(),(),(),(),(),(),(),(),(),();/<code>
3. 關鍵來了,執行幾次下面這條 SQL
<code>SELECT sub.rnd FROM (SELECT FLOOR(RAND()*10) rnd FROM test) sub WHERE sub.rnd<3;/<code>
明明指定了篩選內層 sub.rnd 小於 3 的條件,輸出出來的結果卻完全不對。
4. 接下來排查問題的觸發條件
由於直接使用 RAND() 函數輸出出來的結果是隨機的,首先要做的就是指定一枚固定的種子,一是以免干擾後續排查,二是可以讓大家自行精確復現。
首先將種子設定為 100,並多次查詢內層的隨機數
可以看到,符合預期。繼續:
仍然符合預期,看起來不像是 RAND() 函數本身的問題。
5. 為第三步中的 SQL 指定種子:
<code>SELECT sub.rnd FROM (SELECT FLOOR(RAND(100)*10) rnd FROM test) sub WHERE sub.rnd<3;/<code>
熟悉的味道出現了,刺激的感覺回來了…… EXPLAIN 一波
6. 去掉第三步中的 test 表再試
<code>SELECT sub.rnd FROM (SELECT FLOOR(RAND(100)*10) rnd) sub WHERE sub.rnd<3;/<code>
哈?並沒有問題?再 EXPLAIN 一波
到這裡就有了個懷疑,是不是跟派生表物化相關?
7. 再改改第三步中的 SQL
<code>SELECT sub.rnd FROM (SELECT FLOOR(RAND(100)*10) rnd FROM test LIMIT 10000) sub WHERE sub.rnd<3;/<code>
再再 EXPLAIN 一波
嗯,不出所料呢。這回結果對了。
8. 再驗證一次,把第三步中的 SQL 拉平
<code>SELECT FLOOR(RAND(100)*10) rnd FROM test HAVING rnd<3;/<code>
再再再 EXPLAIN 一波
沒錯,還是熟悉的味道,還是刺激的感覺。
9. 這時候可以推測,大概率是在派生表未物化的情況下 RAND() 在外層重算了……
拿著推測,去 google 一波,立刻找到了一個相關 Bug:
https://bugs.mysql.com/bug.php?id=86624
嗯,2017 年年中就有人報過的 Bug,再看看 Bug 狀態,噢,“嘻嘻,我們驗證了但不打算修”……
好在官方還是給出瞭解決方法:
- 對於5.7,跟我們的做法一樣,加上 LIMIT ;
- 對於8.0,加上 no_merge。
文末例行完結撒花。
閱讀更多 愛可生 的文章