MySQL架構與歷史
和其他資料庫系統相比,MySQL有點與眾不同,它的架構可以在多種不同場景中應用併發揮好的作用,但同時也會帶來一點選擇上的困難。MySQL並不完美,卻足夠靈活,能夠適應高要求的環境,例如Web類應用。同時,MySQL既可以嵌入到應用程式中,也可以支援資料倉庫、內容索引和部署軟體、高可用的冗餘系統、線上事務處理系統 (OLTP)等各種應用型別。
為了充分發揮MySQL的效能並順利地使用,就必須理解其設計。MySQL的靈活性體現在很多方面。例如,你可以通過配置使它在不同的硬體上都執行得很好,也可以支援多種不同的資料型別。但是,MySQL最重要、最與眾不同的特性是它的儲存引擎架構,這種架構的設計將査詢處理(Query Processing
本章概要地描述了MySQL的伺服器架構、各種儲存引擎之間的主要區別,以及這些區別的重要性。另外也會回顧一下MySQL的歷史背景和基準測試,並試圖通過簡化細節和演示案例來討論MySQL的原理。這些討論無論是對資料庫一無所知的新手,還是熟知其他資料庫的專家,都不無裨益。
1.MySQL邏輯架構
如果能在頭腦中構建出一幅MySQL各元件之間如何協同工作的架構圖,就會有助於深 入理解MySQL伺服器。圖1-1展示了MySQL的邏輯架構圖。
最上層的服務並不是MySQL所獨有的,大多數基於網路的客戶端/伺服器的工具或者服務都有類似的架構。比如連線處理、授權認證、安全等等。
第二層架構是MySQL比較有意思的部分。大多數MySQL的核心服務功能都在這一層,包括査詢解析、分析、優化、快取以及所有的內建函式(例如,日期、時間、數學和加密函式),所有跨儲存引擎的功能都在這一層實現:儲存過程、觸發器、檢視等。
第三層包含了儲存引擎。儲存引擎負責MySQL中資料的儲存和提取。和GNU/Linux下的各種檔案系統一樣,每個儲存引擎都有它的優勢和劣勢。伺服器通過API與儲存引擎進行通訊。這些介面遮蔽了不同儲存引擎之間的差異,使得這些差異對上層的査詢過程透明。儲存引擎API包含幾十個底層函式,用於執行諸如“開始一個事務”或者“根據主鍵提取一行記錄”等操作。但儲存引擎不會去解析SQL(InnoDB是一個例外,它會解析外來鍵定義,因為MySQL伺服器本身沒有實現該功能。),不同儲存引擎之間也不會相互通訊,而只是簡單地響應上層伺服器的請求。
1.1 連線管理與安全性
每個客戶端連線都會在伺服器程序中擁有一個執行緒,這個連線的査詢只會在這個單獨的 執行緒中執行,該執行緒只能輪流在某個CPU核心或者CPU中執行。伺服器會負責快取執行緒,因此不需要為每一個新建的連線建立或者銷燬執行緒。(MySQL 5.5或者更新的版本提供了一個API,支援執行緒池(Thread-Pooling)外掛,可以使用池中少量的執行緒來服務大量的連線。)
當客戶端(應用)連線到MySQL伺服器時,伺服器需要對其進行認證。認證基於使用者名稱、原始主機資訊和密碼。如果使用了安全套接字(SSL)的方式連線,還可以使用X.509證書認證。一旦客戶端連線成功,伺服器會繼續驗證該客戶端是否具有執行某個特定査詢的許可權(例如,是否允許客戶端對world資料庫的Country表執行SELECT語句)。
1.2 優化與執行
MySQL會解析査詢,並建立內部資料結構(解析樹),然後對其進行各種優化,包括重寫査詢、決定表的讀取順序,以及選擇合適的索引等。使用者可以通過特殊的關鍵字提示 (hint)優化器,影響它的決策過程。也可以請求優化器解釋(explain)優化過程的各個因素,使使用者可以知道伺服器是如何進行優化決策的,並提供一個參考基準,便於使用者重構查詢和schema、修改相關配置,使應用盡可能高效執行。
優化器並不關心表使用的是什麼儲存引擎,但儲存引擎對於優化査詢是有影響的。優化 器會請求儲存引擎提供容量或某個具體操作的開銷資訊,以及表資料的統計信肩等。例如,某些儲存引擎的某種索引,可能對一些特定的査詢有優化。關於索引與schema的優化。
對於SELECT語句,在解析査詢之前,伺服器會先檢査査詢快取(Query Cache),如果能 夠在其中找到對應的査詢,伺服器就不必再執行査詢解析、優化和執行的整個過程,而是直接返回査詢快取中的結果集。
2.併發控制
無論何時,只要有多個査詢需要在同一時刻修改資料,都會產生併發控制的問題。本章的目的是討論MySQL在兩個層面的併發控制:伺服器層與儲存引擎層。併發控制是一個內容龐大的話題,有大量的理論文獻對其進行過詳細的論述。本章只簡要地討論MySQL如何控制併發讀寫,因此讀者需要有相關的知識來理解本章接下來的內容。
以Unix系統的email box為例,典型的mbox檔案格式是非常簡單的。一個郵箱中的所有郵件都序列在一起,彼此首尾相連。這種格式對於讀取和分析郵件資訊非常友好,同時投遞郵件也很容易,只要在檔案末尾附加新的郵件內容即可。
但如果兩個程序在同一時刻對同一個郵箱投遞郵件,會發生什麼情況?顯然,郵箱的資料會被破壞,兩封郵件的內容會交叉地附加在郵箱檔案的末尾。設計良好的郵箱投遞系統會通過鎖(lock)來防止資料損壞。如果客戶試圖投遞郵件,而郵箱已經被其他客戶鎖住,那就必須等待,直到鎖釋放才能進行投遞。
這種鎖的方案在實際應用環境中雖然工作良好,但並不支援併發處理。因為在任意一個 時刻,只有一個程序可以修改郵箱的資料,這在大容量的郵箱系統中是個問題。
2.1 讀寫鎖
從郵箱中讀取資料沒有這樣的麻煩,即使同一時刻多個使用者併發讀取也不會有什麼問題。 因為讀取不會修改資料,所以不會出錯。但如果某個客戶正在讀取郵箱,同時另外一個使用者試圖刪除編號為25的郵件,會產生什麼結果?結論是不確定,讀的客戶可能會報錯退出,也可能讀取到不一致的郵箱資料。所以,為安全起見,即使是讀取郵箱也需要特別注意。
如果把上述的郵箱當成資料庫中的一張表,把郵件當成表中的一行記錄,就很容易看出, 同樣的問題依然存在。從很多方面來說,郵箱就是一張簡單的資料庫表。修改資料庫表中的記錄,和刪除或者修改郵箱中的郵件資訊,十分類似。
解決這類經典問題的方法就是併發控制,其實非常簡單。在處理併發讀或者寫時,可以 通過實現一個由兩種型別的鎖組成的鎖系統來解決問題。這兩種型別的鎖通常被稱為共享鎖(shared lock)和排他鎖(exclusive lock),也叫讀鎖(read lock)和寫鎖(write lock)。
這裡先不討論鎖的具體實現,描述一下鎖的概念如下:讀鎖是共享的,或者說是相互不 阻塞的。多個客戶在同一時刻可以同時讀取同一個資源,而互不干擾。寫鎖則是排他的,也就是說一個寫鎖會阻塞其他的寫鎖和讀鎖,這是出於安全策略的考慮,只有這樣,才能確保在給定的時間裡,只有一個使用者能執行寫入,並防止其他使用者讀取正在寫入的同一資源。
在實際的資料庫系統中,每時每刻都在發生鎖定,當某個使用者在修改某一部分資料時, MySQL會通過鎖定防止其他使用者讀取同一資料。大多數時候,MySQL鎖的內部管理都是透明的。
2.2 鎖粒度
一種提髙共享資源併發性的方式就是讓鎖定物件更有選擇性。儘量只鎖定需要修改的部分資料,而不是所有的資源。更理想的方式是,只對會修改的資料片進行精確的鎖定。任何時候,在給定的資源上,鎖定的資料量越少,則系統的併發程度越高,只要相互之間不發生衝突即可。
問題是加鎖也需要消耗資源。鎖的各種操作,包括獲得鎖、檢査鎖是否已經解除、釋放 鎖等,都會增加系統的開銷。如果系統花費大量的時間來管理鎖,而不是存取資料,那麼系統的效能可能會因此受到影響。
所謂的鎖策略,就是在鎖的開銷和資料的安全性之間尋求平衡,這種平衡當然也會影響 到效能。大多數商業資料庫系統沒有提供更多的選擇,一般都是在表上施加行級鎖(row-levellock),並以各種複雜的方式來實現,以便在鎖比較多的情況下儘可能地提供更好的效能。
而MySQL則提供了多種選擇。每種MySQL儲存引擎都可以實現自己的鎖策略和鎖粒度。在儲存引擎的設計中,鎖管理是個非常重要的決定。將鎖粒度固定在某個級別,可以為某些特定的應用場景提供更好的效能,但同時卻會失去對另外一些應用場景的良好支援。好在MySQL支援多個儲存引擎的架構,所以不需要單一的通用解決方案。下面將介紹兩種最重要的鎖策略。
表鎖(table lock)
表鎖是MySQL中最基本的鎖策略,並且是開銷最小的策略。表鎖非常類似於前文描述的郵箱加鎖機制:它會鎖定整張表。一個使用者在對錶進行寫操作(插入、刪除、更新等)前,需要先獲得寫鎖,這會阻塞其他使用者對該表的所有讀寫操作。只有沒有寫鎖時,其他讀取的使用者才能獲得讀鎖,讀鎖之間是不相互阻塞的。
在特定的場景中,表鎖也可能有良好的效能。例如,READLOCAL表鎖支援某些型別的併發寫操作。另外,寫鎖也比讀鎖有更高的優先順序,因此一個寫鎖請求可能會被插入到讀鎖佇列的前面(寫鎖可以插入到鎖佇列中讀鎖的前面,反之讀鎖則不能插入到寫鎖的前面)。
儘管儲存引擎可以管理自己的鎖,MySQL本身還是會使用各種有效的表鎖來實現不同的目的。例如,伺服器會為諸如ALTERTABLE之類的語句使用表鎖,而忽略儲存引擎的鎖機制。
行級鎖(rowlock)
行級鎖可以最大程度地支援併發處理(同時也帶來了最大的鎖開銷)。眾所周知,在InnoDB和XtraDB,以及其他一些儲存引擎中實現了行級鎖。行級鎖只在儲存引擎層實現,而MySQL伺服器層(如有必要,請回顧前文的邏輯架構圖)沒有實現。伺服器層完全不瞭解儲存引擎中的鎖實現。在mysql中,所有的儲存引擎都以自己的方式顯現了鎖機制。
3.事務
參考以下文章<MySQL的事務(ACID)和MySQL的隔離級別>
4.多版本併發控制
MySQL的大多數事務型儲存引擎實現的都不是簡單的行級鎖。基於提升併發效能的考 慮,它們一般都同時實現了多版本併發控制(MVCC)。不僅是MySQL,包括Oracle、PostgreSQL等其他資料庫系統也都實現了MVCC,但各自的實現機制不盡相同,因為MVCC沒有一個統一的實現標準。
可以認為MVCC是行級鎖的一個變種,但是它在很多情況下避免了加鎖操作,因此開銷更低。雖然實現機制有所不同,但大都實現了非阻塞的讀操作,寫操作也只鎖定必要的行。
MVCC的實現,是通過儲存資料在某個時間點的快照來實現的。也就是說,不管需要執行多長時間,每個事務看到的資料都是一致的。根據事務開始的時間不同,每個事務對同一張表,同一時刻看到的資料可能是不一樣的。如果之前沒有這方面的概念,這句話聽起來就有點迷惑。熟悉了以後會發現,這句話真實還是很容易理解的。
前面說到不同儲存引擎的MVCC實現是不同的,典型的有樂觀(optimistic)併發控制和悲觀(pessimistic)併發控制。下面我們通過InnoDB的簡化版行為來說明MVCC是如何工作的。
InnoDB的MVCC,是通過在每行記錄後面儲存兩個隱藏的列來實現的。這兩個列,一個儲存了行的建立時間,一個儲存行的過期時間(或刪除時間)。當然儲存的並不是實際的時間值,而是系統版本號(systemversionnumber)。每開始一個新的事務,系統版本號都會自動遞增。事務開始時刻的系統版本號會作為事務的版本號,用來和査詢到的每行記錄的版本號進行比較。下面看一下在REPEATABLE READ隔離級別下,MVCC具體是如何操作的。
SELECT
InnoDB會根據以下兩個條件檢査每行記錄:
a.InnoDB只查找版本早於當前事務版本的資料行(也就是,行的系統版本號小於或等於事務的系統版本號),這樣可以確保事務讀取的行,要麼是在事務開始前已經存在的,要麼是事務自身插入或者修改過的。
b.行的刪除版本要麼未定義,要麼大於當前事務版本號。這可以確保事務讀取到的行,在事務開始之前未被刪除。
只有符合上述兩個條件的記錄,才能返回作為査詢結果。
INSERT
InnoDB為新插入的每一行儲存當前系統版本號作為行版本號。
DELETE
InnoDB為刪除的每一行儲存當前系統版本號作為行刪除標識。
UPDATE
InnoDB為插入一行新記錄,儲存當前系統版本號作為行版本號,同時儲存當前系統版本號到原來的行作為行刪除標識。
儲存這兩個額外系統版本號,使大多數讀操作都可以不用加鎖。這樣設計使得讀資料操 作很簡單,效能很好,並且也能保證只會讀取到符合標準的行。不足之處是每行記錄都需要額外的儲存空間,需要做更多的行檢査工作,以及一些額外的維護工作。
MVCC只在REPEATABLE READ和READ COMMITTED兩個隔離級別下工作。其他兩個隔離級別都和MVCC不相容(MVCC並沒有正式的規範,所以各個儲存引擎和資料庫系統的實現都是各異的,沒有人能說其他的實現方式是錯的),因為READ UNCOMMITTED總是讀取最新的資料行,而不是符合當前事務版本的資料行。而SERIALIZABLE則會對所有讀取的行都加鎖。
5.MySQL的儲存引擎
參考以下文章<MySQL的儲存引擎>
6.MySQL時間線(Timeline)
參考以下文章<MySQL的時間線>
7.MySQL的開發模式
MySQL的開發過程和釋出模型在不同的階段有很大的變化,但目前已經基本穩定下來。 在Oracle定期釋出的新里程碑開發版本中,會包含即將在下一個GA版本釋出的新特性。這樣做是為了測試和獲得反饋,請不要在生產環境使用此版本,雖然Oracle宣稱每個里程碑版本的質量都是可靠的,並隨時可以正式釋出(到目前為止也沒有任何理由去推翻這個說法)。Oracle也會定期釋出實驗室預覽版,主要包含一些特定的需要評估的特性,這些特性並不保證會在下一個正式版本中包括進去。最終,Oracle會將穩定的特性打包釋出一個新的GA版本。
MySQL依然遵循GPL開源協議,全部的原始碼(除了一些商業版本的外掛)都會開放給社群。Oracle似乎也理解,為社群和付費使用者提供不同的版本並非明智之舉。MySQL AB曾經嘗試過不同版本的策略,結果導致付費使用者變成了“睜眼瞎”,無法從社群的測試和反饋中獲得好處。不同版本的策略並不受企業使用者的歡迎,所以後來被Sun廢除了。
現在Oracle為付費使用者單獨提供了一些伺服器外掛,而MySQL本身還是遵循開源模式。 儘管對於私有的伺服器外掛的釋出有一些抱怨,但這只是少數的聲音,並且慢慢地在平息。大多數MySQL使用者對此並不在意,有需求的使用者也能夠接受商業授權的付費外掛。
無論如何,不開源的擴充套件也只是擴充套件而已,並不會將MySQL變成受限制的非開源模式。沒有這些擴充套件,MySQL也是功能完整的資料庫。坦白地說,我們也很欣賞Oracle將更多的特性做成外掛的開發模式。如果將特性直接包含在伺服器中而不是API的方式,那就更加沒有選擇了:使用者只能接受這種實現,而失去了選擇更適合業務的實現的機會。
例如,如果Oracle將InnoDB的全文索引功能以API的方式實現,那麼就可能以同樣的API實現Sphinx或者Lucene的外掛,這可能對一些使用者更有用。伺服器內部的API設計也很乾淨,這對於提升程式碼質量非常有幫助,誰不想要這個呢?
8.總結
MySQL擁有分層的架構。上層是伺服器層的服務和査詢執行引擎,下層則是儲存引擎。雖然有很多不同作用的外掛API,但儲存引擎API還是最重要的。如果能理解MySQL在儲存引擎和服務層之間處理査詢時如何通過API來回互動,就能抓住MySQL的核心基礎架構的精髓。
MySQL最初基於ISAM構建(後來被MyISAM取代),其後陸續添加了更多的儲存引擎和事務支援。MySQL有一些怪異的行為是由於歷史遺留導致的。例如,在執行ALTERTABLE時,MySQL提交事務的方式是由於儲存引擎的架構直接導致的,並且資料字典也儲存在.frm檔案中(這並不是說InnoDB會導致ALTER變成非事務型的。對於InnoDB來說,所有的操作都是事務)。
當然,儲存引擎API的架構也有一些缺點。有時候選擇多並非好事,而在MySQL5.0和MySQL5.1中有太多的儲存引擎可以選擇。InnoDB對於95%以上的使用者來說都是最佳選擇,所以其他的儲存引擎可能只是讓事情變得複雜難搞,當然也不可否認某些情況下某些儲存引擎能更好地滿足需求。
Oracle—開始收購了InnoDB,之後又收購了MySQL,在同一個屋簷下對於兩者都是有利的。InnoDB和MySQL伺服器之間可以更快地協同發展。MySQL依然基於GPL協議開放全部原始碼,社群和客戶都可以獲得堅固而穩定的資料庫,MySQL正在變得越來越可擴充套件和有用。
作者:小家電維修
相見有時,後會無期。