第29節(正文免費部分):深入理解MySQL主從原理

gaopengtttt發表於2019-07-14

本文是我《深入理解MySQL主從原理》 系列的試看章節,具體可以點選:ttps://j.youzan.com/yEY_Xi


最後我想簡單說一下我的MySQL除錯環境的搭建,但是在此之前不得不簡單說一下什麼是執行緒,因為如果不解釋一下什麼是執行緒,簡單的除錯可能都會有阻礙,同時瞭解執行緒對我們普通DBA診斷效能問題也有極大的幫助。但是詳細解釋執行緒已經超出了我的能力範圍也超出了本系列討論的範圍,具體我推薦給大家兩本書:

  • 《POSIX多執行緒程式設計》
  • 《Linux UNIX系統程式設計手冊》 第29到32章

第一本書很老了,但是我覺得還可以,如果有興趣可以參考一下。

一、執行緒簡介

我們知道MySQLD是一個單程式多執行緒的使用者程式,因此我們有必要了解一下什麼執行緒。實際上MySQL中的執行緒都是POSIX執行緒,比如我們的會話執行緒、DUMP執行緒、IO執行緒以及其他一些Innodb執行緒都是POSIX執行緒。

程式實際上就是執行中的程式,一個程式中可以包含多個執行緒也可以只包含一個執行緒。在Linux中執行緒也叫輕量級程式(light-weight process)簡稱為LWP,程式的第一個執行緒通常稱為主控執行緒。程式是記憶體分配的最小單位,執行緒是CPU排程的最小單位,也就是說如果CPU有足夠多核,那麼多個執行緒可以達到並行處理的效果,核心直接排程執行緒。下面是我學習Linux執行緒的時候看到的一張我認為比較好理解的圖,我重新畫了一下放在下面供大家參考(圖29-1,高清原圖包含在文末原圖中):
1.jpg

一個程式內部的所有執行緒都擁有相同的程式碼程式、堆、全域性變數、共享庫等,但是每個執行緒擁有自己獨立棧空間和暫存器,他們共享程式的虛擬記憶體地址空間。下面我們假定是32位作業系統下的MySQLD程式,它的程式虛擬記憶體地址示意圖如下,實際上這個圖也是參考《Linux UNIX系統程式設計手冊》畫的(圖29-2,高清原圖包含在文末原圖中):

2.jpg

我們發現執行緒的堆記憶體和全域性變數是共享的,因此執行緒之間資料共享很輕鬆,但是要控制好這些共享記憶體就需要引入我們的執行緒同步技術,比如我們常說的Mutex。

如果想了解執行緒到底共享了哪些資源,執行緒和程式到底各有什麼優勢和劣勢,可執行參考上面我給出的書籍。

二、PID、LWP ID、Thread TID

如果要進行除錯就需要了解這三種ID,其中PID和LWP ID是比較重要,因為不管是除錯和運維都會遇到它們,而Thread TID不做多執行緒開發一般很少用到,下面是我對它們的總結:

  • PID:核心分配,用於識別各個程式的ID。這個應該是大家最熟悉的。
  • LWP ID:核心分配,用於識別各個執行緒的ID,它就像是執行緒是‘PID’一樣。同一個程式下的所有執行緒有相同的PID,但是LWP ID卻不一樣,主控執行緒的LWP ID就是程式PID。
  • Thread TID:程式內部用於識別各個執行緒的內部ID,這個ID用得不多。

下面我寫了一個簡單的C測試程式僅僅用於觀察這些ID,它是通過主控執行緒再建立一個執行緒,也就是說這個程式包含了兩個執行緒。它們分別列印自己的PID、LWP ID、Thread TID,然後做一個迴圈自加操作引起高CPU消耗現象便於觀察。然後我們使用Linux的top -H和ps -eLlf命令分別進行觀察。

  1. 程式輸出

下面是程式的輸出:


# ./gaopengtest 
main thread: pid 13188 tid 2010470144 lwp 13188
new thread:  pid 13188 tid 2010461952 lwp 13189

我們可以看到兩個執行緒的PID都是13188,但是主控執行緒的LWP ID是13188,新建立的一個執行緒LWP ID是13189。然後就是Thread TID了,不過多解釋。

  1. top -H 觀察如下:

我們可以看到這兩個執行緒都是高耗CPU的執行緒,CPU已經處於飽和狀態。這裡我們看到top -H命令的輸出中‘PID’就是LWP ID。這裡我們還能看它們的記憶體資訊完全一致,記憶體資訊實際上是整個程式的記憶體資訊,因為程式是記憶體分配的最小單位嘛。

image.png

注意如果這個時候檢視這個程式佔用的%cpu就超過了100%,接近200%,如下:

image.png

  1. ps -eLlf 觀察如下:

我們可以看到這裡包含了PID和LWP ID,不過多解釋了,大家可以自己去試試。

image.png

三、如何將MySQL的執行緒和LWP ID進行對應

在5.7中我們已經可以通過MySQL語句和LWP ID進行對應了,這讓效能診斷變得更加便捷。比如我上面的列子如果兩個高耗CPU的執行緒是MySQL的執行緒那麼我們就拿到了執行緒的LWP ID,然後可以通過語句找到這兩個執行緒到底是MySQL的什麼執行緒。語句如下:


mysql> select a.thd_id,b.THREAD_OS_ID,a.user 
,b.TYPE from  sys.processlist 
a,performance_schema.threads  b where b.thread_id=a.thd_id;
+--------+--------------+---------------------------+---------+------------
| thd_id | THREAD_OS_ID | user                      | conn_id | TYPE       
+--------+--------------+---------------------------+---------+------------
|      1 |        16370 | sql/main                  |    NULL | BACKGROUND 
|      2 |        17202 | sql/thread_timer_notifier |    NULL | BACKGROUND 
|      3 |        17207 | innodb/io_ibuf_thread     |    NULL | BACKGROUND 
|      4 |        17208 | innodb/io_log_thread      |    NULL | BACKGROUND 
|      5 |        17209 | innodb/io_read_thread     |    NULL | BACKGROUND 
|      6 |        17210 | innodb/io_read_thread     |    NULL | BACKGROUND 
|      7 |        17211 | innodb/io_read_thread     |    NULL | BACKGROUND 
|      8 |        17212 | innodb/io_read_thread     |    NULL | BACKGROUND 
|      9 |        17213 | innodb/io_read_thread     |    NULL | BACKGROUND 
|     10 |        17214 | innodb/io_read_thread     |    NULL | BACKGROUND 
|     11 |        17215 | innodb/io_read_thread     |    NULL | BACKGROUND 
|     12 |        17216 | innodb/io_read_thread     |    NULL | BACKGROUND 
|     13 |        17217 | innodb/io_write_thread    |    NULL | BACKGROUND 
|     14 |        17218 | innodb/io_write_thread    |    NULL | BACKGROUND 
|     15 |        17219 | innodb/io_write_thread    |    NULL | BACKGROUND 
|     16 |        17220 | innodb/io_write_thread    |    NULL | BACKGROUND 
|     17 |        17221 | innodb/io_write_thread    |    NULL | BACKGROUND 
......

這裡的THREAD_OS_ID就是執行緒的LWP ID。然後我們使用剛才的ps -eLlf命令再看一下,如下:

image.png

我們可以發現他們是可以對應上的,這個最好自己實際試試就知道了。

四、除錯環境搭建

除錯環境的搭建我認為不管使用什麼方法只要能夠起到除錯的作用就可以了。這裡介紹一下我的方法。我是在Linux下面直接使用gdb除錯的,我覺得這個方法搭建非常簡單並且很奏效,基本上只要會原始碼安裝就能完成除錯環境的搭建。下面來看看步驟:

1. 第一步下載MySQL原始碼包,解壓。

我特意重新下載了官方版的5.7.26原始碼。

2. 使用原始碼安裝的方法安裝MySQL,注意需要開啟debug選項

下面是我使用的選項:

cmake -DCMAKE_INSTALL_PREFIX=/root/sf/mysql3312/ -DMYSQL_DATADIR=/root/sf/mysql3312/data/ -DSYSCONFDIR=/root/sf/mysql3312/ -DWITH_INNOBASE_STORAGE_ENGINE=1 -DWITH_ARCHIVE_STORAGE_ENGINE=1 -DWITH_BLACKHOLE_STORAGE_ENGINE=1 -DWITH_FEDERATED_STORAGE_ENGINE=1 -DWITH_PARTITION_STORAGE_ENGINE=1 -DMYSQL_UNIX_ADDR=/root/sf/mysql3312/mysql3312.sock -DMYSQL_TCP_PORT=3306 -DENABLED_LOCAL_INFILE=1 -DEXTRA_CHARSETS=all -DDEFAULT_CHARSET=utf8 -DDEFAULT_COLLATION=utf8_general_ci -DMYSQL_USER=mysql -DWITH_BINLOG_PREALLOC=ON -DWITH_BOOST=/root/sf/mysql-5.7.26/boost/boost_1_59_0 -DWITH_DEBUG=1

注意最後的-DWITH_DEBUG=1必須開啟。

3. make&&make install

編譯和安裝。

4. 準備引數檔案和初始化MySQL資料庫,並且確保能成功啟動

這一步自己處理,注意許可權。我的環境啟動成功瞭如下:


[root@gp1 support-files]# ./mysql.server start
Starting MySQL....... SUCCESS! 
[root@gp1 support-files]# ./mysql.server stop
Shutting down MySQL.. SUCCESS!

5. 準備gdb命令檔案

如下是我準備的命令檔案:


[root@gp1 ~]# more debug.file 
break main
run --defaults-file=/root/sf/mysql3312/my.cnf --user=mysql --gdb

第一行是在main函式處打一個斷點。第二行就是gdb呼叫MySQLD的時候,MySQLD加什麼引數了,注意run不要寫掉了。

6.使用gdb啟動MySQL

使用如下命令啟動除錯環境:


gdb -x /root/debug.file /root/sf/mysql3312/bin/mysqld

下面就是我啟動除錯環境成功的記錄:


# gdb -x /root/debug.file /root/sf/mysql3312/bin/mysqld
GNU gdb (GDB) Red Hat Enterprise Linux (7.2-92.el6)
Copyright (C) 2010 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-redhat-linux-gnu".
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>...
Reading symbols from /root/sf/mysql3312/bin/mysqld...done.
Breakpoint 1 at 0xec7c53: file /root/sf/mysql-5.7.26/sql/main.cc, line 25.
[Thread debugging using libthread_db enabled]
Breakpoint 1, main (argc=5, argv=0x7fffffffe3b8) at /root/sf/mysql-5.7.26/sql/main.cc:25
25        return mysqld_main(argc, argv);
Missing separate debuginfos, use: debuginfo-install glibc-2.12-1.212.el6.x86_64 libaio-0.3.107-10.el6.x86_64 libgcc-4.4.7-18.el6.x86_64 libstdc++-4.4.7-18.el6.x86_64 nss-softokn-freebl-3.14.3-23.3.el6_8.x86_64
(gdb) c
Continuing.
[New Thread 0x7fffee883700 (LWP 29375)]
[New Thread 0x7fff9a9f3700 (LWP 29376)]
[New Thread 0x7fff99ff2700 (LWP 29377)]
[New Thread 0x7fff995f1700 (LWP 29378)]
[New Thread 0x7fff98bf0700 (LWP 29379)]
[New Thread 0x7fff981ef700 (LWP 29380)]
[New Thread 0x7fff977ee700 (LWP 29381)]
[New Thread 0x7fff96ded700 (LWP 29382)]
[New Thread 0x7fff963ec700 (LWP 29383)]
.....

注意到了這裡的LWP ID了嗎,前面我們已經討論過了。這個時候MySQL客戶端程式已經可以連線MySQLD瞭如下:


# /root/sf/mysql3312/bin/mysql -S'/root/sf/mysql3312/mysql3312.sock'
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 2
Server version: 5.7.26-debug-log Source distribution
Copyright (c) 2000, 2019, Oracle and/or its affiliates. All rights reserved.
Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.
Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
mysql> select version() ;
+------------------+
| version()        |
+------------------+
| 5.7.26-debug-log |
+------------------+
1 row in set (0.00 sec)

好了到這裡基本的除錯環境就搭建起來了,我們可以發現很簡單。之後我們就可以進行斷點除錯了。我常用的gdb命令包含:

  • info threads:檢視全部執行緒
  • thread n:指定某個執行緒
  • bt:檢視某個執行緒棧幀
  • b:設定斷點
  • c:繼續執行
  • s:執行一行程式碼,如果程式碼函式呼叫,則進入函式
  • n:執行一行程式碼,函式呼叫不進入
  • p:列印某個變數值
  • list:列印程式碼的文字資訊

當然gdb還有很多命令,可自行參考其他資料。

六、使用除錯環境證明問題的一個列子

這裡我們就用一個例子來看看除錯環境的使用方法。我們前面第15節說過binlog cache是在order commit的flush階段才寫入到binary log的,呼叫的是函式binlog_cache_data::flush。好了我們可以將斷點打到這個函式如下:


(gdb) b binlog_cache_data::flush
Breakpoint 2 at 0x1846333: file /root/sf/mysql-5.7.26/sql/binlog.cc, line 1674.

然後我們在MySQL客戶端執行一個事物如下,並且提交:


mysql> begin;
Query OK, 0 rows affected (0.00 sec)
mysql> insert into gpdebug values(1);
Query OK, 1 row affected (0.03 sec)
mysql> commit;

commit的時候已經卡主了,斷點觸發如下:


Breakpoint 2, binlog_cache_data::flush (this=0x7fff3c00df20...)
    at /root/sf/mysql-5.7.26/sql/binlog.cc:1674
1674      DBUG_ENTER("binlog_cache_data::flush");

我們使用bt命令檢視棧幀發現如下:


#0  binlog_cache_data::flush
at /root/sf/mysql-5.7.26/sql/binlog.cc:1674
#1  0x0000000001861b41 in binlog_cache_mngr::flush 
at /root/sf/mysql-5.7.26/sql/binlog.cc:967
#2  0x00000000018574ce in MYSQL_BIN_LOG::flush_thread_caches 
at /root/sf/mysql-5.7.26/sql/binlog.cc:8894
#3  0x0000000001857712 in MYSQL_BIN_LOG::process_flush_stage_queue 
at /root/sf/mysql-5.7.26/sql/binlog.cc:8957
#4  0x0000000001858d19 in MYSQL_BIN_LOG::ordered_commit 
at /root/sf/mysql-5.7.26/sql/binlog.cc:9595
#5  0x00000000018573b4 in MYSQL_BIN_LOG::commit
at /root/sf/mysql-5.7.26/sql/binlog.cc:8851
#6  0x0000000000f58de9 in ha_commit_trans 
at /root/sf/mysql-5.7.26/sql/handler.cc:1799
#7  0x000000000169e02b in trans_commit 
at /root/sf/mysql-5.7.26/sql/transaction.cc:239
......

好了看到這個棧幀,就能證明我們的說法了,如果想深入學習程式碼就可以從這個棧幀出發進行學習。但是值得注意的是,這是建立在知道函式介面功能的前提下的,如果我們不知道寫入binary log會呼叫binlog_cache_data::flush函式那麼除錯也就不好進行了,我就經常遇到這樣的困境。因此整個系列我給出了很多這樣的介面,供有興趣的朋友除錯和測試。


第29節結束

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/7728585/viewspace-2650459/,如需轉載,請註明出處,否則將追究法律責任。

相關文章