系列文章:MySQL系列專欄html
MySQL 系列將深刻的學習研究 MySQL 的一些底層原理和設計,便於更好的理解和使用MySQL,本系列將主要從如下幾個方面深刻學習:MySQL體系結構、Innodb存儲引擎、索引與SQL優化、鎖、事務、高可用與性能優化、日誌等。一些基礎的表設計、增刪改查SQL、數據庫範式等不會涉及。java
這篇文章咱們先從總體上來了解下MySQL的架構設計,對MySQL有個總體的認識。mysql
本地使用的MySQL版本:5.7.29
web
本系列文章主要參考的一些資料:sql
首先來看看官方給出的 MySQL體系結構圖:數據庫
能夠知道 MySQL 由如下幾部分組成:編程
從大的層次上能夠分爲四層:緩存
第一層 鏈接層:主要負責鏈接處理、身份驗證、安全性等,通常 C/S 架構都會有這一層。安全
第二層 核心服務層:主要有查詢緩存、分析器、優化器、執行器等,以及全部的內置函數(如日期、時間、數學和加密函數等),全部跨存儲引擎的功能都在這一層實現,好比存儲過程、觸發器、視圖等。性能優化
第三層 存儲引擎層:這一層是底層數據存取操做實現部分,由多種存儲引擎共同組成。服務器經過API與存儲引擎通訊,API規避了不一樣存儲引擎的差別,不一樣存儲引擎也不會互相通訊。
第四層 數據存儲層:文件系統存儲數據,存放數據文件、日誌文件等,完成與存儲引擎的交互。
咱們的應用要去訪問 MySQL數據庫,必需要依賴一個數據庫驅動,這個MySQL驅動,它會在底層跟數據庫創建網絡鏈接,有網絡鏈接,才能去發送請求給數據庫服務器,才能基於這個鏈接去執行各類各樣的SQL語句。
對於Java、PHP、Perl、.NET、Python、Ruby等各類常見的編程語言,MySQL都會提供對應語言的MySQL驅動,讓各類語言編寫的系統經過MySQL驅動去訪問數據庫。
好比在Java中,須要在 pom.xml 中引入 mysql-connector-java
的驅動依賴:
<dependency>
<artifactId>mysql-connector-java</artifactId>
<groupId>mysql</groupId>
<version>5.1.47</version>
</dependency>
複製代碼
下面咱們以一條SQL查詢語句的執行過程來看看這些組件分別有什麼做用。
看下面的SQL查詢執行路徑圖,總的來講一條SQL查詢語句將通過以下過程獲得查詢結果:
首先要知道,咱們部署在容器中的應用程序是有多個工做線程來處理用戶請求的,多個線程都會去獲取一個數據庫鏈接來訪問數據庫。不能每次都建立一個新的數據庫鏈接,用完而後銷燬,這樣效率很是低下,所以客戶端須要一個數據庫鏈接池,每次從鏈接池裏拿一個鏈接來處理SQL請求,用完以後又放回鏈接池中,避免頻繁建立銷燬數據庫鏈接。
常見的數據庫鏈接池有 DBCP、C3P0、Druid 等。
客戶端請求鏈接數據庫時,鏈接器就會負責跟客戶端創建鏈接、獲取權限、維持和管理鏈接。MySQL服務器端也會有一個鏈接池,由於通常都會有多個系統與MySQL創建不少個鏈接,MySQL經過這個鏈接池去維護與客戶端的數據庫鏈接。
除此以外,鏈接器還會根據請求的帳號和密碼,進行安全認證,庫表權限認證。若是用戶名或密碼不對,就會收到一個"Access denied for user"
的錯誤。若是用戶名密碼認證經過,鏈接器會到權限表裏面查出你擁有的權限。以後,這個鏈接裏面的權限判斷邏輯,都將依賴於此時讀到的權限。這也意味着,一個用戶成功創建鏈接後,即便你用管理員帳號對這個用戶的權限作了修改,也不會影響已經存在鏈接的權限。修改完成後,只有再新建的鏈接纔會使用新的權限設置。
鏈接完成後,若是沒有後續的動做,這個鏈接就處於空閒狀態。客戶端若是太長時間沒動靜,鏈接器就會自動將它斷開。這個時間是由參數 wait_timeout
控制的,默認值是 8 小時。
對於一個 MySQL鏈接,任什麼時候刻都有一個狀態,該狀態表示了MySQL當前正在作什麼。可使用 SHOW FULL PROCESSLIST
命令查看當前鏈接的狀態(Command列)。在一個查詢的生命週期中,狀態會變化不少次。
MySQL官方手冊中給出了以下狀態:
Sleep
:處於空閒狀態,線程正在等待客戶端發送新的請求。Query
:線程正在執行查詢或者正在將結果發送給客戶端。Locked
:在MySQL服務器層,該線程正在等待表鎖。在存儲引擎級別實現的鎖,例如InnoDB的行鎖,並不會體如今線程狀態中。對於MyISAM來講這是一個比較典型的狀態。Analyzing and statistics
:線程正在收集存儲引擎的統計信息,並生成查詢的執行計劃。Copying to tmp table [on disk]
:線程正在執行查詢,而且將其結果集都複製到一個臨時表中,這種狀態通常要麼是在作 GROUP BY 操做,要麼是文件排序操做,或者是 UNION 操做。若是這個狀態後面還有「on disk」標記,那表示MySQL正在將一個內存臨時表放到磁盤上。Sorting result
:線程正在對結果集進行排序。Sending data
:線程可能在多個狀態之間傳送數據,或者在生成結果集,或者在向客戶端返回數據。MySQL客戶端和服務器之間的通訊協議是半雙工
的,這意味着,在任何一個時刻,要麼是由服務器向客戶端發送數據,要麼是由客戶端向服務器發送數據,這兩個動做不能同時發生。
這種協議讓 MySQL通訊簡單快速,可是也從不少地方限制了MySQL。一個明顯的限制是,無法進行流量控制,一旦一端開始發送消息,另外一端要接收完整個消息才能響應它。
客戶端用一個單獨的數據
包將查詢傳給服務器,若是查詢太大,服務端會拒絕接收更多的數據並拋出相應錯誤(可經過max_allowed_packet
參數控制請求大小)。一旦客戶端發送了請求,它能作的事情就只是等待結果了。
相反的,通常服務器響應給客戶端的數據一般不少,由多個數據包
組成。當服務器開始響應客戶端請求時,客戶端必須完整地接收整個返回結果,而不能簡單地只取前面幾條結果,而後讓服務器中止發送數據。因此必要的時候必定要在查詢中加上 LIMIT 限制返回數量。
客戶端從服務器取數據時,看起來是一個拉數據的過程,但其實是MySQL在向客戶端推送數據的過程
。客戶端不斷地接收從服務器推送的數據,客戶端也無法讓服務器停下來。
數據庫鏈接池中的鏈接接收到了網絡鏈接,好比客戶端發送一條SQL查詢語句,此時會有一個MySQL工做線程監聽請求,從鏈接中讀取請求數據,解析出客戶端發送的SQL語句,而後再轉交給SQL接口處理。
咱們能夠很容易理解一段SQL語句的意圖,但數據庫要執行各類增刪改查SQL是很是複雜的,因此MySQL內部首先提供了一個SQL接口組件,它是一套執行SQL語句的接口,專門執行客戶端發送給MySQL的各類增刪改查的SQL語句。
在解析一個查詢語句以前,若是查詢緩存是打開的,那麼MySQL會優先檢查這個查詢是否命中查詢緩存中的數據。以前執行過的語句及其結果可能會以 key-value 對的形式,被直接緩存在內存中。key 是查詢的語句,value 是查詢的結果。查詢和緩存中的查詢即便只有一個字節不一樣,那也不會匹配緩存結果,這種狀況下查詢就會進入下一階段的處理。
若是當前的查詢剛好命中了查詢緩存,那麼在返回查詢結果以前MySQL會檢查一次用戶權限。這仍然是無須解析查詢SQL語句的,由於在查詢緩存中已經存放了當前查詢須要訪問的表信息。若是權限沒有問題,MySQL會跳過全部其餘階段,直接從緩存中拿到結果並返回給客戶端。這種狀況下,查詢不會被解析,不用生成執行計劃,不會被執行,這個效率會很高。
但大多數狀況下建議不要使用查詢緩存,由於查詢緩存每每弊大於利。查詢緩存的失效很是頻繁,只要有對一個表的更新,這個表上全部的查詢緩存都會被清空。所以極可能緩存起來的結果還沒使用,就被一個更新全清空了。對於更新壓力大的數據庫來講,查詢緩存的命中率會很是低。
須要注意的是,MySQL 8.0
版本直接將查詢緩存的整塊功能刪掉了,也就是說 8.0 開始完全沒有這個功能了。
接下來,SQL接口該怎麼執行SQL呢?這個時候就須要解析器來拆解這個SQL,生成一棵對應的「解析樹」,將其變成MySQL能理解的東西。
首先會進行詞法分析
,SQL語句是由多個字符串和空格組成的,MySQL 須要識別出裏面的字符串分別是什麼,表明什麼。接着進行語法分析
,根據詞法分析的結果,語法分析器會根據語法規則,判斷你輸入的這SQL語句是否知足 MySQL 語法。
例如,它將驗證是否使用錯誤的關鍵字,使用關鍵字的順序是否正確,引號是否能先後正確匹配等。若是你的語句不對,就會收到「You have an error in your SQL syntax」
的錯誤提醒。
預處理器則根據一些MySQL規則進一步檢查解析樹是否合法,這裏將檢查數據表和數據列是否存在,解析名字和別名,看看它們是否有歧義。下一步預處理器會驗證是否有表權限、字段權限。
一條查詢能夠有不少種執行方式,最後都返回相同的結果。優化器的做用就是找到這其中最好的執行計劃。MySQL使用基於成本的優化器,它將嘗試預測一個查詢使用某種執行計劃時的成本,並選擇其中成本最小的一個。
不過咱們須要知道的是,查詢優化器選擇的最優執行計劃可能並非最好的,有多種緣由會致使MySQL優化器選擇錯誤的執行計劃,例以下面的一些:
統計信息不許確,MySQL依賴存儲引擎提供的統計信息來評估成本,可是有的存儲引擎提供的信息是準確的,有的誤差可能很是大。例如,InnoDB由於其MVCC的架構,並不能維護一個數據表的行數的精確統計信息。
執行計劃中的成本估算不等同於實際執行的成本,好比MySQL層面並不知道哪些頁面在內存中、哪些在磁盤上,因此查詢實際執行過程當中到底須要多少次物理I/O是沒法得知的。
MySQL只是基於其成本模型選擇最優的執行計劃,而有些時候這並非最快的執行方式。
MySQL從不考慮其餘併發執行的查詢,這可能會影響到當前查詢的速度。
優化器有時候沒法去估算全部可能的執行計劃,因此它可能錯過實際上最優的執行計劃。
MySQL的查詢優化器是一個很是複雜的部件,它使用了不少優化策略來生成一個最優的執行計劃,下面列舉了一部分MySQL可以處理的優化類型:
從新定義關聯表的順序:數據表的關聯並不老是按照在查詢中指定的順序進行,關聯表的順序是很重要的一個優化。
將外鏈接轉化成內鏈接:外鏈接可能會轉化爲內鏈接,而後調整表關聯順序。
使用等價變換規則:MySQL可使用一些等價變換來簡化並規範表達式。
優化COUNT()、MIN()和MAX():MySQL會利用存儲引擎或一些索引的特性來優化這類表達式。
預估並轉化爲常數表達式:當MySQL檢測到一個表達式能夠轉化爲常數的時候,就會一直把該表達式做爲常數進行優化處理。
覆蓋索引掃描:當索引中的列包含全部查詢中的列的時候,MySQL就可使用索引返回須要的數據,而無須查詢對應的數據行。
子查詢優化:在某些狀況下能夠將子查詢轉換一種效率更高的形式,從而減小多個查詢屢次對數據進行訪問。
提早終止查詢:在發現已經知足查詢需求的時候,就可以馬上終止查詢。好比使用了LIMIT子句的時候。
等值傳播:若是兩個列的值經過等式關聯,那麼MySQL可以把其中一個列的WHERE條件傳遞到另外一列上。
列表IN()的比較:MySQL將IN()列表中的數據先進行排序,而後經過二分查找的方式來肯定列表中的值是否知足條件。
咱們能夠在一個查詢SQL前添加 explain extended
,而後在末尾添加 show warnings
就能夠看到重構後的查詢語句。
EXPLAIN EXTENDED
select f from xxxx;
SHOW WARNINGS;
複製代碼
查詢優化器已經選擇好最優的查詢路徑了,接下來就須要交給底層的存儲引擎去真正的執行。這時候就須要執行引擎根據執行計劃,按照必定的順序和步驟,不停的調用存儲引擎的各類接口去完成SQL語句的執行計劃,大體就是不停的更新或者提取—些數據出來。
開始執行的時候,要先判斷一下對這個表有沒有執行查詢的權限,若是沒有,就會返回沒有權限的錯誤。若是有權限,就打開表繼續執行。打開表的時候,執行器就會根據表的引擎定義,去使用這個引擎提供的接口。
存儲引擎其實就是執行SQL語句的,他會按照必定的步驟去查詢內存緩存數據,更新磁盤數據,查詢磁盤數據等等。MySQL支持多種存儲引擎,好比常見的 InnoDB、MyISAM、Memory 等。
MySQL 數據庫區別於其餘數據庫的最重要的一個特色就是其插件式的表存儲引擎,存儲引擎是底層物理結構的實現,負責數據的存儲和提取。每一個存儲引擎都有各自的特色,能夠根據具體的應用創建不一樣存儲引擎表。
經常使用的存儲引擎有 InnoDB、MyISAM、Memory 等,最經常使用的存儲引擎是 InnoDB,它從 MySQL 5.5.8
版本開始成爲默認的存儲引擎。
執行 create table 建表的時候,若是不指定引擎類型,默認使用的就是 InnoDB。也能夠在 create table 時使用 engine=xxx 來指定使用的引擎。
建立表時,無論是什麼存儲引擎,MySQL會在數據庫子目錄下建立一個和表同名的.frm
文件保存表的定義。例如建立一個名爲 MyTable 的表,MySQL會在 MyTable.frm 文件中保存該表的定義。
可使用 show table status like <table>
命令顯示錶的相關信息,也能夠查詢 information_schema
中對應的表。
一些關鍵的信息如:
InnoDB存儲引擎支持事務,其設計目標主要面向在線事務處理(OLTP)的應用。其特色是行鎖設計、支持外鍵,並支持相似於Oracle 的非鎖定讀,即默認讀取操做不會產生鎖。
InnoDB存儲引擎將數據放在一個邏輯的表空間中,表空間是由InnoDB管理的一個黑盒子,由一系列的數據文件組成。InnoDB能夠將每一個表的數據和索引存放在單獨的文件中。
對於表中數據的存儲,InnoDB存儲引擎採用了彙集(clustered)的方式,所以每張表的存儲都是按主鍵的順序進行存放。若是沒有顯式地在表定義時指定主鍵,InnoDB存儲引擎會爲每一行生成一個6字節的ROWID,並以此做爲主鍵。
InnoDB採用多版本併發控制(MVCC)
來支持高併發,而且實現了SQL標準的四種隔離級別。默認級別是REPEATABLE READ(可重複讀)
,同時,RR級別經過間隙鎖(next-keylocking)
的策略防止幻讀(phantom)的出現。
除此以外,InnoDB儲存引擎還提供了插入緩衝(insert buffer)、二次寫(double write)、自適應哈希索引(adaptive hashindex)、預讀(read ahead)等高性能和高可用的功能。
MyISAM提供了大量的特性,包括全文索引、壓縮、空間函數(GIS)等,但MyISAM不支持事務和行級鎖,且有一個缺陷就是崩潰後沒法安全恢復,主要面向一些OLAP數據庫應用。此外,MyISAM存儲引擎的另外一個不同凡響的地方是它的緩衝池只緩存索引文件,而不緩衝數據文件。
MyISAM會將表存儲在兩個文件中:數據文件和索引文件,分別以.MYD
和 .MYI
爲擴展名。MyISAM表能夠存儲的行記錄數,通常受限於可用的磁盤空間,或者操做系統中單個文件的最大尺寸。從MySQL 5.0版本開始,MyISAM默認支持256TB的單表數據,這足夠知足通常應用需求。
若是須要快速地訪問數據,而且這些數據不會被修改,重啓之後丟失也沒有關係,那麼使用Memory表是很是有用的。Memory表至少比MyISAM表要快一個數量級,由於全部的數據都保存在內存中,不須要進行磁盤I/O。若是數據庫重啓或發生崩潰,Memory表的結構還會保留,但數據會丟失。
Memory表支持Hash索引,所以查找操做很是快。Memroy表是表級鎖,所以併發寫入的性能較低。它不支持BLOB或TEXT類型的列,而且每行的長度是固定的,因此即便指定了VARCHAR列,實際存儲時也會轉換成CHAR,這可能致使部份內存的浪費。
若是MySQL在執行查詢的過程當中須要使用臨時表來保存中間結果,內部使用的臨時表就是Memory表。若是中間結果太大超出了Memory表的限制,或者含有BLOB或TEXT字段,則臨時表會轉換成MyISAM表。
要注意Memory表和臨時表的區別,臨時表是指使用 CREATE TEMPORARY TABLE
語句建立的表,它可使用任何存儲引擎,所以和Memory表不是一回事。臨時表只在單個鏈接中可見,當鏈接斷開時,臨時表也將不復存在。
MySQL的存儲引擎有不少種,不過通常咱們選擇InnoDB存儲引擎便可,若是非要用到一些InnoDB不具有的特性的引擎,咱們也能夠用其它方案來代替,例如緩存Redis、消息中間件等。
可使用 SHOW ENGINES
命令查看當前 MySQL 支持的存儲引擎:
以下是常見存儲引擎功能的一些對比:(參考官方文檔)
系統文件存儲層主要是負責將數據庫的數據和日誌存儲在系統的文件中,同時完成與存儲引擎的之間的交互,是文件的物理存儲層。
主要的一些文件有:數據文件、日誌文件、配置文件等。
可使用 SHOW VARIABLES LIKE '%datadir%';
命令查看數據文件的目錄,在數據文件目錄下咱們能夠看到以下的一些文件。
db.opt
:記錄這個數據庫默認使用的字符集和校驗規則。ib_logfile0、ib_logfile1
:Redo log 日誌文件。.frm
:存儲表相關的元數據信息,包含表結構的定義信息等,每一張表都會有一個frm文件與之對應。InnoDB數據文件:
.ibd
:使用獨享表空間存儲表數據和索引信息,一張表對應一個ibd文件.ibdata
:使用共享表空間存儲表數據和索引信息,全部表共同使用一個或者多個ibdata文件MyISAM數據文件:
.MYD
:主要用來存儲表數據信息.MYI
:主要用來存儲表數據文件中任務索引的數據樹經常使用的日誌包括:錯誤日誌、二進制日誌、查詢日誌、慢查詢日誌和事務redo日誌、中繼日誌等等。
能夠經過 SHOW VARIABLES LIKE '%\_log_%';
查詢當前MySQL日誌使用狀況。具體的一些日誌文件會在後面的文章中詳細介紹。
用於存放MySQL全部的配置信息的文件,好比:my.cnf、my.ini 等。
可使用 mysql --help|grep my.cnf
查看MySQL配置文件的位置:
能夠看到MySQL是以 /etc/my.cnf -> /etc/mysql/my.cnf -> /usr/etc/my.cnf -> ~/.my.cnf
的順序讀取配置文件的,若是有相同的配置,會以讀取到的最後一個配置文件中的參數爲準。