MySQL一個關於derived table的bug描述與規避

来源:https://www.cnblogs.com/greatsql/archive/2023/06/21/17495431.html
-Advertisement-
Play Games

# 關聯文章: - [MySQL對derived table的優化處理與使用限制](http://mp.weixin.qq.com/s?__biz=MzkzMTIzMDgwMg==&mid=2247501528&idx=1&sn=03ea1cd0eb8f42bacda0583879ad494c&ch ...


關聯文章:


一、Bug描述

同事遇到一個有意思的語句,說一條SQL在MySQL8.0.25版本運行出的結果明顯與給定的where條件不符,而在8.0.26版本上是正常的,語句上加了一個無關的用戶變數後在8.0.25版本上結果才是正確的,想不通這是怎麼回事,這麼有意思的事情自然引起了我的興趣,藉此機會深入瞭解了一下MySQL關於derived table的優化。為了方便演示效果,讓小伙伴們關註到現象的本質,我將語句進行了簡化處理。

下麵是模擬的表結構與數據。

create table t1(id int,c1 varchar(100));

insert into t1 values(1,'gg張三');
insert into t1 values(2,'bb李四');
insert into t1 values(3,'cc王五');
insert into t1 values(4,'dd劉麻子');
insert into t1 values(1,'gg張三');
insert into t1 values(2,'bb李四');

SQL語句:

SELECT temp.type
  FROM (SELECT  SUBSTRING(t.type, 3) type
          FROM (SELECT distinct t1.c1 type
                  FROM test.t1
                 ORDER BY t1.c1) t) temp
 WHERE temp.type='張三'
 ORDER BY temp.type DESC;

在MySQL8.0.25版本的運行結果如下:

mysql> SELECT temp.type
    ->   FROM (SELECT  SUBSTRING(t.type, 3) type
    ->           FROM (SELECT distinct t1.c1 type
    ->                   FROM test.t1
    ->                  ORDER BY t1.c1) t) temp
    ->  WHERE temp.type='張三'
    ->  ORDER BY temp.type DESC;
+--------+
| type   |
+--------+
| 李四   |
+--------+
1 rows in set (0.01 sec)

在MySQL8.0.26版本的運行結果如下:

mysql> SELECT temp.type
    ->   FROM (SELECT  SUBSTRING(t.type, 3) type
    ->           FROM (SELECT distinct t1.c1 type
    ->                   FROM test.t1
    ->                  ORDER BY t1.c1) t) temp
    ->  WHERE temp.type='張三'
    ->  ORDER BY temp.type DESC;
+--------+
| type   |
+--------+
| 張三   |
+--------+
1 rows in set (0.00 sec)

很明顯,這個語句在8.0.25版本運行出的結果與我們給定where條件不符,我們要查詢關於“張三”的記錄,結果返回的結果是”李四“的,很明顯的一個bug,但是到8.0.26版本這個問題得到了修正。

懷著對各版本對此語句執行情況的好奇,我先是往前追溯,查看了8.0.24,8.0.23,8.0.22,8.0.21,5.7.39版本上做了測試,發現在8.0.24,8.0.23,8.0.22版本結果與8.0.25相同,都是錯誤結果,而在8.0.21版本上運行結果是正確的,5.7版本上結果也是正確的的。往後追溯,8.0.26,8.0.32版本也都是正確的,因此判斷此問題在8.0.22~8.0.25版本上存在此問題。

這個語句最大的特點就是運用了派生表(derived table),MySQL在8.0.22版本上引入了一個關於派生表的優化器開關derived_condition_pushdown, 預設設置為on。我們看一下關於這個特性在官方文檔中的描述:

MySQL 8.0.22 and later supports derived condition pushdown for eligible subqueries. For a query such as SELECT * FROM (SELECT i, j FROM t1) AS dt WHERE i > constant, it is possible in many cases to push the outer WHERE condition down to the derived table, in this case resulting in SELECT * FROM (SELECT i, j FROM t1 WHERE i > constant) AS dt.

在8.0.26版本中修複的bug中發現一個與此問題相關的bug。描述如下:

When a condition is pushed down to a materialized derived table, a clone of the derived table expression replaces the column (from the outer query block) in the condition. When the cloned item included a FULLTEXT function, it was added to the outer query block instead of the derived table query block, which led to problems. To fix this, we now use the derived query block to clone such items. (Bug #32820437)

看到這裡我們可以確定,就是8.0.22版本時這個新特性的引入,導致了此問題的產生,慶幸的是這個問題在8.0.26版本中已得到解決

文章開頭說的問題語句跟這個bug的描述是吻合的,派生表temp外部的過濾條件 temp.type=’張三' 其實是substring(t.type,3)='張三',應該就是對應bug描述中的”the cloned item included a FULLTEXT function“不管substring函數是不是fulltext函數,總之這個問題伴隨著這個bug的修複也修複了。這種語句結構下,很多函數都有這個問題,比如trim,replace等。MySQL內部如何處理得到的錯誤結果我們就不去深究了,但是如何規避這個bug我們需要瞭解一下。

二、bug規避

升級到8.0.26及以上的版本問題自然就解決了,如果不想升級也是有很多方式來規避此問題的。這個bug的產生主要是因為新特性derived_condition_pushdown的引入,關閉此特性,在這幾個版本中就不會出現這個問題。

mysql> set optimizer_switch="derived_condition_pushdown=off";
Query OK, 0 rows affected (0.00 sec)

mysql> SELECT temp.type
    ->   FROM (SELECT  SUBSTRING(t.type, 3) type
    ->           FROM (SELECT distinct t1.c1 type
    ->                   FROM test.t1
    ->                  ORDER BY t1.c1) t) temp
    ->  WHERE temp.type='張三'
    ->  ORDER BY temp.type DESC;
+--------+
| type   |
+--------+
| 張三   |
+--------+
1 rows in set (0.00 sec)

優化器開關里還有一個派生表相關的開關,就是derived_merge,是否進行派生表合併。關閉這個derived_merge,結果也是正確的。

mysql> set optimizer_switch="derived_merge=off,derived_condition_pushdown=on";
Query OK, 0 rows affected (0.00 sec)

mysql> SELECT temp.type
    ->   FROM (SELECT  SUBSTRING(t.type, 3) type
    ->           FROM (SELECT distinct t1.c1 type
    ->                   FROM test.t1
    ->                  ORDER BY t1.c1) t) temp
    ->  WHERE temp.type='張三'
    ->  ORDER BY temp.type DESC;
+--------+
| type   |
+--------+
| 張三   |
+--------+
1 rows in set (0.00 sec)

也就是說當派生表條件下推撞上派生表合併時,資料庫做的處理不對,導致了問題的產生。

因此只要控制不發生合併,或者不發生條件下推,就能規避此bug。除了關閉優化器開關,在語句級別我們還有很多方式來規避,下麵列舉幾個。

1.使用NO_MERGE的hint來阻止derived table合併。

SELECT  /*+ NO_MERGE(temp) */ temp.type
  FROM (SELECT substring(t.type,3) type
          FROM (SELECT distinct t1.c1 type
                  FROM test.t1
                 ORDER BY t1.c1) t
) temp
 WHERE temp.type='張三'
 ORDER BY temp.type DESC;

2.使用NO_DERIVED_CONDITION_PUSHDOWN的hint阻止條件下推。

SELECT  temp.type
  FROM (SELECT /*+ NO_DERIVED_CONDITION_PUSHDOWN(t) */ substring(t.type,3) type
          FROM (SELECT  distinct t1.c1 type
                  FROM test.t1
                 ORDER BY t1.c1) t
) temp
 WHERE temp.type='張三'
 ORDER BY temp.type DESC;

3.使用limit子句,能同時阻止合併與條件下推。

例如:

SELECT temp.type
  FROM (SELECT substring(t.type,3) type
          FROM (SELECT distinct t1.c1 type
                  FROM test.t1
                 ORDER BY t1.c1) t limit 100000000000) temp
 WHERE temp.type='張三'
 ORDER BY temp.type DESC;

4.分配用戶變數,阻止derived table 合併。

例如:

SELECT temp.type
  FROM (SELECT (@i:=0) as num, substring(t.type,3) type
          FROM (SELECT distinct t1.c1 type
                  FROM test.t1
                 ORDER BY t1.c1) t) temp
 WHERE temp.type='張三'
 ORDER BY temp.type DESC;

這種方式就是前文提到的,為什麼加了一個與業務邏輯無關的用戶變數,結果就正確的原因。

5.使用union all來阻止derived table 合併

SELECT temp.type
  FROM (SELECT substring(t.type,3) type
          FROM (SELECT distinct t1.c1 type
                  FROM test.t1
                 ORDER BY t1.c1) t
                 union all 
                 select '1') temp
 WHERE temp.type='張三'
 ORDER BY temp.type DESC;

這些方法主要是依據優化器使用hint靈活控制優化器的開關,以及derive_merge與derived_condition_pushdown的使用限制。

三、總結

  1. MySQL8.0.22~MySQL8.0.25 因為優化器新特性derived_condition_pushdown帶來的bug,可以通過derived merge與 derived_condition_pushdown 的使用限制以及優化器開關hint來有效規避bug,當然升級到高版本更好。
  2. 如果想讓新特性derived_condition_pushdown發揮作用,就要避開它的使用限制。
  3. 一個新特性的出現,不可避免會伴隨著一些bug的產生,不要對此心存恐懼,只要深入瞭解它,就能取其長,避其短。

Enjoy GreatSQL

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...