使用 RAND() 函數過程中發現的詭異 Bug 分析

愛可生研發團隊成員,負責數據庫管理平臺相關項目,.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>


技術分享 | 使用 RAND() 函數過程中發現的詭異 Bug 分析


明明指定了篩選內層 sub.rnd 小於 3 的條件,輸出出來的結果卻完全不對。

4. 接下來排查問題的觸發條件

由於直接使用 RAND() 函數輸出出來的結果是隨機的,首先要做的就是指定一枚固定的種子,一是以免干擾後續排查,二是可以讓大家自行精確復現。

首先將種子設定為 100,並多次查詢內層的隨機數


技術分享 | 使用 RAND() 函數過程中發現的詭異 Bug 分析


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


技術分享 | 使用 RAND() 函數過程中發現的詭異 Bug 分析


仍然符合預期,看起來不像是 RAND() 函數本身的問題。

5. 為第三步中的 SQL 指定種子:

<code>SELECT sub.rnd FROM (SELECT FLOOR(RAND(100)*10) rnd FROM test) sub WHERE sub.rnd<3;/<code>


技術分享 | 使用 RAND() 函數過程中發現的詭異 Bug 分析


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


技術分享 | 使用 RAND() 函數過程中發現的詭異 Bug 分析


6. 去掉第三步中的 test 表再試

<code>SELECT sub.rnd FROM (SELECT FLOOR(RAND(100)*10) rnd) sub WHERE sub.rnd<3;/<code>


技術分享 | 使用 RAND() 函數過程中發現的詭異 Bug 分析


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


技術分享 | 使用 RAND() 函數過程中發現的詭異 Bug 分析


到這裡就有了個懷疑,是不是跟派生表物化相關?

7. 再改改第三步中的 SQL

<code>SELECT sub.rnd FROM (SELECT FLOOR(RAND(100)*10) rnd FROM test LIMIT 10000) sub WHERE sub.rnd<3;/<code>


技術分享 | 使用 RAND() 函數過程中發現的詭異 Bug 分析


再再 EXPLAIN 一波


技術分享 | 使用 RAND() 函數過程中發現的詭異 Bug 分析


嗯,不出所料呢。這回結果對了。

8. 再驗證一次,把第三步中的 SQL 拉平

<code>SELECT FLOOR(RAND(100)*10) rnd FROM test HAVING rnd<3;/<code>


技術分享 | 使用 RAND() 函數過程中發現的詭異 Bug 分析


再再再 EXPLAIN 一波


技術分享 | 使用 RAND() 函數過程中發現的詭異 Bug 分析


沒錯,還是熟悉的味道,還是刺激的感覺。

9. 這時候可以推測,大概率是在派生表未物化的情況下 RAND() 在外層重算了……

拿著推測,去 google 一波,立刻找到了一個相關 Bug:

https://bugs.mysql.com/bug.php?id=86624

嗯,2017 年年中就有人報過的 Bug,再看看 Bug 狀態,噢,“嘻嘻,我們驗證了但不打算修”……

好在官方還是給出瞭解決方法:

  • 對於5.7,跟我們的做法一樣,加上 LIMIT ;
  • 對於8.0,加上 no_merge。

文末例行完結撒花。


分享到:


相關文章: