游戲服務端開發中的MySQL進階知識(三)--慢查詢

本系列文章主要是本人在游戲服務端開發過程中,遇到的一些不那么為人熟知但我又覺得比較重要的MySQL知識的介紹。希望里面淺薄的文字能為了提供一點點的幫助。

慢查詢

從我有限的從業經驗來看,大多數數據庫導致的游戲服務器問題,十有八九慢查詢。比如:SQL寫的亂七八糟導致的慢查詢、表數據太多導致的慢查詢、沒有使用索引導致的慢查詢、突然大并發量導致的慢查詢等等。數據庫一出現以上這些問題,開發團隊免不了要背大鍋。所以我認為數據庫慢查詢應該成為每個服務器開發人員必知必會的知識點。

慢查詢事故分享:

這里我分享一下我參與的第一次慢查詢定位和優化經歷,這是一次非常慘痛的經歷:

當時我所在的項目組上線了一個零點秒殺的活動,秒殺活動開啟的第一個晚上有大量玩家投訴卡死在活動界面。我們當時按直覺判斷肯定是太多人秒殺導致游戲服務器壓力過大處理不過來,然后就決定發公告宣布延遲活動和重啟游戲服務器。重啟服務器之后,玩家再次進行秒殺問題還是如此。這時有個同事說,壓力應該主要在MySQL服務器上應該重啟MySQL服務器。然后我們又又發公告宣布延遲活動 + 重啟游戲服務器 + 重啟MySQL服務器。可能是因為大部分玩家都放棄的原因吧,這次問題終于解決。

后期我們進行了復盤發現問題是:游戲服務器后臺會在零點執行一個定時統計任務,其中包含許多復雜的SQL語句(各種join、全表查詢等等)。剛好這些SQL所操作的表又和秒殺活動需要操作的表完全重合,最終導致數據庫大量慢查詢、MySQL服務器假死、線上玩家卡在秒殺界面。而出問題那會項目組成員甚至不知道慢查詢是個什么東西,只是單純認為是并發量太大導致服務器壓力太大負載不下,然后在沒有準確定位根本原因的情況下就天真地用重啟游戲服務器和MySQL服務器來解決問題。下圖說明了但是MySQL服務器面臨的兩方面的壓力:

image.png

第一次我們只重啟游戲服務器,非但沒有減輕MySQL服務器的壓力,反而導致其壓力變得更大了。這是因為:

  1. 游戲服務器重啟有大量在線玩家被強制下線,這些玩家的數據需要刷進數據庫,加重服務器壓力(本來上面那些慢查詢還沒執行完,現在又來新的了);
  2. 游戲服務器重啟之后,零點執行的那個定時統計任務會自主判斷是否要繼續執行(由于上一次重啟游戲服務器導致今天的任務中斷,所以判斷結果是還要執行一遍),這樣對于MySQL而言就算是同時執行兩個這個定時統計任務了。

在知道慢查詢日志這個東西之后,我們還嘗試去線上找這個日志來看看,但由于數據庫默認不開啟慢查詢日志的打印,結果可想而知。不過我們還是模擬了當時線上的情況重新測試一遍(這次開了慢查詢),表現和當時線上的狀況一致。最終才將這個問題全部復盤。

有上面這個案例打底之后,接下就開始慢查詢的介紹

慢查詢是指那些執行時間過長且涉及行數過多的語句(一般超過配置的慢查詢時間和涉及行數才算)。慢查詢并不是一定是SELECT語句,其實所有的DML類型的SQL(INSERTUPDATEREPLACEDELETE)都可能是慢查詢。根據官網的說明,慢查詢日志的監聽和打印可能會是一個耗時任務,所以MySQL服務器是默認不開啟的慢查詢。

慢查詢配置(官方文檔):

參數名 作用 默認值 備注
slow_query_log 是否開啟slow log慢日志記錄 OFF 默認關閉慢查詢功能
slow_query_log_file slow log日志的路徑 host_name-slow.log 注意:MySQL服務器要對指定的目錄有寫權限
long_query_time 當SQL語句執行時間超過該配置時間則算是慢查詢,如果有開啟慢查詢日志的話就會將慢查詢SQL記錄到慢查詢日志中。 10 該值的取值范圍是0-10,最小精度單位是微秒(比如可以配成0.001),如果該值配成0那就是所有SQL都是慢查詢。
min_examined_row_limit 當一個SQL語句掃描行數小于該值時,不會計入到慢查詢中 0 可以屏蔽掉一些偶發性干擾的慢查詢(比如一句十分簡單且用了索引的select語句也會因為抖動或其他因素變為慢查詢,而實際上是沒問題的)
log_queries_not_using_indexes 不走索引的查詢是否被記錄 OFF 不走索引的查詢是否被記錄,即默認是不走索引就不記錄慢查詢日志
log_throttle_queries_not_using_indexes 不走索引被記錄的語句條數閾值 0 當一分鐘不走索引慢查詢記錄數據超過該值就不再記錄,每分鐘清0。
log_slow_admin_statements 對服務器管理語句是否進行記錄 OFF 管理語句有:ALTER TABLE, ANALYZE TABLE, CHECK TABLE等等,默認不記錄
log_output 指定慢查詢日志的打印目的地 FILE 該值可選項有FILE,TABLE,NONE(低版本可能沒有TABLE這個選項),可以是一個也可以包含多個(多個的話就用逗號分隔,如:TABLE, FILE)。 選擇TABLE記錄到mysql庫中的 slow_log表中; 選擇FILE記錄到日志文件;NONE禁用日志記錄,如果存在NONE則其他均無效。

以上可以通過SHOW VARIABLES LIKE '%***%'這種形式進行查詢,并且可以通過SET GLOBAL ***=?來進行動態修改。這些變量均是全局變量,對MySQL所有庫所有用戶生效。
查詢配置示例:

image.png

我在上面介紹information_schema庫的時候就有說過,能用SHOW VARIABLES LIKE '%***%'查詢到的東西基本上也可以在information_schema庫查到。而且上面這些配置都是全局的,所以通過information_schema庫的GLOBAL_VARIABLES表也能查到(某些版本的MySQL可能是在performance_schema庫中,可以通過:SELECT table_schema FROM information_schema.TABLES WHERE table_name = 'GLOBAL_VARIABLES'來查詢到底在哪個庫),如下圖:

image.png

image.png

慢查詢的判斷流程:

這里想強調的是:MySQL服務器是否打印慢查詢日志,不是只通過SQL語句的執行速度來判斷的,還和上面這些參數有關(源碼請戳)。這里上一張判斷流程圖說明一下:

image.png

判斷是否要打印慢查詢日志的方法在log.cc中:
image.png

日志格式(官方文檔):

我把long_query_time設置為0(SET GLOBAL long_query_time=0),打印格式設置為FILE和TABLE兩種模式(SET GLOBAL log_output='FILE,TABLE')。下面截圖分別是慢查詢日志和慢查詢表的數據:

慢查詢日志.png

慢查詢表.png

這里說明一下幾個關鍵的字段:

  • Query_time:該SQL語句執行時長(秒),精確到微秒;
  • Lock_time:該SQL持有鎖的時長(秒),精確到微秒。單純的select語句不會持有鎖(這時該值為0);
  • Rows_sent:發送給客戶端的數據行數
  • Rows_examined:Server層掃描的數據行數

注意不管是慢查詢日志還是慢查詢表,記錄的信息都是一條一條的慢查詢SQL語句。這樣的形式直接進行分析會非常麻煩(比如想知道某一條慢查詢具體的耗時、鎖持有時間,或者對某一張表的慢查詢分析)。這里介紹一下業界比較常用的慢查詢日志分析工具:

  • mysqldumpslow:官方慢查詢分析工具,會分析指定的慢查詢日志并統計出同一慢查詢SQL出現的次數、總耗時等信息(如下圖),操作十分簡單(Windows的需要安裝perl才行,使用說明)。
image.png
  • pt-query-digest:業界常用的慢查詢日志分析工具。對分表分庫的情況有很好的支持;定制化功能很多;并且該工具不止能分析慢查詢日志,還能分析一般SQL日志(general_log)和binlog日志等。但是使用門檻比較高,如果應用在線上環境需要一定的上手時間。
  • Anemometer:圖形化界面,需要在pt-query-digest的基礎上才能工作。搭建繁瑣

業界對慢查詢的通用的處理流程:

  • 如果是使用云服務器,可以在MySQL云服務器上設置慢查詢日志分析and告警,做到異常就郵件、釘釘甚至是電話告警,云服務廠商都有這個功能(阿里慢查詢告警設置);
  • 每周甚至是每天匯總一份慢查詢的統計自動發郵件給DBA、運維和開發同學;
  • 對有問題的慢查詢做分析(有一些慢查詢是偶發性的,可以忽略),然后做歸零處理。

如何避免和解決慢查詢:

如何避免:

當數據庫壓力過大時(CPU和I/O),更容易出現慢查詢。本來一些正常的操作也會因為阻塞而變成慢查詢。有效降低數據庫壓力就是一個標本兼治的方案,慢查詢的避免倒是順帶的。降低數據庫壓力有很多種,這里介紹下我認為比較有效的幾種給大家參考一下:

  • 使用緩存降低數據庫壓力:對于游戲服務器來說,更新和讀取數據的頻率是非常驚人的(很多炸服的游戲就是栽在這上面)。很多時候玩家一個操作可能涉及大量數據的更新或者是查詢(比如:玩家登錄需要加載大量玩家數據;戰斗結束需要發放獎品和更新戰斗記錄等等)。如果這些操作都直接落到數據庫上面,那么數據庫的壓力將十分巨大。
    通過引用緩存中間件(MongoDB、Redis)和游戲服務器進程緩存來合并玩家操作產生的數據庫SQL、延遲回寫數據庫(延遲回寫這個不同項目可能有不同要求),可以很大程度上改善這種情況。并且引入緩存之后,玩家讀取相關數據也會命中緩存而不是直接打到數據庫,這一點對降低服務器壓力幫助是非常有效的。如果不想引入緩存中間件,那直接使用進程緩存也是可以的,不過要注意內存空間和釋放的問題。許多大廠商對他們的產品(自研或代理)在擊穿數據庫這方面(即服務器繞過緩存直接訪問數據庫加載玩家數據)都有嚴格的要求和限制。他們都會極力避免代碼中出現直接操作數據庫的行為,最最重要的原因還是為了防止流量洪峰對數據庫的直接沖擊(即使是MySQL自己也有內置的采用LRU策略的buff pool內存緩存)。
    注意!雖然引入緩存有好處,但是也會有一定的風險。增加緩存不僅會增加系統復雜度,同時也意味著數據冗余,這很可能導致數據不一致。事實也是如此,大部分的回檔、數據錯亂等問題都是緩存部分代碼有bug導致的,所以決定引入緩存之前要預留好充足的測試和壓測的時間。

  • 多服務器分攤壓力:上面關于緩存這一點更多是從客戶端(相對于數據庫)來解決問題,同時我們也可以通過增強數據庫服務器性能這一點來實現。比如使用主從模式下的讀寫分離、分庫分表等方法來將壓力分攤到多臺數據庫服務器上。

  • 避免在線上跑統計:一般來說,游戲業務功能(即玩家直接交互的玩法)不會有過于復雜的SQL出現,無非就是把一個玩家的數據select出來、update一些之類的而且都會帶上where條件,join這些操作基本沒有。
    復雜SQL主要出現在一些統計work(比如:統計玩家付費和在線時長的關系)上。這些統計工作一般都會在夜深人靜的時候偷偷在線上服務器跑,然后第二天把數據給到策劃。但是總有一些夜深人不靜的時候,比如上面我介紹的零點秒殺活動。一旦高并發+復雜SQL,出問題就是分分鐘的事。
    這里給的建議是——盡量不要在線上環境跑這些統計work,把這個工作放到冷備庫去做(當然這會有延遲),如果這一點做不到,那就盡量把一條大SQL拆分成多個簡單的SQL分批執行,還是做不到的話那我建議跑work的時間定在凌晨3、4點。

  • 寫SQL的時候使用explain分析一下:寫復雜SQL的時候使用explain分析SQL看看索引使用、復雜度等情況是非常必要的。根據分析結果和實際功能情況,該加索引的加索引,該拆分就拆分。如果不得不做一些復雜操作需要復雜的SQL,還是強烈建議將大SQL拆分成多個簡單的SQL分批執行。不要對自己寫SQL的本事盲目自信,要明確知道數據庫給你的反饋。

如何解決:

上面說了如何預防慢查詢,這里說下如果出現慢查詢怎么辦。

  • 偶發慢SQL:首先要說明一下,不一定所有的慢查詢都是異常能夠優化的。有些時候MySQL服務器內部的一些動作(比如臟頁落盤),會導致MySQL有抖動,這時是可能導致原本正常的SQL變成慢查詢。只要SQL不復雜且有用索引、這條SQL只在慢查詢中出現過一次并且MySQL當時的表現良好(比如當時的IO、CPU監控和慢查詢數量),基本就能確認是這種情況。這種情況可以不用理會。
  • SQL一直執行:這種情況一般是死鎖導致的,這里可以使用show processlist查看MySQL線程池的狀態和正在執行的SQL語句:
    image.png

    當然你也可以從information_schema庫里的PROCESSLIST表找到這些信息(有些版本在performance_schema庫中):
    image.png

    其中Time字段代表線程執行在某個狀態下的時長(這張表其他字段含義請戳)。當發現一條SQL異常準備,就可以通過kill命令和線程ID來kill掉這個線程,這樣有問題的命令將會停止執行,但是要注意客戶端是否有重試邏輯。注意如果是使用線程池方案的項目組,可以通過設置thread_pool_stall_limit參數來實現SQL語句最大執行時長的限制(該值僅對使用線程池方案的MySQL生效),至于線程池能否可以kill線程我還沒試過,有興趣的同學可以嘗試一下。
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,732評論 6 539
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,214評論 3 426
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,781評論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,588評論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,315評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,699評論 1 327
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,698評論 3 446
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,882評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,441評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,189評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,388評論 1 372
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,933評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,613評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,023評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,310評論 1 293
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,112評論 3 398
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,334評論 2 377

推薦閱讀更多精彩內容