做者:高鵬 文章末尾有他著做的《深刻理解MySQL主從原理 32講》,深刻透徹理解MySQL主從,GTID相關技術知識。html
本文節選自《深刻理解MySQL主從原理》第29節 注意:本文分爲正文和附件兩部分,都是圖片格式,若是正文有圖片不清晰能夠將附件的圖片保存到本地查看。mysql
我想簡單說一下個人 MySQL 調試環境的搭建,可是在此以前不得不簡單說一下什麼是線程,由於若是不解釋一下什麼是線程,簡單的調試可能都會有阻礙,同時瞭解線程對咱們普通DBA診斷性能問題也有極大的幫助。可是詳細解釋線程已經超出了個人能力範圍也超出了本系列討論的範圍。 具體我推薦給你們兩本書:linux
咱們知道 mysqld 是一個單進程多線程的用戶程序,所以咱們有必要了解一下什麼線程。實際上 MySQL 中的線程都是 POSIX 線程,好比咱們的會話線程、DUMP 線程、IO 線程以及其餘一些 InnoDB 線程都是 POSIX 線程。c++
進程實際上就是運行中的程序,一個進程中能夠包含多個線程,也能夠只包含一個線程。在 Linux 中線程也叫輕量級進程(light-weight process)簡稱爲 LWP,進程的第一個線程一般稱爲主控線程。進程是內存分配的最小單位,線程是 CPU 調度的最小單位,也就是說若是 CPU 有足夠多核,那麼多個線程能夠達到並行處理的效果,內核直接調度線程。下面是我學習 Linux 線程的時候看到的一張我認爲比較好理解的圖,我從新畫了一下放在下面供你們參考(圖29-1,高清原圖包含在文末原圖中):redis
一個進程內部的全部線程都擁有相同的代碼程序、堆、全局變量、共享庫等,可是每一個線程擁有本身獨立棧空間和寄存器,他們共享進程的虛擬內存地址空間。下面咱們假定是32位操做系統下的 mysqld 進程,它的進程虛擬內存地址示意圖以下,實際上這個圖也是參考《Linux UNIX 系統編程手冊》畫的(圖29-2,高清原圖包含在文末原圖中):sql
咱們發現線程的堆內存和全局變量是共享的,所以線程之間數據共享很輕鬆,可是要控制好這些共享內存就須要引入咱們的線程同步技術,好比咱們常說的 Mutex。數據庫
若是想了解線程到底共享了哪些資源,線程和進程到底各有什麼優點和劣勢,可執行參考上面我給出的書籍。編程
若是要進行調試就須要瞭解這三種 ID,其中 PID 和 LWP ID 是比較重要,由於不論是調試和運維都會遇到它們,而 Thread TID 不作多線程開發通常不多用到。 下面是我對它們的總結:多線程
下面我寫了一個簡單的 C 測試程序僅僅用於觀察這些 ID,它是經過主控線程再建立一個線程,也就是說這個進程包含了兩個線程。它們分別打印本身的 PID、LWP ID、Thread TID,而後作一個循環自加操做引發高 CPU 消耗現象便於觀察。而後咱們使用 Linux 的 top -H 和 ps -eLlf 命令分別進行觀察。運維
下面是程序的輸出:
# ./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 了,不過多解釋。
咱們能夠看到這兩個線程都是高耗 CPU 的線程,CPU 已經處於飽和狀態。這裏咱們看到 top -H 命令的輸出中‘PID’就是 LWP ID。這裏咱們還能看它們的內存信息徹底一致,內存信息其實是整個進程的內存信息,由於進程是內存分配的最小單位嘛。
注意若是這個時候查看這個進程佔用的 %cpu 就超過了 100%,接近 200%,以下:
咱們能夠看到這裏包含了 PID 和 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 命令再看一下,以下:
咱們能夠發現他們是能夠對應上的,這個最好本身實際試試就知道了。
調試環境的搭建我認爲無論使用什麼方法只要可以起到調試的做用就能夠了。這裏介紹一下個人方法。我是在 Linux 下面直接使用 gdb 調試的,我以爲這個方法搭建很是簡單而且很奏效,基本上只要會源碼安裝就能完成調試環境的搭建。下面來看看步驟:
我特地從新下載了官方版的 5.7.26 源碼。
下面是我使用的選項:
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 必須開啓。
編譯和安裝。
這一步本身處理,注意權限。個人環境啓動成功了以下:
[root@gp1 support-files]# ./mysql.server start Starting MySQL....... SUCCESS! [root@gp1 support-files]# ./mysql.server stop Shutting down MySQL.. SUCCESS!
以下是我準備的命令文件:
[root@gp1 ~]# more debug.file break main run --defaults-file=/root/sf/mysql3312/my.cnf --user=mysql --gdb
第一行是在 main 函數處打一個斷點。第二行就是 gdb 調用 mysqld 的時候,mysqld 加什麼參數了,注意 run 不要寫掉了。
使用以下命令啓動調試環境:
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 命令包含:
固然 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 函數那麼調試也就很差進行了,我就常常遇到這樣的困境。所以整個系列我給出了不少這樣的接口,供有興趣的朋友調試和測試。
最後推薦高鵬的專欄《深刻理解MySQL主從原理 32講》,想要透徹瞭解學習MySQL 主從原理的朋友不容錯過。