天天看點

MySQL自治平台建設的核心原理及實踐(上)

作者:閃念基因

本文作者在演講後根據同學們的回報,補充了很多技術細節,跟演講(視訊)相比,内容更加豐富。文章分成上、下兩篇,上篇将介紹資料庫的異常發現跟診斷方面的内容,下篇将介紹核心可觀測性建設、全量SQL、異常處理以及索引優化建議與SQL治理方面的内容。希望能夠對大家有所幫助或啟發。

  • 1 背景&目标
  • 2 平台演進政策
  • 3 異常發現
    • 3.1 資料分布規律與算法選擇
    • 3.2 模型選擇
  • 4 異常診斷
    • 4.1 主從延遲(核心代碼路徑分析)
    • 4.2 大事務診斷分析(核心功能增強)
    • 4.3 MySQL Crash分析(核心Core Dump分析)

美團技術團隊,贊4

1 背景&目标

MySQL的故障與SQL的性能,是DBA跟研發同學每天都需要關注的兩個重要問題,它們直接影響着資料庫跟業務應用程式的穩定性。而當故障或者SQL性能問題發生時,如何快速發現、分析以及處理這些問題,使得資料庫或者業務系統快速恢複,是一項比較大的挑戰。

針對此問題,美團資料庫自治平台經過多輪的疊代建設,在多個場景下已經實作了異常的發現、分析以及處理的端到端能力。本文将跟大家分享一下我們平台建設的心路曆程,同時提供一些經驗、教訓供同行參考,希望能夠起到“抛磚引玉”的作用。本文主要介紹以下主題:

  • 異常發現:基于數理統計方式的動态閥值政策,來發現資料庫系統的名額異常。
  • 故障分析:豐富完善資料庫關鍵資訊,來做精确的資料庫異常根因分析;深入挖掘核心價值,來解決根因診斷方面的疑難雜症。
  • 故障處理:依據異常根因分析的不同結果,通過自助化或自動化的方式來進行故障的恢複處理。
  • 核心可觀測性建設:如何跟資料庫核心團隊合作,從核心的角度來分析SQL性能問題,通過核心團隊大量的核心代碼改造,力求将資料庫的可觀測性跟診斷做到極緻。
  • 單SQL優化建議:通過改造MySQL存儲引擎,同時結合查詢優化來打造基于Cost模式的索引優化建議。
  • 基于workload索引優化建議:基于整個DB或者執行個體的Workload政策的索引優化建議,為實作資料庫的索引自維護提供前置條件。
  • 基于SQL生命周期的治理:實作從SQL上線前、執行過程中、執行完畢後幾個環節,以期實作端到端的慢SQL治理。

2 平台演進政策

美團資料庫自治平台從下到上總體分為四層,分别為接口與展示、平台功能層,計算與存儲、資料采集層,平台的總體架構以及每一層的作用如下:

MySQL自治平台建設的核心原理及實踐(上)
  • 資料庫采集層:要進行資料庫的診斷與分析,需要依靠關鍵的名額以及SQL文本資料,目前在每個資料庫執行個體上部署一個資料采集程式(rds-agent)統一負責采集、上報關鍵數值名額以及SQL文本資料。
  • 資料計算與存儲層:資料采集層上報上來的資料,依托Kafka、Flink&Spark作為資料緩沖,對關鍵元件進行相關的資料處理,如SQL解析、SQL模版化、資料聚合等操作,再把處理的結果存入ES、Blade(美團自研的分布式資料庫)、Hive等分布式資料庫或者大資料平台,提供給上層的平台功能層使用。
  • 平台功能層:此層是整個系統最為重要的部分,由于平台同時服務于DBA運維團隊及研發團隊,是以平台的建設分成了兩條路:1)主要面向DBA使用者,按照可觀測性建設、異常發現、故障根因分析、故障處理幾個階段來進行建設;2)主要面向研發同學,按照SQL優化建議、風險SQL的發現、分析與SQL治理等跟SQL相關的幾個階段來建設。當然,兩者并沒有嚴格界限,這些功能所有的使用者都可以同時使用。
  • 接口與展示:平台功能層提供的核心功能會通過Portal來展示,同時為了讓平台提供的功能更好地內建在使用者自己的系統中,我們也通過OpenAPI的方式對外提供服務。

3 異常發現

資料庫産生異常時需要盡早地發現,才能防止異常一進步放大,避免造成真正的故障。異常發現的主要方式是對資料庫或者OS的關鍵數值名額進行監控,相關名額包括seconds_behind_master、slow_queries、thread_running、system load、Threads_connected等等,也可以是業務端研發關注的關鍵名額,如“應用程式通路資料庫的報錯數量”、“SQL執行平均耗時”等名額來進行監控。如果這些名額短時間内發生比較大的波動,那麼資料庫很可能出現了一些異常,這就需要及時進行處理。

這些異常如何才能被發現呢?業界一般有基于靜态閥值以及動态閥值的兩種異常發現政策。前者很簡單,如根據專家經驗,人工設定seconds_behind_master或者Threads_connected的告警閥值,超過閥值就認為發生了異常。此方式雖然簡單易用,但OLTP、OLAP等不同的業務場景,對于相同名額的敏感度是不一樣的,如果所有場景都使用統一的靜态閥值來做異常發現,難免會有很多誤告。而如果每個場景都去手工去調整,既不靈活,成本又太高,解決方案是基于不同場景的曆史時序資料,使用數理統計的方式來分别模組化,通過拟合出各自場景的模型來作為異常發現的政策。

| 3.1 資料分布規律與算法選擇

基于數理統計方法的異常發現,需要根據具體的場景使用特定的模型。一般來說,模型的選擇跟時序資料的分布形态有很大的關系,時序資料的分布并不總是都像正态分布一樣都是對稱的,而是有些是左偏的,有些是右偏的,當然也有對稱分布的。下圖就展示典型的三種不同的時序資料分布形态。

MySQL自治平台建設的核心原理及實踐(上)

針對上面的三種不同時序資料的分布形态,以及每種異常檢測算法自身的特性,我們分别采用不同的異常檢測算法。

對于低偏态高對稱分布選擇“絕對中位差(MAD)”,中等偏态分布選擇“箱形圖(Boxplot)”,高偏态分布選擇“極值理論(EVT)”。沒有選擇3Sigma的主要原因是:它對異常容忍度較低(模組化的時候,如果有噪音等異常點也不會對模型的形态産生很大的影響,則說明異常容忍度很高),而絕對中位差(MAD)從理論上而言具有更好的異常容忍度,是以在資料呈現高對稱分布時,通過絕對中位差替代3Sigma來進行檢測。

MySQL自治平台建設的核心原理及實踐(上)

| 3.2 模型選擇

資料分布跟算法适用場景的分析之後,對内部的時序資料進行檢查,發現資料的規律主要呈現漂移、周期和平穩三種狀态,對樣本先進行時序的漂移(如果檢測存在漂移的場景,則需要根據檢測獲得的漂移點t來切割輸入時序,使用漂移點後的時序樣本作為後續模組化流程的輸入)。

之後同時進行平穩性分析(如果輸入時序S滿足平穩性檢驗,則直接通過箱形圖或絕對中位差的方式來進行模組化)以及周期分析(存在周期性的情況下,将周期跨度記為T,将輸入時序S根據跨度T進行切割,針對各個時間索引j∈{0,1,⋯,T−1} 所組成的資料桶進行模組化流程。不存在周期性的情況下,針對全部輸入時序S作為資料桶進行模組化流程),再對時序資料分布特性進行偏度的計算,最後再根據不同的偏度特性選擇不同的算法模型,具體如下:

MySQL自治平台建設的核心原理及實踐(上)

在算法确定之後,先在離線環境針對不同的場景使用曆史名額來訓練模型,模型訓練完畢之後會存入資料庫,在生産環境運作過程中,對于不同場景下的數值名額根據其特性來加載不同的模型,并且結合Flink實時計算架構來實時的發現名額的異常并進行告警。

MySQL自治平台建設的核心原理及實踐(上)

4 異常診斷

發現名額異常後,需要快速的給出異常的根因,我們可以根據具體的根因來選擇不同的處理政策,然後進行自動或者手動的恢複工作。根因分析可以基于專家經驗,也可以嚴格地按照核心代碼的邏輯來進行分析。

MySQL自治平台建設的核心原理及實踐(上)

本文重點講述後者,強調如何使用“核心思維”來解決專家經驗很難或者無法解決的診斷問題。本文将列舉“核心代碼路徑分析”、”核心日志分析”、“核心功能增強“、“核心Core Dump分析”以及“核心埋點”等幾種不同的範式,來說明資料庫根因診斷的思路。

| 4.1 主從延遲(核心代碼路徑分析)

這裡先介紹“核心代碼路徑分析”這個方式來診斷根因。對于資料一緻性要求比較高的應用程式,seconds_behind_master是一個十分重要的名額,如果其值過大就需要診斷其根因,防止應用程式讀取到不一緻的資料。根據專家經驗,其值過大可能由“QPS突增”、“大事務”、“大表DDL”、“鎖阻塞”、“表缺少主鍵或者唯一健”、“低效執行計劃”、“硬體資源不足”等因數造成,把這些專家經驗總結成規則清單,當異常産生時逐個疊代去驗證是不是符合某個規則,據此來診斷根因,然而此方式存在如下兩大問題:

  1. 無法枚舉所有根因:經驗由于其固有的局限性不可能考慮到所有的故障場景,如何完整的給出造成seconds_behind_master值異常的所有規則是一個挑戰;另外,如果需要對一個全新的名額進行診斷,而在沒有任何的專家經驗情況下,如何能快速地整理出完整的規則清單?
  2. 缺乏對根因的深層次了解:“QPS突增”、“大事務”、“大表DDL”、“鎖阻塞”、“低效執行計劃”、“硬體資源不足”等因素會造成seconds_behind_master名額值的異常,但是為什麼這些因數會造成名額值的異常呢?如果不從核心源碼角度來了解這些因素跟seconds_behind_master之間的邏輯計算關系,那麼是無法了解真正原因的。

4.1.1 核心代碼路徑分析

針對上面兩個問題,具體政策是直接看seconds_behind_master這個變量在核心層面是如何計算的,隻有這樣才能完整的枚舉出所有影響seconds_behind_master值計算的因數。

從源碼角度看,seconds_behind_master的值由①time(0)、②mi->rli->last_master_timestamp和③mi->clock_diff_with_master這三個變量來決定(代碼層面seconds_behind_master的計算邏輯是:seconds_behind_master=((long)(time(0) - mi->rli->last_master_timestamp)- mi->clock_diff_with_master),其中time(0)是系統目前時間(用秒表示),clock_diff_with_master這個值的計算很複雜、又很關鍵,會放到下一節詳細進行說明。

而針對mi->clock_diff_with_master的計算,這個變量從源碼層面看就是主、從執行個體之間的時間差;根據目前的資訊就可以看出來,從庫的目前時間以及主從庫之間的時間差都會影響seconds_behind_master值的計算。seconds_behind_master的計算和事務在主從庫執行的情況如下:

MySQL自治平台建設的核心原理及實踐(上)

last_master_timestamp計算邏輯

從上面分析可以知道,last_master_timestamp值是影響seconds_behind_master值計算的關鍵變量,是以很有必要從源碼角度分析影響last_master_timestamp值的因數有哪些(進而間接擷取了影響seconds_behind_master值的因素)。

last_master_timestamp的值來自于一個叫rli->gaq->head_queue()的成員變量new_ts(此處的rli->gaq->head_queue()是指代某個最新的已經完成replay的事務對應的event group,event group是指一個事務在binlog檔案裡生成一組event來表示某個事務,這個event group裡的event從主庫傳輸到從庫後進行replay操作來還原主庫的事務)。new_ts值來源于rli->gaq->head_queue())->ts,而rli->gaq->head_queue())->ts的值是通過ptr_group->ts= common_header->when.tv_sec + (time_t) exec_time計算擷取的。

再看一下when.tv_sec跟exec_time的含義,前者指代SQL在主庫上的SQL執行的開始時間,後者指代SQL在主庫上的“執行時長”,“執行時長”又跟“鎖阻塞”、“低效執行計劃”、“硬體資源不足”等因素息息相關。

值得注意的是,前面提到的rli->gaq->head_queue())->ts的計算跟slave_checkpoint_period以及sql_delay兩個變量也有關系,按照這個思路層層疊代下去找出所有影響seconds_behind_master值的因素,這些因素都是潛在的主從延遲異常的根源,這樣也解決了前面說的“無法枚舉所有根因”跟“缺乏對根因的深層次了解”兩大問題。

為了便于了解上訴的邏輯,放出關鍵的源代碼:擷取last_master_timestamp值的來源rli->gaq->head_queue()的成員變量new_ts。

bool mts_checkpoint_routine(Relay_log_info *rli, ulonglong period,
                            bool force, bool need_data_lock)
{
 do
  { 
    cnt= rli->gaq->move_queue_head(&rli->workers);
  } 
 .......................
  ts= rli->gaq->empty()
    ? 0
    : reinterpret_cast<Slave_job_group*>(rli->gaq->head_queue())->ts; //其中的ts來自下面的get_slave_worker函數;
  rli->reset_notified_checkpoint(cnt, ts, need_data_lock, true);
  //  社群版本的代碼 rli->reset_notified_checkpoint(cnt, rli->gaq->lwm.ts, need_data_lock);
  /* end-of "Coordinator::"commit_positions" */
 ......................
}           

擷取Master執行個體上執行的SQL的開始跟執行時長資訊tv_sec跟exec_time。

Slave_worker *Log_event::get_slave_worker(Relay_log_info *rli)
{
if (ends_group() || (!rli->curr_group_seen_begin && (get_type_code() == binary_log::QUERY_EVENT || !rli->curr_group_seen_gtid)))
  {
  ..............
    ptr_group->checkpoint_seqno= rli->checkpoint_seqno;
    ptr_group->ts= common_header->when.tv_sec + (time_t) exec_time; // Seconds_behind_master related
    rli->checkpoint_seqno++;
  }
}            

根因層疊圖

如果進一步分析核心代碼,可以發現影響seconds_behind_master變量計算的因素還有很多,但是找出這些因素的思路是相同的。這個思路的好處是:無論之前有沒有相關專家經驗,理論上這種分析方式都能盡可能地枚舉出所有的根因。

除了seconds_behind_master,其他的像thread_running、Threads_connected,slow_queries等名額異常的分析也都可以套用這種思路。下面為按照上述思路整理出來的影響seconds_behind_master值的部分因素的層次結構圖:

MySQL自治平台建設的核心原理及實踐(上)

4.1.2 流程分析

把影響seconds_behind_master值的相關因素确認後,可以畫一個流程圖把這些因素都展現在流程圖中的具體位置。這樣既能比較形象地了解影響seconds_behind_master的因素在整個主從複制流程中的所處的位置,又便于對整體規則進行查漏補缺。

下面我們使用一個具體的例子,來說明一下上面分析的因素是如何影響seconds_behind_master的。從下圖可以看出在執行SQL的過程中影響seconds_behind_master計算的兩個變量thd->start_time跟exec_time的計算在master執行個體。假設start_time的值為2023-07-03 00:00:00,SQL執行了60秒,是以exec_time為60,2023-07-03 00:01:00,SQL在主庫上執行完畢,在從庫上replay這個SQL,可以看到seconds_behind_master值會從0開始并且逐漸增加60秒,然後再傳回0。

MySQL自治平台建設的核心原理及實踐(上)

具體原因是:假設我們忽略binlog日志的傳輸時間,那麼從庫開始執行replay這個SQL的開始時間也是2023-07-03 00:01:00,是以根據seconds_behind_master=((long)(time(0) - mi->rli->last_master_timestamp)- mi->clock_diff_with_master)=2023-07-03 00:01:00 - 2023-07-03 00:00:00-60s,結果就是0,然後SQL的執行時間是60秒,并且(long)(time(0)(目前時間)的時間一秒一秒的在增加,是以seconds_behind_master值會從0開始逐漸增加至60秒。

再看一下其他的因數,協調器(Coordinator)會把Group放入一個叫做GAP Group的隊列中,Coordinator會以slave_checkpoint_period值為周期來掃描GAP Group中的元素并且更新rli->gaq->head_queue())->ts值,是以果slave_checkpoint_period的值被設定的很大,那麼rli->gaq->head_queue())->ts的值因為沒有及時更新而變得比較舊,進而引起seconds_behind_master值變大。

另外,每個Worker讀取自己隊列的Group元素,進行repaly操作,需要注意的是sql_delay這個變量,如果目前時間還沒有達到sql_delay規定的時間(假設sql_delay被設定為100秒,那麼SQL對應的binlog日志到達從庫後需要等待100秒再執行),那麼worker就不會進行repaly工作,這間接導緻影響計算seconds_behind_master變量thd->start_time值比正常情況下小了100秒,是以當worker進行replay的時候,seconds_behind_master的值就會相應的增加100秒。

4.1.3 産品展示

下面的産品展示了因為流量突增跟MDL鎖造成的主從延遲的診斷分析報告的産品頁面。我們可以看到,流量突增的具體SQL以及MDL鎖的持有者,友善使用者進行限流或者Kill掉阻塞者SQL。

MySQL自治平台建設的核心原理及實踐(上)

| 4.2 大事務診斷分析(核心功能增強)

大事務的存在,對整個資料庫系統的穩定性與總體SQL的性能都會産生很大的挑戰,如大事務長時間持有某個鎖會造成大面積阻塞,或者更改過多的行數造成整個執行個體硬體資源的不足。我們需要及時發現這些場景,并且将其資訊發送給使用者治理,但在實踐過程中,往往面臨如下的兩大挑戰:

第一個挑戰:無法得到大事務所包含的完整的SQL清單,造成使用者不清楚大事務的全貌是什麼,使用者也就無法識别需要優化的大事務。

  • 解決方案:每個事務來MySQL會在核心層面生成一個唯一的事務ID:trx_id,如果事務包含的每條SQL,都給其附加一個事務ID trx_id字段,并且把這些SQL連同trx_id一起輸出(通過全量SQL輸出),問題就可以解決;不過這裡還有一個挑戰,這個事務ID到底是何時産生的呢?如何大家熟悉核心内部事務的執行過程,就會知道事務ID的隻有在事務修改資料的時候才會通過trx_assign_id_for_rw這個方法被擷取,這意味着就下面這個圖上展示的事務而言,是無法擷取SQL4之前執行的讀SQL語句清單,是以擷取到的事務的SQL清單還是殘缺的,那麼如何擷取到完整的SQL清單呢?解決方案也很簡單,可以把事務ID的生成邏輯提前到在事務剛開始執行的時候生成就可以了。

第二個挑戰:大事務的耗時組成不明确。資料庫規定執行時長大于某個閥值的事務被定義為大事務,但是不清楚耗時到底是SQL本身的執行時間還是SQL執行之外的耗時,SQL執行之外的耗時可能是在執行上下兩個SQL之間,業務端在處理一些跟資料庫無關的業務邏輯,也可能是網絡延遲造成的。

  • 解決方案:上述問題可以通過在資料庫核心内對SQL執行開始時、結束時分别埋點時間戳來解決,這樣整個大事務執行總時間中有多少時間是在執行SQL,有多少時間是在Sleep就一目了然;當然,這一塊還可以做的更加的細緻,比如兩條SQL之間的Sleep時間到底是網絡延遲還是應用程式端的延遲等,可以進一步細分大事務造成的原因到底是在MySQL端、網絡端還是使用者自己的應用程式造成的等待;關于計算網絡端的延遲計算,可以參考MySQL内部的mysql_socket_send_time跟vio_socket_io_wait_time這2個關鍵名額的實作思路,下圖是一個大事務的SQL清單以及耗時組成清單。
MySQL自治平台建設的核心原理及實踐(上)

4.2.1 産品展示

核心團隊通過核心改造,對事務中所包含的SQL都提供了trx_id後就可以根據trx_id把整個事務所有的SQL串起來。根據SQL執行的開始跟結束時間,我們也提供了所有SQL之間的Sleep時間,成功解決了上面的兩個挑戰,産品效果圖如下:

MySQL自治平台建設的核心原理及實踐(上)

| 4.3 MySQL Crash分析(核心Core Dump分析)

MySQL執行個體突然Crash了怎麼進行根因診斷?程序Crash的根因分析,也是資料庫故障中最難分析的問題之一。本節提供一些思路嘗試去分析各種場景下的MySQL Crash的根因。

4.3.1 Crash的觸發方式

在分析Crash的根因之前,我們可以先了解一下MySQL程序是如何被Crash的整個過程十分有必要。一般來說,觸發Crash的原因分成兩類:①MySQL程序自己觸發了Crash(這裡稱之為MySQL自殺);②MySQL程序被OS殺死。

針對前者,比如MySQL發現某個關鍵資料發生了Data Corruption、磁盤空間不足、硬體錯誤、等待核心鎖時間過長、MySQL 核心Bug等場景,都可能導緻MySQL自殺。尤其是檢查到MySQL核心裡有些資料的狀态不符合預期時,是必要要讓那個執行個體Crash也不能繼續執行,否則可能會産生更加嚴重的資料不一緻性問題;而後者,OS如果發現系統記憶體嚴重不足或者空指針引用等情況,就會把包括MySQL在内相關的程序殺掉。

分析一下MySQL自身觸發Crash這個場景,在MySQL内部有很多地方通過ut_a(如果是ut_error的話,則直接觸發Crash)斷言對程式的内部資料狀态進行異常檢查,如果發現資料狀态不符合預期,那麼勢必發生了Data Corruption,這個時候程式會調用ut_dbg_assertion_failed在程序Crash之前做一些關鍵資訊(如thread id、發生Crash的檔案名字跟code line等)的記錄後,會繼續調用abort()向程序發送SIGABRT信号量讓程序Crash掉。

需要注意的是,從abort()的源碼可知,這裡調用了兩次raise (SIGABRT),第一次調用raise (SIGABRT)觸發處理函數handle_fatal_signal(此函數在MySQL執行個體初始化時通過sigaction的sa_handler注冊)被調用,這個函數主要是列印一些重要的調試資訊,便于做Core Dump分析;第二次調用raise (SIGABRT)的目的,是為了讓OS生成Core Dump檔案(core Dump檔案非常重要,因為所有引起MySQL Crash的現場資訊都包含在Core Dump裡面);如果是在MySQL自殺的情況下發生了Crash ,一般會在errorlog裡會産生如下的一段跟Crash相關的現場資訊,其中的“signal”、“觸發Crash的線程堆棧”、“正在執行的SQL”等資訊都是分析Crash根因的關鍵資訊。

下圖為MySQL通過ut_a斷言檢查異常問題後再到OS觸發程序Crash的整體流程圖。總體來說,MySQL通過raise來發送SIGABRT信号到一個隊列,OS核心處理這個隊列的信号并調用MySQL的處理程式handle_fatal_signal來保留一些關鍵的現場資訊。這裡需要注意到的是,OS核心在__setup_rt_frame中執行“regs->ip = (unsigned long) ka->sa.sa_handler;”,這個步驟正是讓MySQL的handle_fatal_signal方法被順利的調用的原因。

MySQL自治平台建設的核心原理及實踐(上)

4.3.2 根據Signal類型做根因分析

分析Crash根因的第一步就是看觸發了什麼類型的signal,常見類型有“signal 6”、“signal 7”、“signal 11”幾種類型,當知道了Signal類型後就有一個根因分析的大方向。根據經驗,我們将常見的signal類型以及可能引起的原因大緻如下圖所示,下面對于常見的signal類型以及引起的根因做一個簡單的分析。

MySQL自治平台建設的核心原理及實踐(上)

1) 如果是signal 6,一般是執行個體的磁盤空間不足或者磁盤隻讀,MySQL的資料發生了data corruption,核心層面latch鎖的長時間的鎖等待造成。不過這裡的data corruption、長時間的鎖等待可能是硬碟損壞或者MySQL的Bug造成的,判斷邏輯如下:

  1. 磁盤空間不足或者磁盤隻讀
  • 磁盤寫資料時,如果磁盤沒有剩餘空間或者資料庫被設定為read only就會造成執行個體的Crash,日志中有“Either disk is full or file system is read only while opening the binlog”的字樣。
  1. data corruption
  • MySQL在運作過程中如果斷言(比如這裡ut_a(table != NULL)傳回false,很可能是資料發生了corruption并且MySQL就會自行Crash掉;發生data corruption時一般在MySQL的error log中有“Database page corruption on disk or a failed file read of tablespace”的字樣,是以檢視日志來判斷否有硬碟故障問題,如果沒有硬體故障資訊,則可能是MySQL Bug造成的data corruption,具體分析看下面 “MySQL Bug”那部分。
  1. 長時間無法擷取Latch鎖
  • 如果MySQL長時間沒有辦法擷取到Latch鎖,那麼MySQL認為系統可能是Hang住了也會引起執行個體的Crash,并且日志中列印“We intentionally crash the server because it appears to be hung”字樣,一般是硬體故障造成的性能問題或者MySQL自身的設計缺陷形成的性能問題造成的,這次場景根因分析比較有挑戰。
  1. MySQL Bug
  • 如果不屬于上面任何一種情況,那麼有可能是MySQL自身的Bug造成的Crash,比如MySQL對一些SQL解析或者執行時會發生Crash;這種場景一般先看一下Crash發生時正在執行的SQL是什麼,這個SQL可能存在于Crash日志中(這個Crash日志中有個例子),可以先把SQL提取出來再次執行檢視能否複現問題;如果在Crash日志中看不到SQL語句,就需要從core dump檔案中提取SQL了,提取方式是MySQL每個連結對應的THD的成員變量m_query_string就包含了SQL文本,隻需要打開Core Dump檔案切換到某個包含THD執行個體的方法内,通過指令“p this->thd->m_query_string.str ”來列印,這裡有個例子。
  • 再舉個MySQL的Bug造成data corruption的例子,從Crash日志裡“InnoDB: Assertion failure in thread 139605476095744 in file rem0rec.cc line 578”看出,從rec_get_offsets_func函數中觸發ut_error而導緻的Crash,之是以觸發這個Crash是因為rec_get_offsets_func中的rec_get_status(rec)擷取到的MySQL的記錄類型不符合預期(因為記錄類型隻有固定的REC_STATUS_ORDINARY、REC_STATUS_NODE_PTR,REC_STATUS_INFIMUM,REC_STATUS_SUPREMUM這4種類型),如果核心發現一個記錄類型不屬于這4種類型的任何一種,那麼就是發生了data corruption,是以必須要把自己Crash掉。為了驗證剛才的結論,看一下Crash發生時的rec的類型是什麼,從源碼可知rec的類型是通過rec_get_status擷取,并且其通過調用的rec_get_bit_field_1跟mach_read_from_1兩個函數可以知道rec的類型其實就是rec這個指針往前三個byte(通過#define REC_NEW_STATUS_MASK 0x7UL可知)代表的值。
  • 通過gdb加載core dump檔案後,切換到抛出exception的線程,因為異常是在rec_get_offsets_func裡抛出的,切換到rec_get_offsets_func對應的frame 7來驗證rec的類型,看到rec的指針位址為0x7f14b7f5685d(相關分析資料可以看此連結)。前面說過,rec的類型值在rec指針往前三個byte裡,也就是指針0x7f14b7f5685a(0x7f14b7f5685d-3)那個位置的值,發現是0x1f,執行與計算(11111(1f)&00111(0x7UL)=00111=7 )得到的類型是7,而記錄類型的範圍是(0~3),很明顯這個指針指向的記錄類型值資訊發生了data corruption(分析過程檢視此連結),這裡做了一個rec的類型在正常情況下跟本例異常情況下的類型值計算的對比表,發現正常情況下,rec的類型值就應該是3。
MySQL自治平台建設的核心原理及實踐(上)
  • 這裡有個重要問題是,為什麼rec的類型是無效的呢?很可能是MySQL搜尋滿足條件的記錄的時候,rec指向的記錄很可能被page_cleaner在背景被清理掉了,是以rec指針指向的記錄就是無效了。官方有個bugfix,解決方案就是把prev_rec設定為NULL(這裡的prev_rec是persistent cursor指向的記錄,這裡說一下persistent cursor,它是MySQL從InnoDB 層獲得記錄後進入SQL層前在B-tree上的cursor會被暫時存儲到row_prebuilt_t::pcur中,當再次從InnoDB層拿資料時,如果對應的buf_block_t沒有發生修改,則可以繼續沿用之前存儲的cursor,否則需要重新定位,如果沒有persistent cursor則每次都需要重新定位則對性能有影響),這樣prev_rec != NULL這個條件不滿足,也就沒有機會進入rec_get_offsets_g_func裡去檢查rec的類型值而引發Crash了。

2) 如果為signal 7,那麼大機率是記憶體硬體錯誤,并且日志裡一般有“mce: [Hardware Error]: Machine check events logged , mce: Uncorrected hardware memory error in user-access at xxx MCE xxx: Killing mysqld:xxx due to hardware memory corruption”等字樣。

3) 如果為signal 9,表示這個程序被Kill指令殺掉了。

4) 如果為signal 11,表示是由MySQL的Bug造成的,這類問題較難分析特别是MySQL Crash現場(通過core dump列印出來的堆棧資訊)往往還不是第一現場,由于篇幅關系具體的例子分析不在本文中給出,但是分析的思路跟上面的“MySQL Bug”是類似的。

5 本文作者

裕鋒,來自美團基礎研發平台-基礎技術部,負責美團資料庫自治平台的相關工作。

6 參考

  • https://github.com/shenyufengdb/sql
  • https://github.com/percona/percona-server/blob/release-5.7.41-44
  • An Efficient, Cost-Driven Index Selection Tool for Microsoft SQL Server
  • plan-stitch-harnessing-the-best-of-many-plans-2
  • Random Sampling for Histogram Construction: How Much is Enough?
  • AutoAdmin “what-if” index analysis utility
  • What is a Self-Driving Database Management System?
  • https://www.microsoft.com/en-us/research/publication/self-tuning-database-systems-a-decade-of-progress/
  • Automatic Database Management System Tuning Through Large-scale Machine Learning
  • Query-based Workload Forecasting for Self-Driving Database Management Systems
  • The TSA Method
  • https://blog.langchain.dev/langchain-chat/
  • https://github.com/hwchase17/langchain
  • REAC T: SYNERGIZING REASONING AND ACTING IN LANGUAGE MODELS
  • Evaluating the Text-to-SQL Capabilities of Large Language Models
  • SQL-PALM: IMPROVED LARGE LANGUAGE MODEL ADAPTATION FOR TEXT-TO-SQL

作者:裕鋒

來源:微信公衆号:美團技術團隊

出處:https://mp.weixin.qq.com/s/bJMEhDv89OWD7sEOr3e0VA

繼續閱讀