TiDB SQL調優案例之避免TiFlash幫倒忙

来源:https://www.cnblogs.com/hohoa/archive/2023/03/28/17266326.html
-Advertisement-
Play Games

背景 早上收到某系統的告警tidb節點掛掉無法訪問,情況十萬火急。登錄中控機查了一下display信息,4個TiDB、Prometheus、Grafana全掛了,某台機器hang死無法連接,經過快速重啟後集群恢復,經排查後是昨天上線的某個SQL導致頻繁OOM。 於是開始亡羊補牢,來一波近期慢SQL巡 ...


背景

早上收到某系統的告警tidb節點掛掉無法訪問,情況十萬火急。登錄中控機查了一下display信息,4個TiDB、Prometheus、Grafana全掛了,某台機器hang死無法連接,經過快速重啟後集群恢復,經排查後是昨天上線的某個SQL導致頻繁OOM。

企業微信截圖_20230316113735.png

於是開始亡羊補牢,來一波近期慢SQL巡檢 #手動狗頭#。。。

隨便找了一個出現頻率比較高的慢SQL,經過優化後竟然性能提升了1500倍以上,感覺有點東西,分享給大家。

分析過程

該慢SQL邏輯非常簡單,就是一個單表聚合查詢,但是耗時達到8s以上,必有蹊蹺。

脫敏後的SQL如下:

SELECT
    cast( cast( CAST( SUM( num ) / COUNT( time ) AS CHAR ) AS DECIMAL ( 9, 2 )) AS signed ) speed,
    ... -- 此處省略n個欄位
FROM
    (
    SELECT 
        DATE_FORMAT( receive_time, '%Y-%m-%d %H:%i:00' ) AS time,
        COUNT(*) AS num 
    FROM
        db1.table 
    WHERE
        create_time > DATE_SUB( sysdate(), INTERVAL 20 MINUTE ) 
    GROUP BY
        time 
    ORDER BY
    time 
    ) speed;

碰到慢SQL不用多想,第一步先上執行計劃:

企業微信截圖_20230316150702.png

很明顯,這張900多萬行的表因為創建了TiFlash副本,在碰到聚合運算的時候優化器選擇了走列存查詢,最終結果就是在TiFlash完成暴力全表掃描、排序、分組、計算等一系列操作,返回給TiDB Server時基本已經加工完成,總共耗時8.02s。

咋一看好像沒啥優化空間,但仔細觀察會發現一個不合理的地方。執行計劃倒數第二排的Selection運算元,也就是SQL裡面子查詢的where過濾,實際有效數據1855行,卻掃描了整個表接近950W行,這是一個典型的適合索引加速的場景。但遺憾的是,在TiFlash裡面並沒有索引的概念,所以只能默默地走全表掃描。

那麼優化的第一步,先看過濾欄位是否有索引,通常來說create_time這種十有八九都建過索引,檢查後發現確實有。

第二步,嘗試讓優化器走TiKV查詢,這裡直接使用hint的方式:

SELECT /*+ READ_FROM_STORAGE(TIKV[db1.table]) */
    cast( cast( CAST( SUM( num ) / COUNT( time ) AS CHAR ) AS DECIMAL ( 9, 2 )) AS signed ) speed,
    ... -- 此處省略n個欄位
FROM
    (
    SELECT 
        DATE_FORMAT( receive_time, '%Y-%m-%d %H:%i:00' ) AS time,
        COUNT(*) AS num 
    FROM
        db1.table 
    WHERE
        create_time > DATE_SUB( sysdate(), INTERVAL 20 MINUTE ) 
    GROUP BY
        time 
    ORDER BY
    time 
    ) speed;

再次生成執行計劃,發現還是走了TiFlash查詢。這裡就引申出一個重要知識點,關於hint作用域的問題,也就是說hint只能在指定的查詢範圍內生效。具體到上面這個例子,雖然指定了db1.table走TiKV查詢,但是對於它所在的查詢塊來說,壓根不知道db1.table是誰直接就忽略掉了。所以正確的寫法是把hint寫到子查詢中:

SELECT
    cast( cast( CAST( SUM( num ) / COUNT( time ) AS CHAR ) AS DECIMAL ( 9, 2 )) AS signed ) speed,
    ... -- 此處省略n個欄位
FROM
    (
    SELECT  /*+ READ_FROM_STORAGE(TIKV[db1.table]) */
        DATE_FORMAT( receive_time, '%Y-%m-%d %H:%i:00' ) AS time,
        COUNT(*) AS num 
    FROM
        db1.table 
    WHERE
        create_time > DATE_SUB( sysdate(), INTERVAL 20 MINUTE ) 
    GROUP BY
        time 
    ORDER BY
    time 
    ) speed;

對應的執行計劃為:

企業微信截圖_20230316153949.png

小提示:

也可以通過set session tidb_isolation_read_engines = 'tidb,tikv';來讓優化器走tikv查詢。

發現這次雖然走了TiKV查詢,但還是用的TableFullScan運算元,整體時間不降反升,和我們預期的有差距。

沒走索引那肯定是和查詢欄位有關係,分析上面SQL的邏輯,開發是想查詢table表創建時間在最近20分鐘的數據,用了一個sysdate()函數獲取當前時間,問題就出在這。

獲取當前時間常用的函數有now()sysdate(),但這兩者是有明顯區別的。引用自官網的解釋:

  • now()得到的是語句開始執行的時間,是一個固定值
  • sysdate()得到的是該函數實際執行的時間,是一個動態值

聽起來比較饒,來個慄子一看便知:

mysql> select now(),sysdate(),sleep(3),now(),sysdate();
+---------------------+---------------------+----------+---------------------+---------------------+
| now()               | sysdate()           | sleep(3) | now()               | sysdate()           |
+---------------------+---------------------+----------+---------------------+---------------------+
| 2023-03-16 15:55:18 | 2023-03-16 15:55:18 |        0 | 2023-03-16 15:55:18 | 2023-03-16 15:55:21 |
+---------------------+---------------------+----------+---------------------+---------------------+
1 row in set (3.06 sec)

這個動態時間就意味著TiDB優化器在估算的時候並不知道它是個什麼值,走索引和不走索引哪個成本更高,最終導致索引失效。

從業務上來看,這個SQL用now()sysdate()都可以,那麼就嘗試改成now()看看效果:

SELECT
    cast( cast( CAST( SUM( num ) / COUNT( time ) AS CHAR ) AS DECIMAL ( 9, 2 )) AS signed ) speed,
    ... -- 此處省略n個欄位
FROM
    (
    SELECT  /*+ READ_FROM_STORAGE(TIKV[db1.table]) */
        DATE_FORMAT( receive_time, '%Y-%m-%d %H:%i:00' ) AS time,
        COUNT(*) AS num 
    FROM
        db1.table 
    WHERE
        create_time > DATE_SUB( now(), INTERVAL 20 MINUTE ) 
    GROUP BY
        time 
    ORDER BY
    time 
    ) speed;

企業微信截圖_20230316160428.png

最終結果4.43ms搞定,從8.02s到4.43ms,1800倍的提升。

濫用函數,屬於是開發給自己挖的坑了。

解決方案

經過以上分析,優化思路已經很清晰了,甚至都是常規優化不值得專門拿出來講,但前後效果差異太大,很適合作為一個反面教材來提醒大家認真寫SQL。

其實就兩點:

  • 讓優化器不要走TiFlash查詢,改走TiKV,可通過hint或SQL binding解決
  • 非必須不要使用動態時間,避免帶來索引失效的問題

深度思考

優化完成之後,我開始思考優化器走錯執行計劃的原因。

在最開始的執行計劃當中,優化器對Selection運算元的估算值estRows和實際值actRows相差非常大,再加上本身計算和聚合比較多,這可能是導致誤走TiFlash的原因之一。不清楚TiFlash的estRows計算原理是什麼,如果在估算準確的情況並且索引正常的情況下會不會走TiKV呢?

另外,我還懷疑過動態時間導致優化器判斷失誤(認為索引失效才選擇走TiFlash),但是在嘗試只修改sysdate()now()的情況下,發現依然走了TiFlash,說明這個可能性不大。

在索引欄位沒問題的時候,按正常邏輯來說,我覺得一個成熟的優化器應該要能夠判斷出這種場景走TiKV更好。

總結

TiFlash雖然是個好東西,但是優化器還在進化當中,難免有判斷失誤的時候,那麼會導致適得其反的效果,我們要及時通過人工手段介入。再給TiDB優化器一些時間。

良好的SQL習慣至關重要,這也是老生常談的問題了,再好的資料庫也扛不住亂造的SQL。

作者介紹:hey-hoho,來自神州數位鈦合金戰隊,是一支致力於為企業提供分散式資料庫TiDB整體解決方案的專業技術團隊。團隊成員擁有豐富的資料庫從業背景,全部擁有TiDB高級資格證書,並活躍於TiDB開源社區,是官方認證合作伙伴。目前已為10+客戶提供了專業的TiDB交付服務,涵蓋金融、證券、物流、電力、政府、零售等重點行業。

文章作者:hoho 首發論壇:博客園 文章出處:http://www.cnblogs.com/hohoa/ 歡迎大家一起討論分享,喜歡請點右下角的推薦鼓勵一下,我會有更多的動力來寫出好文章!歡迎持續關註我的博客! 歡迎轉載,轉載的時候請註明作者和原文鏈接。


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

-Advertisement-
Play Games
更多相關文章
  • PDF/A是一種ISO標準的PDF文件格式版本,是為長期保存文件而設計的。它提供了一種工具,使電子文件在長時間之後依然以一種保留其外觀的方式重現,而不管該文件是用什麼工具和系統創建、儲存或製作的。這種保留方式使PDF文件可自我持續。PDF/A通過嵌入在文檔自身內部顯示該文檔的信息(內容、顏色、字體、 ...
  • 本文屬於OData系列 目錄 武裝你的WEBAPI-OData入門 武裝你的WEBAPI-OData便捷查詢 武裝你的WEBAPI-OData分頁查詢 武裝你的WEBAPI-OData資源更新Delta 武裝你的WEBAPI-OData之EDM 武裝你的WEBAPI-OData常見問題 武裝你的WE ...
  • 一、前言 在最近的項目開發中,涉及到瞭解析DICOM文件。根據百度百科可知,DICOM(Digital Imaging and Communications in Medicine)即醫學數字成像和通信,是醫學圖像和相關信息的國際標準(ISO 12052)。它定義了質量能滿足臨床需要的可用於數據交換 ...
  • 一:背景 1. 講故事 前段時間收到了一個朋友的求助,說他的ERP網站系統會出現偶發性崩潰,找了好久也沒找到是什麼原因,讓我幫忙看下,其實崩潰好說,用 procdump 自動抓一個就好,拿到 dump 之後,接下來就是一頓分析了。 二:WinDbg 分析 1. 是什麼導致的崩潰 windbg 有一個 ...
  • 操作系統 移動端 安卓 iOS 鴻蒙 其他工業系統 桌面端 Windows MaciOS Unix Linux 伺服器 Unix Linux 購買主機 阿裡雲 騰訊雲 華為雲 其他雲平臺 虛擬機 宿主主機 物理硬體 CPU 記憶體 硬碟 操作系統 Mac Windows 虛擬機 Virtual Box ...
  • 一·依賴包以及下載地址 本文使用到的離線包: apr-1.7.0.tar.gz apr-util-1.6.1.tar.gz pcre2-10.40.tar.gz expat-2.1.0-14.el7_9.x86_64.rpm expat-devel-2.1.0-14.el7_9.x86_64.rpm ...
  • SFTP 常用命令 通過堡壘機進入的 Linux 操作系統,無法直接使用 WinSCP 等工具進行文件的上傳下載。 可使用 SecureCRT 先進入命令行模式 ...
  • 1. 概述 1.1. SQL-92標準裡加入的最有用的特性 1.2. 寫法 1.2.1. 簡單CASE表達式 CASE sex WHEN '1' THEN ’男’ WHEN '2' THEN ’女’ ELSE ’其他’ END 1.2.1.1. 寫法簡單,但能實現的事情比較有限 1.2.2. 搜索C ...
一周排行
    -Advertisement-
    Play Games
  • 1. 說明 /* Performs operations on System.String instances that contain file or directory path information. These operations are performed in a cross-pla ...
  • 視頻地址:【WebApi+Vue3從0到1搭建《許可權管理系統》系列視頻:搭建JWT系統鑒權-嗶哩嗶哩】 https://b23.tv/R6cOcDO qq群:801913255 一、在appsettings.json中設置鑒權屬性 /*jwt鑒權*/ "JwtSetting": { "Issuer" ...
  • 引言 集成測試可在包含應用支持基礎結構(如資料庫、文件系統和網路)的級別上確保應用組件功能正常。 ASP.NET Core 通過將單元測試框架與測試 Web 主機和記憶體中測試伺服器結合使用來支持集成測試。 簡介 集成測試與單元測試相比,能夠在更廣泛的級別上評估應用的組件,確認多個組件一起工作以生成預 ...
  • 在.NET Emit編程中,我們探討了運算操作指令的重要性和應用。這些指令包括各種數學運算、位操作和比較操作,能夠在動態生成的代碼中實現對數據的處理和操作。通過這些指令,開發人員可以靈活地進行算術運算、邏輯運算和比較操作,從而實現各種複雜的演算法和邏輯......本篇之後,將進入第七部分:實戰項目 ...
  • 前言 多表頭表格是一個常見的業務需求,然而WPF中卻沒有預設實現這個功能,得益於WPF強大的控制項模板設計,我們可以通過修改控制項模板的方式自己實現它。 一、需求分析 下圖為一個典型的統計表格,統計1-12月的數據。 此時我們有一個需求,需要將月份按季度劃分,以便能夠直觀地看到季度統計數據,以下為該需求 ...
  • 如何將 ASP.NET Core MVC 項目的視圖分離到另一個項目 在當下這個年代 SPA 已是主流,人們早已忘記了 MVC 以及 Razor 的故事。但是在某些場景下 SSR 還是有意想不到效果。比如某些靜態頁面,比如追求首屏載入速度的時候。最近在項目中回歸傳統效果還是不錯。 有的時候我們希望將 ...
  • System.AggregateException: 發生一個或多個錯誤。 > Microsoft.WebTools.Shared.Exceptions.WebToolsException: 生成失敗。檢查輸出視窗瞭解更多詳細信息。 內部異常堆棧跟蹤的結尾 > (內部異常 #0) Microsoft ...
  • 引言 在上一章節我們實戰了在Asp.Net Core中的項目實戰,這一章節講解一下如何測試Asp.Net Core的中間件。 TestServer 還記得我們在集成測試中提供的TestServer嗎? TestServer 是由 Microsoft.AspNetCore.TestHost 包提供的。 ...
  • 在發現結果為真的WHEN子句時,CASE表達式的真假值判斷會終止,剩餘的WHEN子句會被忽略: CASE WHEN col_1 IN ('a', 'b') THEN '第一' WHEN col_1 IN ('a') THEN '第二' ELSE '其他' END 註意: 統一各分支返回的數據類型. ...
  • 在C#編程世界中,語法的精妙之處往往體現在那些看似微小卻極具影響力的符號與結構之中。其中,“_ =” 這一組合突然出現還真不知道什麼意思。本文將深入剖析“_ =” 的含義、工作原理及其在實際編程中的廣泛應用,揭示其作為C#語法奇兵的重要角色。 一、下劃線 _:神秘的棄元符號 下劃線 _ 在C#中並非 ...