技術分享 | 使用 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。

文末例行完結撒花。