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
  • PasteSpider是什麼? 一款使用.net編寫的開源的Linux容器部署助手,支持一鍵發佈,平滑升級,自動伸縮, Key-Value配置,項目網關,環境隔離,運行報表,差量升級,私有倉庫,集群部署,版本管理等! 30分鐘上手,讓開發也可以很容易的學會在linux上部署你得項目! [從需求角度介 ...
  • SQLSugar是什麼 **1. 輕量級ORM框架,專為.NET CORE開發人員設計,它提供了簡單、高效的方式來處理資料庫操作,使開發人員能夠更輕鬆地與資料庫進行交互 2. 簡化資料庫操作和數據訪問,允許開發人員在C#代碼中直接操作資料庫,而不需要編寫複雜的SQL語句 3. 支持多種資料庫,包括但 ...
  • 在C#中,經常會有一些耗時較長的CPU密集型運算,因為如果直接在UI線程執行這樣的運算就會出現UI不響應的問題。解決這類問題的主要途徑是使用多線程,啟動一個後臺線程,把運算操作放在這個後臺線程中完成。但是原生介面的線程操作有一些難度,如果要更進一步的去完成線程間的通訊就會難上加難。 因此,.NET類 ...
  • 一:背景 1. 講故事 前些天有位朋友在微信上丟了一個崩潰的dump給我,讓我幫忙看下為什麼出現了崩潰,在 Windows 的事件查看器上顯示的是經典的 訪問違例 ,即 c0000005 錯誤碼,不管怎麼說有dump就可以上windbg開幹了。 二:WinDbg 分析 1. 程式為誰崩潰了 在 Wi ...
  • CSharpe中的IO+NPOI+序列化 文件文件夾操作 學習一下常見的文件、文件夾的操作。 什麼是IO流? I:就是input O:就是output,故稱:輸入輸出流 將數據讀入記憶體或者記憶體輸出的過程。 常見的IO流操作,一般說的是[記憶體]與[磁碟]之間的輸入輸出。 作用 持久化數據,保證數據不再 ...
  • C#.NET與JAVA互通之MD5哈希V2024 配套視頻: 要點: 1.計算MD5時,SDK自帶的計算哈希(ComputeHash)方法,輸入輸出參數都是byte數組。就涉及到字元串轉byte數組轉換時,編碼選擇的問題。 2.輸入參數,字元串轉byte數組時,編碼雙方要統一,一般為:UTF-8。 ...
  • CodeWF.EventBus,一款靈活的事件匯流排庫,實現模塊間解耦通信。支持多種.NET項目類型,如WPF、WinForms、ASP.NET Core等。採用簡潔設計,輕鬆實現事件的發佈與訂閱。通過有序的消息處理,確保事件得到妥善處理。簡化您的代碼,提升系統可維護性。 ...
  • 一、基本的.NET框架概念 .NET框架是一個由微軟開發的軟體開發平臺,它提供了一個運行時環境(CLR - Common Language Runtime)和一套豐富的類庫(FCL - Framework Class Library)。CLR負責管理代碼的執行,而FCL則提供了大量預先編寫好的代碼, ...
  • 本章將和大家分享在ASP.NET Core中如何使用高級客戶端NEST來操作我們的Elasticsearch。 NEST是一個高級別的Elasticsearch .NET客戶端,它仍然非常接近原始Elasticsearch API的映射。所有的請求和響應都是通過類型來暴露的,這使得它非常適合快速上手 ...
  • 參考delphi的代碼更改為C# Delphi 檢測密碼強度 規則(仿 google) 仿 google 評分規則 一、密碼長度: 5 分: 小於等於 4 個字元 10 分: 5 到 7 字元 25 分: 大於等於 8 個字元 二、字母: 0 分: 沒有字母 10 分: 全都是小(大)寫字母 20 ...