最新互聯網大廠面試真題、Java程序員面試策略(面試前的準備、面試中的技巧)請移步GitHubmysql
在一主一備的雙 M 架構裏,主備切換隻須要把客戶端流量切到備庫;而在一主多從架構裏,主備切換除了要把客戶端流量切到備庫外,還須要把從庫接到新主庫上。git
主備切換有兩種場景,一種是主動切換,一種是被動切換。而其中被動切換,每每是由於主庫出問題了,由 HA 系統發起的。程序員
這也就引出了咱們今天要討論的問題:怎麼判斷一個主庫出問題了?github
你必定會說,這很簡單啊,連上 MySQL,執行個 select 1 就行了。可是 select 1 成功返回了,就表示主庫沒問題嗎?面試
實際上,select 1 成功返回,只能說明這個庫的進程還在,並不能說明主庫沒問題。如今,咱們來看一下這個場景。sql
set global innodb_thread_concurrency=3; CREATE TABLE `t` ( `id` int(11) NOT NULL, `c` int(11) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB; insert into t values(1,1)
咱們設置 innodb_thread_concurrency 參數的目的是,控制 InnoDB 的併發線程上限。也就是說,一旦併發線程數達到這個值,InnoDB 在接收到新請求的時候,就會進入等待狀態,直到有線程退出。數據庫
這裏,我把 innodb_thread_concurrency 設置成 3,表示 InnoDB 只容許 3 個線程並行執行。而在咱們的例子中,前三個 session 中的 sleep(100),使得這三個語句都處於「執行」狀態,以此來模擬大查詢。服務器
你看到了, session D 裏面,select 1 是能執行成功的,可是查詢表 t 的語句會被堵住。也就是說,若是這時候咱們用 select 1 來檢測實例是否正常的話,是檢測不出問題的。session
在 InnoDB 中,innodb_thread_concurrency 這個參數的默認值是 0,表示不限制併發線程數量。可是,不限制併發線程數確定是不行的。由於,一個機器的 CPU 核數有限,線程全衝進來,上下文切換的成本就會過高。架構
因此,一般狀況下,咱們建議把 innodb_thread_concurrency 設置爲 64~128 之間的值。這時,你必定會有疑問,併發線程上限數設置爲 128 夠幹啥,線上的併發鏈接數動不動就上千了。
產生這個疑問的緣由,是搞混了併發鏈接和併發查詢。
併發鏈接和併發查詢,並非同一個概念。你在 show processlist 的結果裏,看到的幾千個鏈接,指的就是併發鏈接。而「當前正在執行」的語句,纔是咱們所說的併發查詢。
併發鏈接數達到幾千個影響並不大,就是多佔一些內存而已。咱們應該關注的是併發查詢,由於併發查詢過高才是 CPU 殺手。這也是爲何咱們須要設置innodb_thread_concurrency 參數的緣由。
而後,你可能還會想起我以前講到的熱點更新和死鎖檢測的時候,若是把innodb_thread_concurrency 設置爲 128 的話,那麼出現同一行熱點更新的問題時,是否是很快就把 128 消耗完了,這樣整個系統是否是就掛了呢?
實際上,在線程進入鎖等待之後,併發線程的計數會減一,也就是說等行鎖(也包括間隙鎖)的線程是不算在 128 裏面的。
MySQL 這樣設計是很是有意義的。由於,進入鎖等待的線程已經不吃 CPU 了;更重要的是,必須這麼設計,才能避免整個系統鎖死。
爲何呢?假設處於鎖等待的線程也佔併發線程的計數,你能夠設想一下這個場景:
這時候 InnoDB 不能響應任何請求,整個系統被鎖死。並且,因爲全部線程都處於等待狀態,此時佔用的 CPU 倒是 0,而這明顯不合理。因此,咱們說 InnoDB 在設計時,遇到進程進入鎖等待的狀況時,將併發線程的計數減 1 的設計,是合理並且是必要的。
雖說等鎖的線程不算在併發線程計數裏,但若是它在真正地執行查詢,就好比咱們上面例子中前三個事務中的 select sleep(100) from t,仍是要算進併發線程的計數的。
在這個例子中,同時在執行的語句超過了設置的 innodb_thread_concurrency 的值,這時候系統其實已經不行了,可是經過 select 1 來檢測系統,會認爲系統仍是正常的。
所以,咱們使用 select 1 的判斷邏輯要修改一下。
爲了可以檢測 InnoDB 併發線程數過多致使的系統不可用狀況,咱們須要找一個訪問InnoDB 的場景。通常的作法是,在系統庫(mysql 庫)裏建立一個表,好比命名爲health_check,裏面只放一行數據,而後按期執行:
mysql> select * from mysql.health_check;
使用這個方法,咱們能夠檢測出因爲併發線程過多致使的數據庫不可用的狀況。
可是,咱們立刻還會碰到下一個問題,即:空間滿了之後,這種方法又會變得很差使。
咱們知道,更新事務要寫 binlog,而一旦 binlog 所在磁盤的空間佔用率達到 100%,那麼全部的更新語句和事務提交的 commit 語句就都會被堵住。可是,系統這時候仍是能夠正常讀數據的。
所以,咱們仍是把這條監控語句再改進一下。接下來,咱們就看看把查詢語句改爲更新語句後的效果。
既然要更新,就要放個有意義的字段,常見作法是放一個 timestamp 字段,用來表示最後一次執行檢測的時間。這條更新語句相似於:
mysql> update mysql.health_check set t_modified=now();
節點可用性的檢測都應該包含主庫和備庫。若是用更新來檢測主庫的話,那麼備庫也要進行更新檢測。
但,備庫的檢測也是要寫 binlog 的。因爲咱們通常會把數據庫 A 和 B 的主備關係設計爲雙 M 結構,因此在備庫 B 上執行的檢測命令,也要發回給主庫 A。
可是,若是主庫 A 和備庫 B 都用相同的更新命令,就可能出現行衝突,也就是可能會致使主備同步中止。因此,如今看來 mysql.health_check 這個表就不能只有一行數據了。
爲了讓主備之間的更新不產生衝突,咱們能夠在 mysql.health_check 表上存入多行數據,並用 A、B 的 server_id 作主鍵。
mysql> CREATE TABLE `health_check` ( `id` int(11) NOT NULL, `t_modified` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`) ) ENGINE=InnoDB; /*檢測命令*/ insert into mysql.health_check(id, t_modified) values (@@server_id, now()) on duplicate
因爲 MySQL 規定了主庫和備庫的 server_id 必須不一樣(不然建立主備關係的時候就會報錯),這樣就能夠保證主、備庫各自的檢測命令不會發生衝突。
更新判斷是一個相對比較經常使用的方案了,不過依然存在一些問題。其中,「斷定慢」一直是讓 DBA 頭疼的問題。
你必定會疑惑,更新語句,若是失敗或者超時,就能夠發起主備切換了,爲何還會有斷定慢的問題呢?
其實,這裏涉及到的是服務器 IO 資源分配的問題。
首先,全部的檢測邏輯都須要一個超時時間 N。執行一條 update 語句,超過 N 秒後還不返回,就認爲系統不可用。
你能夠設想一個日誌盤的 IO 利用率已是 100% 的場景。這時候,整個系統響應很是慢,已經須要作主備切換了。
可是你要知道,IO 利用率 100% 表示系統的 IO 是在工做的,每一個請求都有機會得到 IO資源,執行本身的任務。而咱們的檢測使用的 update 命令,須要的資源不多,因此可能在
拿到 IO 資源的時候就能夠提交成功,而且在超時時間 N 秒未到達以前就返回給了檢測系統。
檢測系統一看,update 命令沒有超時,因而就獲得了「系統正常」的結論。
也就是說,這時候在業務系統上正常的 SQL 語句已經執行得很慢了,可是 DBA 上去一看,HA 系統還在正常工做,而且認爲主庫如今處於可用狀態。
之因此會出現這個現象,根本緣由是咱們上面說的全部方法,都是基於外部檢測的。外部檢測自然有一個問題,就是隨機性。
由於,外部檢測都須要定時輪詢,因此係統可能已經出問題了,可是卻須要等到下一個檢測發起執行語句的時候,咱們纔有可能發現問題。並且,若是你的運氣不夠好的話,可能第一次輪詢還不能發現,這就會致使切換慢的問題。
因此,接下來我要再和你介紹一種在 MySQL 內部發現數據庫問題的方法。
針對磁盤利用率這個問題,若是 MySQL 能夠告訴咱們,內部每一次 IO 請求的時間,那咱們判斷數據庫是否出問題的方法就可靠得多了。
其實,MySQL 5.6 版本之後提供的 performance_schema 庫,就在file_summary_by_event_name 表裏統計了每次 IO 請求的時間。
file_summary_by_event_name 表裏有不少行數據,咱們先來看看event_name='wait/io/file/innodb/innodb_log_file’這一行。
圖中這一行表示統計的是 redo log 的寫入時間,第一列 EVENT_NAME 表示統計的類型。
接下來的三組數據,顯示的是 redo log 操做的時間統計。
第一組五列,是全部 IO 類型的統計。其中,COUNT_STAR 是全部 IO 的總次數,接下來四列是具體的統計項, 單位是皮秒;前綴 SUM、MIN、AVG、MAX,顧名思義指的就是總和、最小值、平均值和最大值。
第二組六列,是讀操做的統計。最後一列 SUM_NUMBER_OF_BYTES_READ 統計的是,總共從 redo log 裏讀了多少個字節。
第三組六列,統計的是寫操做。
最後的第四組數據,是對其餘類型數據的統計。在 redo log 裏,你能夠認爲它們就是對fsync 的統計。
在 performance_schema 庫的 file_summary_by_event_name 表裏,binlog 對應的是event_name = "wait/io/file/sql/binlog"這一行。各個字段的統計邏輯,與 redo log 的各個字段徹底相同。這裏,我就再也不贅述了。
由於咱們每一次操做數據庫,performance_schema 都須要額外地統計這些信息,因此咱們打開這個統計功能是有性能損耗的。
個人測試結果是,若是打開全部的 performance_schema 項,性能大概會降低 10% 左右。因此,我建議你只打開本身須要的項進行統計。你能夠經過下面的方法打開或者關閉某個具體項的統計。
若是要打開 redo log 的時間監控,你能夠執行這個語句:
mysql> update setup_instruments set ENABLED='YES', Timed='YES' where name like '%wait/io
假設,如今你已經開啓了 redo log 和 binlog 這兩個統計信息,那要怎麼把這個信息用在實例狀態診斷上呢?
很簡單,你能夠經過 MAX_TIMER 的值來判斷數據庫是否出問題了。好比,你能夠設定閾值,單次 IO 請求時間超過 200 毫秒屬於異常,而後使用相似下面這條語句做爲檢測邏輯。
mysql> select event_name,MAX_TIMER_WAIT FROM performance_schema.file_summary_by_event_n
發現異常後,取到你須要的信息,再經過下面這條語句:
mysql> truncate table performance_schema.file_summary_by_event_name;
把以前的統計信息清空。這樣若是後面的監控中,再次出現這個異常,就能夠加入監控累積值了。
今天,我和你介紹了檢測一個 MySQL 實例健康狀態的幾種方法,以及各類方法存在的問題和演進的邏輯。
你看完後可能會以爲,select 1 這樣的方法是否是已經被淘汰了呢,但實際上使用很是普遍的 MHA(Master High Availability),默認使用的就是這個方法。
MHA 中的另外一個可選方法是隻作鏈接,就是 「若是鏈接成功就認爲主庫沒問題」。不過據我所知,選擇這個方法的不多。
其實,每一個改進的方案,都會增長額外損耗,並不能用「對錯」作直接判斷,須要你根據業務實際狀況去作權衡。
我我的比較傾向的方案,是優先考慮 update 系統表,而後再配合增長檢測performance_schema 的信息。