和其他數據庫系統相比,MySQL有點與眾不同,它的架構可以在多種場景中應用并發揮很好的作用。但是同事也會帶來一點選擇上的困難。MYSQL并不完美,卻足夠靈活,能適應高要求的環境,例如web類應用。同時mysql既可以嵌入到應用程序中,也可以支持數據倉庫,內容索引和部署軟件,高可用的冗余系統,在線事物處理系統(OLTP)等各種應用類型。
為了充分發揮mysql的性能并順利地使用,及必須理解其架構設計。mysql的靈活性體現在很多方面。例如,你可以通過配置文件使它在不同的硬件上都運行得很好,也可以支持多種不同的數據類型。但是,mysql最重要,最與眾不同的特性是它的存儲引擎架構,這種架構的設計將查詢處理(Query PRocessing)以及其他系統任務(Server Task)和數據的存儲/提取相分離。這種處理和存儲分離的設計可以在使用的時根據性能,特性,以及其他需求來選擇數據存儲的方式。
本章概要地描述了mysql的服務器架構,各種存儲引擎之間的只要區別,以及這些區別的重要性。另外會回歸一下mysql的歷史背景和基準測試,并試圖通過簡化細節和演示案例來討論mysql的原理。這些討論無論是對數據庫一無所知的新手,還是熟知其他數據庫的專家,都不無裨益。
1.1 Mysql邏輯架構 如果能在頭腦中構建出一副mysql各組件之間如何協同工作的架構圖,就會有助于深入了解mysql服務器。圖1.1展示了mysql的邏輯架構圖。


【上面兩張都是展示Mysql的邏輯架構圖,好好理解!!!】
最上層的服務并不是mysql所獨有的,大多數基于網絡的客戶端/服務器的工具或者服務都有類似的架構。不如連接處理,授權認證,安全等等。【這里說的是最上層的connector組件】
第二層架構是mysql比較有意思的部分。大多數mysql的核心服務功能都在這一層,包括查詢解析,分析,優化,緩存以及所有的內置函數(例如日期,時間,數學和加密函數),所有跨存儲引擎的功能都在這一層實現:存儲過程,觸發器,視圖等等。【這一層包括有SQL接口,解析器(Parser),優化器(Optimizer)等】
第三層包含了存儲引擎。存儲引擎負責mysql中數據的存儲和提取,還有和GNU/linux下的各種文件系統一樣,每個存儲引擎都有它的優勢和劣勢。服務器通過API與存儲引擎進行通信。這些接口屏蔽了不同存儲引擎之間的差異,使得這些差異對上層的查詢過程透明。存儲引擎API包含了幾十個底層函數,用于執行諸如“開始一個事務”或者“根據主鍵提取一行記錄”等操作。但是存儲引擎不會去解析SQL,不同存儲引擎之間也不會互相通信,而只是簡單地相應上層服務器的請求。
【SQL接口會判斷查詢緩存是否命中,否則就會交給解析器進行處理,走一遍流程】 【解釋SQL語句是第二層中解析器(Parser)處理的,生成相應的數據結構交給優化器(Optimizer)】 【但是InnoDB這個會去解析SQL中的外鍵定義】
1.1.1 連接管理與安全性 每個客戶端連接都會在服務器進程中擁有一個線程,這個連接的查詢指揮在這個單獨的線程中執行,該線程只能輪流在某個CPU核心或者CPU中運行。服務器會負責緩存線程,因此不需要為每一個新建的連接創建或者銷毀線程。【這個線程可能由Management Service & Utilities來在連接池中進行管理???】
當客戶端(應用程序)連接到mysql服務器的時候,服務器需要對其進行認證。認證基于用戶名,原始主機信息和密碼。如果使用了安全套接字(SSL)的方法連接,還可以使用X.509證書認證。一旦客戶端連接成功,服務器會繼續驗證該客戶端是否具有執行某一個特定查詢的權限(例如,是否允許客戶端對world數據庫的country表執行select語句)。
1.1.2 優化與執行(Optimizer和存儲引擎之間的關系)
Mysql會解析查詢,并創建內部數據結構(解析樹)【應該是在解析器(Parser)中生成】,然后對其進行各種優化,包括重寫查詢,決定表的讀取順序,以及選擇合適的索引等。用戶可以通過特俗的關鍵字提示(hint)優化器,影響它的決策過程【意思就是我們用戶的SQL語句會影響優化器】。也可以請求優化器解釋(explain)優化過程各個因素。使用戶可以知道服務器是如何進行優化決策的,并提供一個參考的基準,便于用戶重構查詢和schema,修改相關配置,使應用盡可能高效運行。后面的章節我們會討論更多優化器(Optimizer)的細節。
優化器(Optimiszer)并不關心表使用的是什么存儲引擎,但是存儲引擎對于優化查詢是有影響的。優化器會請求存儲引擎提供容量或者某個具體操作的開銷信息,以及表數據的統計信息等。例如,某些存儲引擎的某種索引,可能對一些特定的查詢有優化。關于索引和schema的優化,下面會繼續詳細討論。
對于select語句,在解析查詢之前,服務器會先檢查查詢緩存(Query Cache),如果能夠在其中找到對應的查詢,服務器就不必再執行查詢解析,優化和執行的整個過程,而是直接返回查詢緩存中的結果集。后面的章節還會詳細討論的。
1.2 并發控制 無論何時,只有有多個查詢需要在同一時刻修改數據,都會產生并發控制的問題。本章的目的是討論mysql在兩個層面的并發控制:服務器層與存儲引擎層。并發控制是一個內容龐大的話題,有大量的理論文獻對其進行詳細的論述。本章只是簡要地討論mysql如何控制并發讀寫,因此讀者需要有相關的只是來理解本章接下來的內容。
以unix系統的email box為例子,典型的mbox文件格式是非常簡單的。一個mbox郵箱中的所有郵件都串行在一起,彼此首尾相連。這種格式對于讀取和肥西郵件信息非常友好,同時投遞郵件也很容易,只要在文件末尾附加新的郵件內容即可。
但是如果兩個進程在同一時刻對同一個郵箱投遞郵件,會發生什么情況?顯然,郵箱的數據會被破壞,兩封郵件的內容會交叉地附加在郵箱文件的末尾。設計娘好的郵箱投遞系統會通過鎖(lock)來防止數據損壞。如果客戶試圖投遞郵件,而郵箱已經被其他客戶鎖住,那么就必須等待,直到鎖釋放才能進行投遞。
這種鎖的方案在世紀應用環境中雖然工作良好,但是并不支持并發處理。因為在任意一個時刻,只有一個進程可以修改郵箱的數據,這在大容量的郵箱系統中是個問題。
1.2.1 讀寫鎖 從郵箱中讀取數據沒有這樣的麻煩,即使同一時刻多個用戶并發讀取也不會有什么問題。因為讀取不會修改數據,所以不會出錯。但是如果某個客戶正在讀取郵箱,同時另一個用戶試圖刪除編號為25的郵件,會產生什么結果?結論是不確定的,讀的客戶可能會報錯退出,也可能讀取不到一致的郵箱數據。所以,為了安全起見,即使是讀取郵箱也需要特別注意。
如果把上述的郵箱當成數據庫中的一張表,把郵件當成表中的一行記錄,就很容易看出,同樣的問題依然存在。從很多方面來說,郵箱就是一張簡單的數據庫表。修改數據庫表中的記錄,和刪除或者修改郵箱中的郵件信息,十分類似。
解決這類經典的問題的方法就是并發控制(讀鎖和寫鎖)。其實非常簡單,在處理并發讀或者寫的時候,可以通過實現一個由兩種類型的鎖組成鎖系統來解決問題。這兩種類型的鎖通常被稱為共享鎖(shared lock)和排他鎖(exclusive lock),也叫讀鎖(read lock)和寫鎖(write lock)。
這里先不討論如何具體實現,描述一下鎖的概念如下:讀鎖是共享的,或者說是相互不阻塞的。多個客戶在同一時刻可以同時讀取同一資源,而互不干擾。寫鎖則是排他的,也就是說一個寫鎖會阻塞其他的寫鎖和讀鎖,這是出于安全策略的考慮,只有這樣,才能確保給定的時間里,只有一個用戶能執行寫入,并防止其他用戶讀取正在寫入的同一資源。
在實際的數據庫系統中,每時每刻都在發生鎖定,當某個用戶在修改某一部分數據的時候,mysql會通過鎖定防止其他用戶讀取同一數據。大多數時候,mysql鎖的內部管理都是透明的。
1.2.2 鎖粒度 一種提供共享資源并發性的方式就是讓鎖定對象更有選擇性。盡量只鎖定需要修改的部分數據,而不是所有的資源。更理想的方式是,只對會修改的數據片(具體到鎖定所修改的字段)進行精確的鎖定。任何時候,在給定的資源上,鎖定的數據量越少,則系統的并發成都越高,只要互相之間不發生沖突即可。
問題是加鎖也需要消耗資源。鎖的各種操作,包括獲得鎖,檢查鎖是否已經被解除,釋放鎖等,都會增加系統的開銷。如果系統話費大量的時間來管理鎖,而不是存取數據,那么系統的性能可能因此受到影響。
所謂的鎖策略,就是在鎖的開銷和數據的安全性之間尋求平衡,這種平衡當然有會影響到性能,大多數商業數據庫系統沒有提供更多的選擇,一般都是在表上施加行級鎖(row-level-lock),并以各種復雜的方式來實現,以便在鎖比較多的情況下盡可能地提供更好的性能。
而mysql則提供多種選擇,每種MYSQL存儲引擎都可以實現自己的鎖策略和鎖粒度。在存儲引擎的設計中,鎖管理是個非常重要的決定。將鎖粒度固定在某個級別,可以為某些特定的應用場景提供更好的性能。但是同事卻會失去對另外一些應用場景的良好支持。好在Mysql支持多個存儲引擎的架構,所以不需要單一的通用解決方案。下面介紹兩種最重要的鎖策略。
表鎖(table lock) 表鎖是mysql中最基本的鎖策略,并且是開銷最小的策略。表鎖非常類似于前文描述的郵箱加鎖機制:它會鎖定整張表。一個用戶在對表進行寫操作(插入,刪除,更新等等)前,需要先獲得寫鎖,這個會阻塞其他用戶對該表的所有讀寫操作。只有沒有寫鎖的時候,其他讀取的用戶才能獲得讀鎖,讀鎖之間是不互相阻塞的。
在特定的場景中,表鎖也可能有良好的性能。例如,read local表鎖支持某些類型的并發寫操作。另外,寫鎖也比讀鎖有更高的優先級,因此一個寫鎖請求可能會被插入到讀鎖隊列的前面(寫鎖可以插入到鎖隊列中讀鎖的前面,反之讀鎖則不能插入寫鎖的前面)。
盡管存儲引擎可以管理自己的鎖,Mysql本身還是會使用各種有效的表鎖來實現不同的目的。例如服務器會諸如alter table之類的語句使用表鎖,而忽略存儲引擎的鎖機制。
【備注:鎖機制是存儲引擎管理的,但是Mysql本身也會有時候強制管理這個鎖機制】
行級鎖(row lock) 行級鎖可以最大程度地支持并發處理(同時也帶來了最大的鎖開銷)。眾多周知,在InnoDB和XtraDB,以及其他一些存儲引擎中實現了行級鎖。行級鎖只在存儲引擎層實現,而Mysql服務器層(如果有必要,請回顧前文的邏輯架構圖)沒有實現。服務器層完全不了解存儲引擎中的鎖實現。在本章的后續內容以及全書中,所有的存儲引擎都以自己的方式顯示了鎖機制。
1.3 事務(ACID) 在理解事務的概念之前,接觸數據庫系統的其他高級特性還言之過早。事務就是一組原子性的SQL查詢,或者說一個獨立的工作單元。如果數據庫引擎能夠成功對數據庫應用該組查詢的全部語句,那么久執行該組查詢。如果其中有任何一條語句因為崩潰或者其他原因無法執行,那么所有的語句都不會執行。也就是說,事務內的語句,要么全部執行成功,要不全部執行失敗。
【備注:事務就是多條SQL語句組成的集合,集合里面的每條語句都執行成功,這個集合里面的全部語句才會全部被執行;其中一條語句執行失敗,那么這個集合全部語句都不會執行。】
本節的內容并非專屬于Mysql,如果讀者已經熟悉了事務的ACID的概念。可以直接跳轉到1.3.4節。
銀行應用是解釋事務必要性的一個經典例子。假設一個銀行的數據庫有兩站表:支票表(checking)和儲蓄表(savings)。現在要從用戶jane的支票賬戶轉移200美元到她的儲蓄賬戶,那么需要至少三個步驟:
(1)檢查支票賬戶的余額是否高于200美元; (2)從支票賬戶余額中減去200美元; (3)在儲蓄賬戶余額中增加200美元;
上述三個步驟的操作必須打包在一個事務中,任何一個步驟失敗,則必須回滾所有的步驟。
我們可以使用關鍵詞start transaction語句開始一個事務,然后要么使用commit提交事務將修改的數據持久保留,要么使用rollback撤銷所有的修改。事務SQL語句的樣本如下:
1,start transaction; 2,select balance from checking where customer_id = 10233276; 3,update checking set balance = balance-200.00 where customer_id = 10233276; 4,update savings set balance = balance+200.00 where customer_id = 10233286; 5,commit;
單純的事務概念并不是故事的全部。試想一下,如果執行到第四條語句的時候 服務器崩潰了,這將會發生什么?天知道,用戶可能會損失200美元。再假如,在執行到第三條語句和第四條語句之間時間,另外一個進程要刪除支票賬戶的所有余額,那么結果可能就是銀行在不知道這個邏輯的情況下白白給了jane200美元。
除非系統通過嚴格的ACID測試,否則空談事務的概念是不夠的。ACID表示原子性,一致性,隔離型和持久性。一個運行良好的事務處理系統,必須具有這些標準特征。
原子性(A) 一個事務必須被視為一個不可分割的最小工作單元,整個事務中的所有操作要么全部提交成功,要么全部失敗回滾,對于一個事務來說,不可能只執行其中的一部分操作,這就是事物的原子性。
一致性(C) 數據庫總是從一個一致性的狀態轉換到另外一個一致性的狀態。在前面的例子中,一致性確保了,即使在執行第三,第四條語句之前的時候系統崩潰了,支票賬戶也不會損失200美元,因為事務最終沒有提交,所以事務中所做的修改也不會保存到數據庫中。
隔離型(I) 通常來說,一個事務所做的修改在最終提交以前,對其他事務時不可見的。在前面的例子中,當執行完第三條語句,第四條語句還沒開始的時候,此時有另外一個賬戶匯總程序開始運行,則其看到的支票賬戶的余額并沒有被減去200美元。后面我們討論隔離級別(Isolation level)的時候,會發現為什么我們要說“通常來說”是不可見的。
持久性(D) 一旦事務提交了,則其所做的修改就會永久保存到數據庫中。此時即使系統崩潰,修改的數據庫也不會丟失。持久性是一個有點模糊的概念。因為實際上持久性也分很多不同的級別。有些持久性策略能夠提供非常強的安全保證,而有些則未必。而且不可能又能做到100%的持久性保證的策略(如果數據庫本身就能做到真正就吃行買,那么備份又怎么能增加持久性呢?)后面的章節,我們將會繼續討論持久性的真正含義。
新聞熱點
疑難解答