MYSQL 整體架構淺析
對於一個服務端開發來說 MYSQL 可能是他使用最熟悉的資料庫工具,然而熟練掌握 MYSQL 語句的拼寫和卓越的多條件查詢不代表出現效能問題的時候你知道該怎麼解決。致力於不當 SQL boby,我們從頭開始入門 MYSQL,講一些你可能不知道的 MYSQL。
1. 一條 SQL 之旅
現在有一條查詢使用者資訊表的 SQL :
select * from user where uid = 100001;
這條 SQL 是如何從你的應用程式到達 MYSQL 伺服器並執行,然後查到結果再帶給你的呢?要回答這個問題我們的看一下 MYSQL 的整體架構:
-
建立連線
首先客戶端與 MYSQL 伺服器建立連線這個就不用說,客戶端發起請求經過三次握手之後與伺服器建立 TCP 連線;伺服器收到請求之後對輸入的使用者名稱密碼做許可權校驗, 校驗通過之後後續的通訊就基於這個長連線進行傳輸。
一般來說一個 MYSQL 服務端是可以對應多個客戶端的,所以在服務端可能會有很多個客戶端連線同時保持,可以通過
show processlist;
命令來檢視當前服務端有多少個連線:mysql> show processlist; +---------+------+---------------------+-------------------+---------+-------+-------+------------------+ | Id | User | Host | db | Command | Time | State | Info | +---------+------+---------------------+-------------------+---------+-------+-------+------------------+ | 1753984 | test | 10.31.0.64:65264 | xxx | Sleep | 92 | | NULL | | 6348496 | test | 10.26.134.61:43080 | xxx | Sleep | 2 | | NULL | | 7201973 | test | 10.26.8.104:61642 | xxx | Sleep | 927 | | NULL | | 7201976 | test | 10.26.8.104:61650 | xxx | Sleep | 927 | | NULL | | 7414866 | test | 10.26.134.238:52010 | xxx | Sleep | 32 | | NULL | ...... ...... ...... +---------+------+---------------------+-------------------+---------+-------+-------+------------------+ 42 rows in set (0.00 sec)
上面有個
Time
引數:表示該連線已經多久沒有發生過資料傳輸。預設如果超過 8 小時沒有發生過資料傳輸服務端就會自動關閉該連線。可以通過wait_timeout
引數來設定超時時間。 -
查詢快取
MySQL 查詢快取是 MySQL 中比較獨特的一個快取區域,用來快取特定 Query 的整個結果集資訊,且共享給所有客戶端。為了提高完全相同的 Query 語句的響應速度,MySQL Server 會對查詢語句進行 Hash 計算後,把得到的 hash 值與 Query 查詢的結果集對應存放在Query Cache 中。當 MySQL Server 開啟 Query Cache 之後,MySQL Server 會對接收到的每一個 SELECT 語句通過特定的 Hash 演算法計算該 Query 的 Hash 值,然後通過該 hash 值到 Query Cache 中去匹配。
查詢快取相關的配置引數有如下:
mysql> show variables like '%query_cache%'; +------------------------------+---------+ | Variable_name | Value | +------------------------------+---------+ | have_query_cache | YES | --查詢快取是否可用 | query_cache_limit | 1048576 | --可快取具體查詢結果的最大值 | query_cache_min_res_unit | 4096 | --查詢快取分配的最小塊的大小(位元組) | query_cache_size | 599040 | --查詢快取的大小 | query_cache_type | ON | --是否支援查詢快取 | query_cache_wlock_invalidate | OFF | --控制當有寫鎖加在表上的時候,是否先讓該表相關的 Query Cache失效 +------------------------------+---------+ 6 rows in set (0.02 sec)
開啟快取
mysql> set global query_cache_size = 600000; --設定快取記憶體大小 mysql> set global query_cache_type = ON; --開啟查詢快取
關閉快取
mysql> set global query_cache_size = 0; --設定快取記憶體大小為0, 即初始化是不分配快取記憶體 mysql> set global query_cache_type = OFF; --關閉查詢快取
-
分析器
如果查詢快取未開啟或者為命中的情況則會走正常的查詢流程,第一步就是 sql 解析器,作用是將整個查詢語句變為 MYSQL 伺服器能理解的語言。
-
詞法分析:將整個查詢分解為多個元素;
-
語法分析:尋找 sql 語法規則產生一個序列並執行這些程式碼;
-
已經前兩步之後會產生一個解析樹,提供給優化器使用。
-
-
查詢優化器
優化器工作主要包括兩個部分:
- 邏輯優化;
- 物理優化。
邏輯優化階段:大牛們為了我們這些渣渣程式設計師操碎了心,怕你寫的 sql 不好查詢慢然後上網發帖 “MYSQL 垃圾”,“再也不用 MYSQL,我準備自己寫個資料庫”,於是默默在在後臺給你整了個 sql 語句優化。優化內容主要有:
一定能帶來優化效果的:
- 連線的消除(外連線,巢狀連線)
- 語義優化
- 冗餘操作剪枝
可能會帶來效能的提升但是需要根據代價進行選擇
- 借用索引來優化分組、排序等操作
- 合併分組
- 連線查詢條件下推
- 公式條件的提取
- 謂詞上推
物理優化階段: 邏輯優化主要是針對語法規則和語義的規整、合併、裁剪,那麼到了物理優化階段就需要拿著 MYSQL 認為是比較完美的 sql 去查詢底層儲存單元。查詢物理儲存需要解決的問題包括:
- 單表掃描中什麼樣的方式掃描效率最優
- 兩個表連線的時候如何 join 才能最快的獲取資料
- 多表連線的時候如何進行排序,是否要對每種組合都進行探索
早期物理優化階段使用基於關係代數規則和啟發式規則對查詢進行優化後就認為生辰的執行計劃是最優的。後面引入了最小代價查詢方式對每一個可能的可執行方式進行評估找到代價最小的作為最優執行計劃,目前資料庫的查詢通常是將這兩種方式融合到一起。
關於優化器部分是可以講上幾天都講不完的,在此我們只是簡單介紹。
-
執行器
經過上面階段已經將如何查詢得到資料的最優解拿到,執行器需要做的是根據指令去獲取資料。
2. MYSQL 資料儲存
MYSQL 作為一個數據庫管理工具最底層肯定是將資料存入磁碟的,那麼資料是如何進入磁碟的,進入磁碟之後的格式是什麼,用什麼方式來管理這些資料,讓我們帶著種種疑團走進 “今日說法”,一起來揭開這個祕密。
2.1 儲存引擎
根據對資料儲存方式、使用方式的要求, MYSQL 提供了各種私人訂製方案來讓客戶爸爸滿意,這些技術方案通過使用不同的儲存機制、索引方式、鎖技巧最終得到不同的組合效果同而適配不同的應用場景,我們將這種組合得到的產物定義為:儲存引擎。
儲存引擎主要做了哪些事情呢,包括不限於:
- 用合適的格式儲存資料
- 提供資料查詢,更新的介面
- 各種條件下資料一致性的支援
- 索引機制的建立
- 提供資料備份、故障恢復、故障轉移的能力
對於上面這些基本要求的實現,MYSQL 提供了哪些方案呢?
MyISAM
ISAM :Indexed Sequential Access Method(有索引的順序訪問方法)。MyISAM 底層基於這個引擎做了一些改良,這是 MYSQL 5.5 版本之前預設資料庫引擎,在當時那個年代提供了一些 當時沒有但是很必要 的特性:
- 索引管理
- 欄位管理
- 表鎖
當時這些功能可是沒有的,早期的程式設計師確實很辛苦啊,人家造輪子是真輪子用來跑的,現在造輪子也是輪子不過是站在巨人的肩膀看得更遠了吧。
MyISAM 引擎對每張表的存貯會歸結為三個檔案:
- .frm:以表的名字開始,儲存表定義;
- .MYD:儲存表資料;
- .MYI:儲存表索引。
因為 MyISAM 誕生的年代比較早,那時候也沒有現在網際網路這麼龐大的需求,所以放在現在來看它其實是有很多的缺點,比如:
- 鎖粒度太大,MyISAM 表鎖有兩種模式:表共享讀鎖,表獨佔寫鎖。讀的時候他不會阻塞其他使用者對同一表讀的需求,但是在讀期間不能寫;在寫的時候,會同時阻塞其他使用者對錶的讀寫操作。這個放在現在高併發的場景下肯定是個災難。
- MyISAM 不支援事務。這種鎖機制也註定了它不能支援事務,它生來就是為了 select 操作更快而優化的並且也是滿足當時時代的場景。
- 在崩潰恢復方面,因為各種備份機制沒有那麼多,帶來的直接後果就是它不支援崩潰後的安全恢復。
MEMORY
記憶體表,很顯然這種表是為了讀取速度而生。它既不支援事務也不支援外來鍵等等,記憶體表的優勢是讀取快,那缺點就很多了:
- 對於 Varchar 型別使用固定大小的長度儲存,浪費空間;
- 佔用記憶體資源,可能會造成記憶體崩潰;
- 伺服器重啟,資料丟失。
InnoDB
從 MYSQL 5.5 開始,InnoDB成為表的預設引擎。它具有有史以來堪稱完美的特點:行鎖設計、支援多版本隔離控制、支援外來鍵、支援一致性非鎖定讀、同時對記憶體和 CPU 的使用也隨著新硬體的發展做了一定的優化。
InnoDB 引擎本身是基於磁碟儲存資料的,但是因為 CPU速度和磁碟速度之間巨大的差距,所以在 InnoDB 引擎中大量使用緩衝池技術來提高讀寫速度。整體可以分為兩個部分:
-
緩衝區
緩衝區的型別也是各種各樣,主要包括:
- Buffer Pool:緩衝池,在主記憶體中開闢的一個區域,在 InnoDB 讀資料的時候會先訪問這裡,以減少磁碟訪問頻次;
- Change Buffer:寫緩衝區,避免每次增刪改都進行IO操作;
- Adaptive Hash Index:自適應雜湊索引,使用索引關鍵字的字首構建雜湊索引,提升查詢速度;
- Log Buffer:日誌緩衝區,儲存要寫入磁碟上的日誌檔案的資料,緩衝區的內容定期重新整理到磁碟。
-
磁碟資料
磁碟中的資料結構可以分為兩大類:表空間和重做日誌。
表空間又可分為:
- The System Tablespace(系統表空間):儲存更改緩衝區;
- File-Per-Table Tablespaces(獨立表空間):儲存單個Innodb表的資料和索引;
- General Tablespaces(通用表空間):使用
CREATE TABLESPACE
建立的共享表; - Undo Tablespaces(undo表空間):儲存
undo
日誌; - Temporary Tablespaces(臨時表空間):臨時表包括會話臨時表和全域性臨時表。
重做日誌,redo log 儲存的就是 buffer pool 刷到磁碟的資料。
InnoDB 在磁碟中對應的檔案結構比較多,除去 redo log 和 bin log之外的主要檔案有:
- .opt:資料庫配置檔案,包含資料庫字符集屬性;
- .frm:資料表元資料檔案,不管是獨立表空間還是系統表空間,每個表都對應一個;
- .ibd:資料庫獨立表空間檔案,如果是獨立表空間則對應一個.ibd檔案,否則儲存在系統表空間。
後面我們專門找一節來講 InnoDB 引擎,這裡簡單概述。
3. 常用命令
那麼對於我們使用的資料庫如何檢視當前使用的引擎呢:
檢視當前 MYSQL 版本所支援的引擎:
mysql> show engines;
+--------------------+---------+----------------------------------------------------------------+--------------+------+------------+
| Engine | Support | Comment | Transactions | XA | Savepoints |
+--------------------+---------+----------------------------------------------------------------+--------------+------+------------+
| MEMORY | YES | Hash based, stored in memory, useful for temporary tables | NO | NO | NO |
| CSV | YES | CSV storage engine | NO | NO | NO |
| MyISAM | YES | MyISAM storage engine | NO | NO | NO |
| BLACKHOLE | YES | /dev/null storage engine (anything you write to it disappears) | NO | NO | NO |
| MRG_MYISAM | YES | Collection of identical MyISAM tables | NO | NO | NO |
| PERFORMANCE_SCHEMA | YES | Performance Schema | NO | NO | NO |
| ARCHIVE | YES | Archive storage engine | NO | NO | NO |
| FEDERATED | NO | Federated MySQL storage engine | NULL | NULL | NULL |
| InnoDB | DEFAULT | Supports transactions, row-level locking, and foreign keys | YES | YES | YES |
+--------------------+---------+----------------------------------------------------------------+--------------+------+------------+
9 rows in set (0.00 sec)
檢視 MYSQL 預設的儲存引擎:
mysql> show variables like '%storage_engine%';
+----------------------------+--------+
| Variable_name | Value |
+----------------------------+--------+
| default_storage_engine | InnoDB |
| default_tmp_storage_engine | InnoDB |
| storage_engine | InnoDB |
+----------------------------+--------+
3 rows in set (0.01 sec)
檢視某個表用了什麼引擎(在顯示結果裡引數engine後面的就表示該表當前用的儲存引擎):
mysql> show create table 表名;
修改表的儲存引擎:
ALTER TABLE 表名 ENGINE = INNODB;
修改預設儲存引擎
如果修改本次會話的預設儲存引擎(重啟後失效),只對本會話有效,其他會話無效:
mysql> set default_storage_engine=innodb;
Query OK, 0 rows affected (0.00 sec)
修改全域性會話預設儲存引擎(重啟後失效),對所有會話有效:
mysql> set global default_storage_engine=innodb;
Query OK, 0 rows affected (0.00 sec)
希望重啟後也有效,即編輯 /etc/my.cnf
,[mysqld] 下面任意位置新增配置(所有對配置檔案的修改,重啟後生效)
default-storage-engine = InnoDB
關於 MYSQL 的結構部分限於篇幅就簡單介紹,後面我們一一來看細節。