為了充分發(fā)揮MySQL的性能并順利地使用,就必須理解其設(shè)計(jì)。MySQL的靈活性體現(xiàn)在很多方面。例如,你可以通過(guò)配置使它在不同的硬件上都運(yùn)行得很好,也可以支持多種不同的數(shù)據(jù)類型。但是,MySQL最重要、最與眾不同的特性是它的存儲(chǔ)引擎架構(gòu):
- 查詢處理(Query Processing)。
- 其他系統(tǒng)任務(wù)(Server Task)。
- 數(shù)據(jù)的存儲(chǔ)/提取。
這種處理和存儲(chǔ)分離的設(shè)計(jì)可以在使用時(shí)根據(jù)性能、特性,以及其他需求來(lái)選擇數(shù)據(jù)存儲(chǔ)的方式。
1.1 MySQL邏輯架構(gòu)
??最上層的服務(wù)并不是MySQL所獨(dú)有的,大多數(shù)基于網(wǎng)絡(luò)的客戶端/服務(wù)器的工具或者服務(wù)都有類似的架構(gòu)。比如連接處理、授權(quán)認(rèn)證、安全等等。(筆者注:例如MySQL賬號(hào)的權(quán)限,登錄嗎?)
??第二層架構(gòu)是MySQL比較有意思的部分。大多數(shù)MySQL的核心服務(wù)功能都在這一層,包括
查詢解析
、分析
、優(yōu)化
、緩存
以及所有的內(nèi)置函數(shù)(例如,日期、時(shí)間、數(shù)學(xué)和加密函數(shù))
,所有跨存儲(chǔ)引擎的功能都在這一層實(shí)現(xiàn):存儲(chǔ)過(guò)程、觸發(fā)器、視圖等。??第三層包含了
存儲(chǔ)引擎
。存儲(chǔ)引擎負(fù)責(zé)MySQL中數(shù)據(jù)的存儲(chǔ)
和提取
。和GNU/Linux下的各種文件系統(tǒng)一樣,每個(gè)存儲(chǔ)引擎都有它的優(yōu)勢(shì)和劣勢(shì)。服務(wù)器通過(guò)API與存儲(chǔ)引擎進(jìn)行通信。這些接口屏蔽了不同存儲(chǔ)引擎之間的差異,使得這些差異對(duì)上層的查詢過(guò)程透明。存儲(chǔ)引擎API包含幾十個(gè)底層函數(shù),用于執(zhí)行諸如“開(kāi)始一個(gè)事務(wù)”或者“根據(jù)主鍵提取一行記錄”等操作。??但存儲(chǔ)引擎不會(huì)去解析SQL,不同存儲(chǔ)引擎之間也不會(huì)相互通信,而只是簡(jiǎn)單地響應(yīng)上層服務(wù)器的請(qǐng)求。
存儲(chǔ)引擎不會(huì)去解析SQL
。InnoDB是一個(gè)例外,它會(huì)解析外鍵定義,因?yàn)镸ySQL服務(wù)器本身沒(méi)有實(shí)現(xiàn)該功能
1.1.1 連接管理與安全性
每個(gè)客戶端連接都會(huì)在服務(wù)器進(jìn)程中擁有一個(gè)線程,這個(gè)連接的查詢只會(huì)在這個(gè)單獨(dú)的線程中執(zhí)行,該線程只能輪流在某個(gè)CPU核心或者CPU中運(yùn)行。服務(wù)器會(huì)負(fù)責(zé)緩存線程,因此不需要為每一個(gè)新建的連接創(chuàng)建或者銷毀線程。
MySQL 5.5或者更新的版本提供了一個(gè)API,支持線程池(Thread-Pooling)插件,可以使用池中少量的線程來(lái)服務(wù)大量的連接。(筆者注:漲見(jiàn)識(shí)了,沒(méi)想到在MySQL也可以使用線程池,理論上這是一個(gè)優(yōu)化點(diǎn))
當(dāng)客戶端(應(yīng)用)連接到MySQL服務(wù)器時(shí),服務(wù)器需要對(duì)其進(jìn)行認(rèn)證。認(rèn)證基于用戶名、原始主機(jī)信息(筆者注:另類的權(quán)限設(shè)置優(yōu)化?)和密碼。如果使用了安全套接字(SSL)的方式連接,還可以使用X.509證書(shū)認(rèn)證。一旦客戶端連接成功,服務(wù)器會(huì)繼續(xù)驗(yàn)證該客戶端是否具有執(zhí)行某個(gè)特定查詢的權(quán)限(例如,是否允許客戶端對(duì)world數(shù)據(jù)庫(kù)的Country表執(zhí)行SELECT語(yǔ)句)。
1.1.2 優(yōu)化與執(zhí)行
MySQL會(huì)解析查詢,并創(chuàng)建內(nèi)部數(shù)據(jù)結(jié)構(gòu)(解析樹(shù)),然后對(duì)其進(jìn)行各種優(yōu)化,包括重寫(xiě)查詢、決定表的讀取順序,以及選擇合適的索引等(筆者注:這應(yīng)該屬于邏輯結(jié)構(gòu)的第二層)。用戶可以通過(guò)特殊的關(guān)鍵字提示(hint)優(yōu)化器,影響它的決策過(guò)程。也可以請(qǐng)求優(yōu)化器解釋(explain)優(yōu)化過(guò)程的各個(gè)因素,使用戶可以知道服務(wù)器是如何進(jìn)行優(yōu)化決策的,并提供一個(gè)參考基準(zhǔn),便于用戶重構(gòu)查詢和schema、修改相關(guān)配置,使應(yīng)用盡可能高效運(yùn)行。第6章我們將討論更多優(yōu)化器的細(xì)節(jié)。
??優(yōu)化器并不關(guān)心表使用的是什么存儲(chǔ)引擎,但存儲(chǔ)引擎對(duì)于優(yōu)化查詢是有影響的。優(yōu)化器會(huì)請(qǐng)求存儲(chǔ)引擎提供容量或某個(gè)具體操作的開(kāi)銷信息,以及表數(shù)據(jù)的統(tǒng)計(jì)信息。例如,某些存儲(chǔ)引擎的某種索引,可能對(duì)一些特定的查詢有優(yōu)化。關(guān)于索引與schema的優(yōu)化,請(qǐng)參見(jiàn)第4章和第5章。
??對(duì)于SELECT語(yǔ)句,在解析查詢之前,服務(wù)器會(huì)先檢查查詢緩存(Query Cache),如果能夠在其中找到對(duì)應(yīng)的查詢,服務(wù)器就不必再執(zhí)行查詢解析、優(yōu)化和執(zhí)行的整個(gè)過(guò)程,而是直接返回查詢緩存中的結(jié)果集。第7章詳細(xì)討論了相關(guān)內(nèi)容。
1.2 并發(fā)控制
無(wú)論何時(shí),只要有多個(gè)查詢需要在同一時(shí)刻修改數(shù)據(jù),都會(huì)產(chǎn)生并發(fā)控制的問(wèn)題。本章的目的是討論MySQL在兩個(gè)層面的并發(fā)控制:服務(wù)器層與存儲(chǔ)引擎層。并發(fā)控制是一個(gè)內(nèi)容龐大的話題,有大量的理論文獻(xiàn)對(duì)其進(jìn)行過(guò)詳細(xì)的論述。本章只簡(jiǎn)要地討論MySQL如何控制并發(fā)讀寫(xiě),因此讀者需要有相關(guān)的知識(shí)來(lái)理解本章接下來(lái)的內(nèi)容。
??以Unix系統(tǒng)的email box為例,典型的mbox文件格式是非常簡(jiǎn)單的。一個(gè)mbox郵箱中的所有郵件都串行在一起,彼此首尾相連。這種格式對(duì)于讀取和分析郵件信息非常友好,同時(shí)投遞郵件也很容易,只要在文件末尾附加新的郵件內(nèi)容即可。
??但如果兩個(gè)進(jìn)程在同一時(shí)刻對(duì)同一個(gè)郵箱投遞郵件,會(huì)發(fā)生什么情況?顯然,郵箱的數(shù)據(jù)會(huì)被破壞,兩封郵件的內(nèi)容會(huì)交叉地附加在郵箱文件的末尾。設(shè)計(jì)良好的郵箱投遞系統(tǒng)會(huì)通過(guò)鎖(lock)來(lái)防止數(shù)據(jù)損壞。如果客戶試圖投遞郵件,而郵箱已經(jīng)被其他客戶鎖住,那就必須等待,直到鎖釋放才能進(jìn)行投遞。
1.2.1 讀寫(xiě)鎖
從郵箱中讀取數(shù)據(jù)沒(méi)有這樣的麻煩,即使同一時(shí)刻多個(gè)用戶并發(fā)讀取也不會(huì)有什么問(wèn)題。因?yàn)樽x取不會(huì)修改數(shù)據(jù),所以不會(huì)出錯(cuò)。但如果某個(gè)客戶正在讀取郵箱,同時(shí)另外一個(gè)用戶試圖刪除編號(hào)為25的郵件,會(huì)產(chǎn)生什么結(jié)果?結(jié)論是不確定,讀的客戶可能會(huì)報(bào)錯(cuò)退出,也可能讀取到不一致的郵箱數(shù)據(jù)。所以,為安全起見(jiàn),即使是讀取郵箱也需要特別注意。
??如果把上述的郵箱當(dāng)成數(shù)據(jù)庫(kù)中的一張表,把郵件當(dāng)成表中的一行記錄,就很容易看出,同樣的問(wèn)題依然存在。從很多方面來(lái)說(shuō),郵箱就是一張簡(jiǎn)單的數(shù)據(jù)庫(kù)表。修改數(shù)據(jù)庫(kù)表中的記錄,和刪除或者修改郵箱中的郵件信息,十分類似。
??解決這類經(jīng)典問(wèn)題的方法就是并發(fā)控制,其實(shí)非常簡(jiǎn)單。在處理并發(fā)讀或者寫(xiě)時(shí),可以通過(guò)實(shí)現(xiàn)一個(gè)由兩種類型的鎖組成的鎖系統(tǒng)來(lái)解決問(wèn)題。這兩種類型的鎖通常被稱為共享鎖(shared lock)和排他鎖(exclusive lock),也叫讀鎖(read lock)和寫(xiě)鎖(writelock )。(筆者注:以后跟別人說(shuō),你知道shared lock嗎?)
??這里先不討論鎖的具體實(shí)現(xiàn),描述一下鎖的概念如下:讀鎖是共享的,或者說(shuō)是相互不阻塞的。多個(gè)客戶在同一時(shí)刻可以同時(shí)讀取同一個(gè)資源,而互不干擾。寫(xiě)鎖則是排他的,也就是說(shuō)一個(gè)寫(xiě)鎖會(huì)阻塞其他的寫(xiě)鎖和讀鎖,這是出于安全策略的考慮,只有這樣,才能確保在給定的時(shí)間里,只有一個(gè)用戶能執(zhí)行寫(xiě)入,并防止其他用戶讀取正在寫(xiě)入的同一資源。
??在實(shí)際的數(shù)據(jù)庫(kù)系統(tǒng)中,每時(shí)每刻都在發(fā)生鎖定,當(dāng)某個(gè)用戶在修改某一部分?jǐn)?shù)據(jù)時(shí),MySQL會(huì)通過(guò)鎖定防止其他用戶讀取同一數(shù)據(jù)。大多數(shù)時(shí)候,MySQL鎖的內(nèi)部管理都是透明的。
1.2.2 鎖粒度
一種提高共享資源并發(fā)性的方式就是讓鎖定對(duì)象更有選擇性。盡量只鎖定需要修改的部分?jǐn)?shù)據(jù),而不是所有的資源。更理想的方式是,只對(duì)會(huì)修改的數(shù)據(jù)片進(jìn)行精確的鎖定。任何時(shí)候,在給定的資源上,鎖定的數(shù)據(jù)量越少,則系統(tǒng)的并發(fā)程度越高,只要相互之間不發(fā)生沖突即可。(筆者注:難道是表鎖和行鎖?)
??問(wèn)題是加鎖也需要消耗資源。鎖的各種操作,包括獲得鎖、檢查鎖是否已經(jīng)解除、釋放鎖等,都會(huì)增加系統(tǒng)的開(kāi)銷。如果系統(tǒng)花費(fèi)大量的時(shí)間來(lái)管理鎖,而不是存取數(shù)據(jù),那么系統(tǒng)的性能可能會(huì)因此受到影響。(筆者注:類似java線程中的“切換上下文”資源損耗?)
??所謂的鎖策略,就是在鎖的開(kāi)銷和數(shù)據(jù)的安全性之間尋求平衡,這種平衡當(dāng)然也會(huì)影響到性能。大多數(shù)商業(yè)數(shù)據(jù)庫(kù)系統(tǒng)沒(méi)有提供更多的選擇,一般都是在表上施加行級(jí)鎖( row-level lock),并以各種復(fù)雜的方式來(lái)實(shí)現(xiàn),以便在鎖比較多的情況下盡可能地提供更好的性能。
??而MySQL則提供了多種選擇。每種MySQL存儲(chǔ)引擎都可以實(shí)現(xiàn)自己的鎖策略和鎖粒度。在存儲(chǔ)引擎的設(shè)計(jì)中,鎖管理是個(gè)非常重要的決定。將鎖粒度固定在某個(gè)級(jí)別,可以為某些特定的應(yīng)用場(chǎng)景提供更好的性能,但同時(shí)卻會(huì)失去對(duì)另外一些應(yīng)用場(chǎng)景的良好支持。好在MySQL支持多個(gè)存儲(chǔ)引擎的架構(gòu),所以不需要單一的通用解決方案。下面將介紹兩種最重要的鎖策略。(筆者注:我剛才仔細(xì)查看了下我們公司,好像只有InnoDB)
表鎖(table lock)
表鎖是MySQL中最基本的鎖策略,并且是開(kāi)銷最小的策略。表鎖非常類似于前文描述的郵箱加鎖機(jī)制:它會(huì)鎖定整張表。一個(gè)用戶在對(duì)表進(jìn)行寫(xiě)操作(插人、刪除、更新等)前,需要先獲得寫(xiě)鎖,這會(huì)阻塞其他用戶對(duì)該表的所有讀寫(xiě)操作。只有沒(méi)有寫(xiě)鎖時(shí),其他讀取的用戶才能獲得讀鎖,讀鎖之間是不相互阻塞的。
????在特定的場(chǎng)景中,表鎖也可能有良好的性能。例如,READ LOCAL表鎖支持某些類型的并發(fā)寫(xiě)操作。另外,寫(xiě)鎖也比讀鎖有更高的優(yōu)先級(jí),因此一個(gè)寫(xiě)鎖請(qǐng)求可能會(huì)被插入到讀鎖隊(duì)列的前面(寫(xiě)鎖可以插入到鎖隊(duì)列中讀鎖的前面,反之讀鎖則不能插人到寫(xiě)鎖的前面)。
????盡管存儲(chǔ)引擎可以管理自己的鎖,MySQL本身還是會(huì)使用各種有效的表鎖來(lái)實(shí)現(xiàn)不同的目的。例如,服務(wù)器會(huì)為諸如ALTER TABLE之類的語(yǔ)句使用表鎖,而忽略存儲(chǔ)引擎的鎖機(jī)制。
(筆者注:誠(chéng)不欺我)
行鎖(row lock)
行級(jí)鎖可以最大程度地支持并發(fā)處理(同時(shí)也帶來(lái)了最大的鎖開(kāi)銷)。眾所周知,在InnoDB和XtraDB,以及其他一些存儲(chǔ)引擎中實(shí)現(xiàn)了行級(jí)鎖。行級(jí)鎖只在存儲(chǔ)引擎層實(shí)現(xiàn),而MySQL服務(wù)器層(請(qǐng)回顧前文的邏輯架構(gòu)圖)沒(méi)有實(shí)現(xiàn)。服務(wù)器層完全不了解存儲(chǔ)引擎中的鎖實(shí)現(xiàn)。在本章的后續(xù)內(nèi)容以及全書(shū)中,所有的存儲(chǔ)引擎都以自己的方式顯現(xiàn)了鎖機(jī)制。
1.3 事務(wù)
在理解事務(wù)的概念之前,接觸數(shù)據(jù)庫(kù)系統(tǒng)的其他高級(jí)特性還言之過(guò)早。事務(wù)就是一組原子性的SQL查詢,或者說(shuō)一個(gè)獨(dú)立的工作單元。如果數(shù)據(jù)庫(kù)引擎能夠成功地對(duì)數(shù)據(jù)庫(kù)應(yīng)用該組查詢的全部語(yǔ)句,那么就執(zhí)行該組查詢。如果其中有任何一條語(yǔ)句因?yàn)楸罎⒒蚱渌驘o(wú)法執(zhí)行,那么所有的語(yǔ)句都不會(huì)執(zhí)行。也就是說(shuō),事務(wù)內(nèi)的語(yǔ)句,要么全部執(zhí)行成功,要么全部執(zhí)行失敗。本節(jié)的內(nèi)容并非專屬于MySQL,如果讀者已經(jīng)熟悉了事務(wù)的ACID的概念,可以直接跳轉(zhuǎn)到1.3.4節(jié)。
??銀行應(yīng)用是解釋事務(wù)必要性的一個(gè)經(jīng)典例子。假設(shè)一個(gè)銀行的數(shù)據(jù)庫(kù)有兩張表:支票(checking)表和儲(chǔ)蓄(savings)表。現(xiàn)在要從用戶Jane的支票賬戶轉(zhuǎn)移200美元到她的儲(chǔ)蓄賬戶,那么需要至少三個(gè)步驟:
??1. 檢查支票賬戶的余額高干200美元。
??2. 從支票賬戶余額中減去200美元。
??3. 在儲(chǔ)蓄賬戶余額中增加200美元。
??上述三個(gè)步驟的操作必須打包在一個(gè)事務(wù)中,任何一個(gè)步驟失敗,則必須回滾所有的步驟。
??可以用START TRANSACTION語(yǔ)句開(kāi)始一個(gè)事務(wù),然后要么使用COMMIT提交事務(wù)將修改的數(shù)據(jù)持久保留,要么使用ROLLBACK撤銷所有的修改。事務(wù)SQL的樣本如下:
START TRANSACTION;
UPDATE checking SET balance = balance - 200.00 WHERE id = 9001;
UPDATE savings SET balance = balance + 200.00 WHERE id = 9002;
COMMIT;
單純的事務(wù)概念并不是故事的全部。試想一下,如果執(zhí)行到第四條語(yǔ)句時(shí)服務(wù)器崩潰了,會(huì)發(fā)生什么?天知道,用戶可能會(huì)損失200美元。再假如,在執(zhí)行到第三條語(yǔ)句和第四條語(yǔ)句之間時(shí),另外一個(gè)進(jìn)程要?jiǎng)h除支票賬戶的所有余額,那么結(jié)果可能就是銀行在不知道這個(gè)邏輯的情況下白白給了Jane 200美元。
??除非系統(tǒng)通過(guò)嚴(yán)格的ACID測(cè)試,否則空談事務(wù)的概念是不夠的。ACID表示原子性( atomicity )
、一致性(consistency )
、隔離性(isolation)
和持久性(durability)
。一個(gè)運(yùn)行良好的事務(wù)處理系統(tǒng),必須具備這些標(biāo)準(zhǔn)特征。
原子性(atomicity)
一個(gè)事務(wù)必須被視為一個(gè)不可分割的最小工作單元,整個(gè)事務(wù)中的所有操作要么全 部提交成功,要么全部失敗回滾,對(duì)于一個(gè)事務(wù)來(lái)說(shuō),不可能只執(zhí)行其中的一部分操作,這就是事務(wù)的原子性。
一致性(consistency )
數(shù)據(jù)庫(kù)總是從一個(gè)一致性的狀態(tài)轉(zhuǎn)換到另外一個(gè)一致性的狀態(tài)。在前面的例子中, 一致性確保了,即使在執(zhí)行第三、四條語(yǔ)句之間時(shí)系統(tǒng)崩潰,支票賬戶中也不會(huì)損失200美元,因?yàn)槭聞?wù)最終沒(méi)有提交,所以事務(wù)中所做的修改也不會(huì)保存到數(shù)據(jù)庫(kù)中。
隔離性(isolation)
通常來(lái)說(shuō),一個(gè)事務(wù)所做的修改在最終提交以前,對(duì)其他事務(wù)是不可見(jiàn)的。在前面的例子中,當(dāng)執(zhí)行完第三條語(yǔ)句、第四條語(yǔ)句還未開(kāi)始時(shí),此時(shí)有另外一個(gè)賬戶匯總程序開(kāi)始運(yùn)行,則其看到的支票賬戶的余額并沒(méi)有被減去200美元。后面我們討論隔離級(jí)別(Isolation level)的時(shí)候,會(huì)發(fā)現(xiàn)為什么我們要說(shuō)“通常來(lái)說(shuō)”是不可見(jiàn)的。
持久性(durability )
一旦事務(wù)提交,則其所做的修改就會(huì)永久保存到數(shù)據(jù)庫(kù)中。此時(shí)即使系統(tǒng)崩潰,修改的數(shù)據(jù)也不會(huì)丟失。持久性是個(gè)有點(diǎn)模糊的概念,因?yàn)閷?shí)際上持久性也分很多不同的級(jí)別。有些持久性策略能夠提供非常強(qiáng)的安全保障,而有些則未必。而且不可能有能做到100%的持久性保證的策略(如果數(shù)據(jù)庫(kù)本身就能做到真正的持久性,那么備份又怎么能增加持久性呢?)。在后面的一些章節(jié)中,我們會(huì)繼續(xù)討論MySQL中持久性的真正含義。
??事務(wù)的ACID特性可以確保銀行不會(huì)弄丟你的錢(qián)。而在應(yīng)用邏輯中,要實(shí)現(xiàn)這一點(diǎn)非常難,甚至可以說(shuō)是不可能完成的任務(wù)。一個(gè)兼容ACID的數(shù)據(jù)庫(kù)系統(tǒng),需要做很多復(fù)雜但可能用戶并沒(méi)有覺(jué)察到的工作,才能確保ACID的實(shí)現(xiàn)。
??就像鎖粒度的升級(jí)會(huì)增加系統(tǒng)開(kāi)銷一樣,這種事務(wù)處理過(guò)程中額外的安全性,也會(huì)需要數(shù)據(jù)庫(kù)系統(tǒng)做更多的額外工作。一個(gè)實(shí)現(xiàn)了ACID的數(shù)據(jù)庫(kù),相比沒(méi)有實(shí)現(xiàn)ACID的數(shù)據(jù)庫(kù),通常會(huì)需要更強(qiáng)的CPU處理能力、更大的內(nèi)存和更多的磁盤(pán)空間。正如本章不斷重復(fù)的,這也正是MySQL的存儲(chǔ)引擎架構(gòu)可以發(fā)揮優(yōu)勢(shì)的地方。用戶可以根據(jù)業(yè)務(wù)是否需要事務(wù)處理,來(lái)選擇合適的存儲(chǔ)引擎。對(duì)于一些不需要事務(wù)的查詢類應(yīng)用,選擇一個(gè)非事務(wù)型的存儲(chǔ)引擎,可以獲得更高的性能。即使存儲(chǔ)引擎不支持事務(wù),也可以通過(guò)LOCK TABLES語(yǔ)句為應(yīng)用提供一定程度的保護(hù),這些選擇用戶都可以自主決定。
1.3.1 隔離級(jí)別
隔離性其實(shí)比想象的要復(fù)雜。在SQL標(biāo)準(zhǔn)中定義了四種隔離級(jí)別,每一種級(jí)別都規(guī)定了一個(gè)事務(wù)中所做的修改,哪些在事務(wù)內(nèi)和事務(wù)間是可見(jiàn)的,哪些是不可見(jiàn)的。較低級(jí)別的隔離通常可以執(zhí)行更高的并發(fā),系統(tǒng)的開(kāi)銷也更低。
??每種存儲(chǔ)引擎實(shí)現(xiàn)的隔離級(jí)別不盡相同。如果熟悉其他的數(shù)據(jù)庫(kù)產(chǎn)品,可能會(huì)發(fā)現(xiàn)某些特性和你期望的會(huì)有些不一樣(但本節(jié)不打算討論更詳細(xì)的內(nèi)容)。讀者可以根據(jù)所選擇的存儲(chǔ)引擎,查閱相關(guān)的手冊(cè)。
??下面簡(jiǎn)單地介紹一下四種隔離級(jí)別。
1. READ UNCOMMITTED(未提交讀)
在READ UNCOMMITTED級(jí)別,事務(wù)中的修改,即使沒(méi)有提交,對(duì)其他事務(wù)也都是可見(jiàn)的。事務(wù)可以讀取未提交的數(shù)據(jù),這也被稱為臟讀(Dirty Read)。這個(gè)級(jí)別會(huì)導(dǎo)致很多問(wèn)題,從性能上來(lái)說(shuō)READ UNCOMMITTED不會(huì)比其他的級(jí)別好太多,但卻缺乏其他級(jí)別的很多好處,除非真的有非常必要的理由,在實(shí)際應(yīng)用中一般很少使用。
2. READ COMMITTED(提交讀)
大多數(shù)數(shù)據(jù)庫(kù)系統(tǒng)的默認(rèn)隔離級(jí)別都是READ COMMITTED(但MySQL不是)。READ COMMITTED滿足前面提到的隔離性的簡(jiǎn)單定義:一個(gè)事務(wù)開(kāi)始時(shí),只能“看見(jiàn)”已經(jīng)提交的事務(wù)所做的修改。換句話說(shuō),一個(gè)事務(wù)從開(kāi)始直到提交之前,所做的任何修改對(duì)其他事務(wù)都是不可見(jiàn)的。這個(gè)級(jí)別有時(shí)候也叫做不可重復(fù)讀(nonrepeatable read),因?yàn)閮纱螆?zhí)行同樣的查詢,可能會(huì)得到不一樣的結(jié)果。
3. REPEATABLE READ(可重復(fù)讀)
REPEATABLE READ解決了臟讀的問(wèn)題。該級(jí)別保證了在同一個(gè)事務(wù)中多次讀取同樣記錄的結(jié)果是一致的。但是理論上,可重復(fù)讀隔離級(jí)別還是無(wú)法解決另外一個(gè)幻讀(Phantom Read)的問(wèn)題。所謂幻讀,指的是當(dāng)某個(gè)事務(wù)在讀取某個(gè)范圍內(nèi)的記錄時(shí),另外一個(gè)事務(wù)又在該范圍內(nèi)插人了新的記錄,當(dāng)之前的事務(wù)再次讀取該范圍的記錄時(shí),會(huì)產(chǎn)生幻行(Phantom Row)a InnoDB和XtraDB存儲(chǔ)引擎通過(guò)多版本并發(fā)控制(MVCC, Multiversion Concurrency Control)解決T幻讀的Ip7題。本章稍后會(huì)做進(jìn)一步的討論。
可重復(fù)讀是MySQL的默認(rèn)事務(wù)隔離級(jí)別。
4. SERIALIZABLE(可串行化)
SERIALIZABLE是最高的隔離級(jí)別。它通過(guò)強(qiáng)制事務(wù)串行執(zhí)行,避免了前面說(shuō)的幻讀 的問(wèn)題。簡(jiǎn)單來(lái)說(shuō),SERIALIZABLE會(huì)在讀取的每一行數(shù)據(jù)上都加鎖,所以可能導(dǎo)致大量的超時(shí)和鎖爭(zhēng)用的問(wèn)題。實(shí)際應(yīng)用中也很少用到這個(gè)隔離級(jí)別,只有在非常需要確保數(shù)據(jù)的一致性而且可以接受沒(méi)有并發(fā)的情況下,才考慮采用該級(jí)別。
1.3.2 死鎖
死鎖是指兩個(gè)或者多個(gè)事務(wù)在同一資源上相互占用,并請(qǐng)求鎖定對(duì)方占用的資源,從而導(dǎo)致惡性循環(huán)的現(xiàn)象。當(dāng)多個(gè)事務(wù)試圖以不同的順序鎖定資源時(shí),就可能會(huì)產(chǎn)生死鎖。多個(gè)事務(wù)同時(shí)鎖定同一個(gè)資源時(shí),也會(huì)產(chǎn)生死鎖。例如,設(shè)想下面兩個(gè)事務(wù)同時(shí)處理StockPrice表:
??事務(wù)1:
START TRANSACTION;
UPDATE P SET close = 45.50 WHERE id = 4;
UPDATE P SET close = 19.80 WHERE id = 3;
事務(wù)2:
START TRANSACTION;
UPDATE P SET close = 20.50 WHERE id = 4;
UPDATE P SET close = 15.80 WHERE id = 3;
如果湊巧,兩個(gè)事務(wù)都執(zhí)行了第一條UPDATE語(yǔ)句,更新了一行數(shù)據(jù),同時(shí)也鎖定了該行數(shù)據(jù),接著每個(gè)事務(wù)都嘗試去執(zhí)行第二條UPDATE語(yǔ)句,卻發(fā)現(xiàn)該行已經(jīng)被對(duì)方鎖定,然后兩個(gè)事務(wù)都等待對(duì)方釋放鎖,同時(shí)又持有對(duì)方需要的鎖,則陷入死循環(huán)。除非有外部因素介入才可能解除死鎖。
??為了解決這種問(wèn)題,數(shù)據(jù)庫(kù)系統(tǒng)實(shí)現(xiàn)了各種死鎖檢測(cè)和死鎖超時(shí)機(jī)制。越復(fù)雜的系統(tǒng),比如InnoDB存儲(chǔ)引擎,越能檢測(cè)到死鎖的循環(huán)依賴,并立即返回一個(gè)錯(cuò)誤。這種解決方式很有效,否則死鎖會(huì)導(dǎo)致出現(xiàn)非常慢的查詢。還有一種解決方式,就是當(dāng)查詢的時(shí)間達(dá)到鎖等待超時(shí)的設(shè)定后放棄鎖請(qǐng)求,這種方式通常來(lái)說(shuō)不太好。InnoDB目前處理死鎖的方法是,將持有最少行級(jí)排他鎖的事務(wù)進(jìn)行回滾(這是相對(duì)比較簡(jiǎn)單的死鎖回滾算法)。(筆者注:InnoBD好強(qiáng))
??鎖的行為和順序是和存儲(chǔ)引擎相關(guān)的。以同樣的順序執(zhí)行語(yǔ)句,有些存儲(chǔ)引擎會(huì)產(chǎn)生死鎖,有些則不會(huì)。死鎖的產(chǎn)生有雙重原因:有些是因?yàn)檎嬲臄?shù)據(jù)沖突,這種情況通常很難避免,但有些則完全是由于存儲(chǔ)引擎的實(shí)現(xiàn)方式導(dǎo)致的。
??死鎖發(fā)生以后,只有部分或者完全回滾其中一個(gè)事務(wù),才能打破死鎖。對(duì)于事務(wù)型的系統(tǒng),這是無(wú)法避免的,所以應(yīng)用程序在設(shè)計(jì)時(shí)必須考慮如何處理死鎖。大多數(shù)情況下只需要重新執(zhí)行因死鎖回滾的事務(wù)即可。
1.3.3 事務(wù)日志
事務(wù)日志可以幫助提高事務(wù)的效率。使用事務(wù)日志,存儲(chǔ)引擎在修改表的數(shù)據(jù)時(shí)只需要修改其內(nèi)存拷貝,再把該修改行為記錄到持久在硬盤(pán)上的事務(wù)日志中,而不用每次都將修改的數(shù)據(jù)本身持久到磁盤(pán)。事務(wù)日志采用的是追加的方式,因此寫(xiě)日志的操作是磁盤(pán)上一小塊區(qū)域內(nèi)的順序I/O,而不像隨機(jī)I/O需要在磁盤(pán)的多個(gè)地方移動(dòng)磁頭,所以采用事務(wù)日志的方式相對(duì)來(lái)說(shuō)要快得多。事務(wù)日志持久以后,內(nèi)存中被修改的數(shù)據(jù)在后臺(tái)可以慢慢地刷回到磁盤(pán)。目前大多數(shù)存儲(chǔ)引擎都是這樣實(shí)現(xiàn)的,我們通常稱之為預(yù)寫(xiě)式日志(Write-Ahead Logging ),修改數(shù)據(jù)需要寫(xiě)兩次磁盤(pán)。如果數(shù)據(jù)的修改已經(jīng)記錄到事務(wù)日志并持久化,但數(shù)據(jù)本身還沒(méi)有寫(xiě)回磁盤(pán),此時(shí)系統(tǒng)崩潰,存儲(chǔ)引擎在重啟時(shí)能夠自動(dòng)恢復(fù)這部分修改的數(shù)據(jù)。具體的恢復(fù)方式則視存儲(chǔ)引擎而定。(筆者注:主從庫(kù)雖然好,但是如果考慮到服務(wù)器價(jià)格,綜合考慮日志很好,但是公司沒(méi)怎么用,可能公司大牛不行)
1.3.4 MySQL中的事務(wù)
MySQL提供了兩種事務(wù)型的存儲(chǔ)引擎:InnoDB和NDB Cluster。另外還有一些第三方存儲(chǔ)引擎也支持事務(wù),比較知名的包括XtraDB和PBXT。后面將詳細(xì)討論它們各自的一些特點(diǎn)。
自動(dòng)提交(AUTOCOMMIT)
MySQL默認(rèn)采用自動(dòng)提交(AUTOCOMMIT)模式。也就是說(shuō),如果不是顯式地開(kāi)始一個(gè)事務(wù),則每個(gè)查詢都被當(dāng)作一個(gè)事務(wù)執(zhí)行提交操作。在當(dāng)前連接中,可以通過(guò)設(shè)置AUTOCOMMIT變量來(lái)啟用或者禁用自動(dòng)提交模式:
??1或者ON表示啟用,U或者OFF表示禁用。當(dāng)AUTOCOMMIT=0時(shí),所有的查詢都是在一個(gè)
事務(wù)中,直到顯式地執(zhí)行COMM工下提交或者ROLLBACK回滾,該事務(wù)結(jié)束,同時(shí)又開(kāi)始了另一個(gè)新事務(wù)。修改AUTOCOMMIT對(duì)非事務(wù)型的表,比如MyISAM或者內(nèi)存表,不會(huì)有任何影響。對(duì)這類表來(lái)說(shuō),沒(méi)有COMMIT或者ROLLBACK的概念,也可以說(shuō)是相當(dāng)于一直處于AUTOCOMMIT啟用的模式。
??另外還有一些命令,在執(zhí)行之前會(huì)強(qiáng)制執(zhí)行COMM工T提交當(dāng)前的活動(dòng)事務(wù)。典型的例子,在數(shù)據(jù)定義語(yǔ)言(DDL)中,如果是會(huì)導(dǎo)致大量數(shù)據(jù)改變的操作,比如ALTER TABLE,就是如此。另外還有LOCK TABLES等其他語(yǔ)句也會(huì)導(dǎo)致同樣的結(jié)果。如果有需要,請(qǐng)檢查對(duì)應(yīng)版本的官方文檔來(lái)確認(rèn)所有可能導(dǎo)致自動(dòng)提交的語(yǔ)句列表。(筆者注:不止是行鎖,還自動(dòng)提交。)
??MySQL可以通過(guò)執(zhí)行SET TRANSACTION ISOLATION LEVEL命令來(lái)設(shè)置隔離級(jí)別。新的隔離級(jí)別會(huì)在下一個(gè)事務(wù)開(kāi)始的時(shí)候生效。可以在配置文件中設(shè)置整個(gè)數(shù)據(jù)庫(kù)的隔離級(jí)別,也可以只改變當(dāng)前會(huì)話的隔離級(jí)別:
??MySQL能夠識(shí)別所有的4個(gè)ANSI隔離級(jí)別,InnoDB引擎也支持所有的隔離級(jí)別。
在事務(wù)中混合使用存儲(chǔ)引擎
MySQL服務(wù)器層不管理事務(wù),事務(wù)是由下層的存儲(chǔ)引擎實(shí)現(xiàn)的。所以在同一個(gè)事務(wù)中,使用多種存儲(chǔ)引擎是不可靠的。
??如果在事務(wù)中混合使用了事務(wù)型和非事務(wù)型的表(例如InnoDB和MyISAM表),在正常提交的情況下不會(huì)有什么問(wèn)題。
??但如果該事務(wù)需要回滾,非事務(wù)型的表上的變更就無(wú)法撤銷,這會(huì)導(dǎo)致數(shù)據(jù)庫(kù)處于不一致的狀態(tài),這種情況很難修復(fù),事務(wù)的最終結(jié)果將無(wú)法確定。所以,為每張表選擇合適的存儲(chǔ)引擎非常重要。
??在非事務(wù)型的表上執(zhí)行事務(wù)相關(guān)操作的時(shí)候,MySQL通常不會(huì)發(fā)出提醒,也不會(huì)報(bào)錯(cuò)。有時(shí)候只有回滾的時(shí)候才會(huì)發(fā)出一個(gè)警告:“某些非事務(wù)型的表上的變更不能被回滾”。但大多數(shù)情況下,對(duì)非事務(wù)型表的操作都不會(huì)有提示。
隱式和顯式鎖定
InnoDB采用的是兩階段鎖定協(xié)議(two-phase locking protocol )。在事務(wù)執(zhí)行過(guò)程中,隨時(shí)都可以執(zhí)行鎖定,鎖只有在執(zhí)行COMMIT或者ROLLBACK的時(shí)候才會(huì)釋放,并且所有的鎖是在同一時(shí)刻被釋放。前面描述的鎖定都是隱式鎖定,InnoDB會(huì)根據(jù)隔離級(jí)別在需要的時(shí)候自動(dòng)加鎖。
??另外,InnoDB也支持通過(guò)特定的語(yǔ)句進(jìn)行顯式鎖定,這些語(yǔ)句不屬于SQL規(guī)范:
??SELECT ... LOCK IN SHARE MODE
??SELECT ... FOR UPDATE
??MySQL也支持LOCK TABLES和UNLOCK TABLES語(yǔ)句,這是在服務(wù)器層實(shí)現(xiàn)的,和存儲(chǔ)引擎無(wú)關(guān)。它們有自己的用途,但并不能替代事務(wù)處理。如果應(yīng)用需要用到事務(wù),還是應(yīng)該選擇事務(wù)型存儲(chǔ)引擎。
??經(jīng)常可以發(fā)現(xiàn),應(yīng)用已經(jīng)將表從MyISAM轉(zhuǎn)換到InnoDB,但還是顯式地使用LOCK TABLES
語(yǔ)句。這不但沒(méi)有必要,還會(huì)嚴(yán)重影響性能,實(shí)際上InnoDB的行級(jí)鎖工作得更好。(筆者注:沒(méi)見(jiàn)過(guò)這個(gè)語(yǔ)句)
LOCK TABLES和事務(wù)之間相互影響的話,情況會(huì)變得非常復(fù)雜,在某些MySQL版本中甚至?xí)a(chǎn)生無(wú)法預(yù)料的結(jié)果。因此,本書(shū)建議,除了事務(wù)中禁用了AU下。COMMIT,可以使用LOCK TABLES之外,其他任何時(shí)候都不要顯式地執(zhí)行LOCK TABLES,不管使用的是什么存儲(chǔ)引擎。
1.4 多版本并發(fā)控制
MySQL的大多數(shù)事務(wù)型存儲(chǔ)引擎實(shí)現(xiàn)的都不是簡(jiǎn)單的行級(jí)鎖。基于提升并發(fā)性能的考慮,它們一般都同時(shí)實(shí)現(xiàn)了多版本并發(fā)控制(MVCC)。不僅是MySQL,包括Oracle、PostgreSQL等其他數(shù)據(jù)庫(kù)系統(tǒng)也都實(shí)現(xiàn)了MVCC,但各自的實(shí)現(xiàn)機(jī)制不盡相同,因?yàn)镸VCC沒(méi)有一個(gè)統(tǒng)一的實(shí)現(xiàn)標(biāo)準(zhǔn)。
??可以認(rèn)為MVCC是行級(jí)鎖的一個(gè)變種,但是它在很多情況下避免了加鎖操作,因此開(kāi)銷更低。雖然實(shí)現(xiàn)機(jī)制有所不同,但大都實(shí)現(xiàn)了非阻塞的讀操作,寫(xiě)操作也只鎖定必要的行。
??MVCC的實(shí)現(xiàn),是通過(guò)保存數(shù)據(jù)在某個(gè)時(shí)間點(diǎn)的快照來(lái)實(shí)現(xiàn)的。也就是說(shuō),不管需要執(zhí)行多長(zhǎng)時(shí)間,每個(gè)事務(wù)看到的數(shù)據(jù)都是一致的。根據(jù)事務(wù)開(kāi)始的時(shí)間不同,每個(gè)事務(wù)對(duì)同一張表,同一時(shí)刻看到的數(shù)據(jù)可能是不一樣的。如果之前沒(méi)有這方面的概念,這句話聽(tīng)起來(lái)就有點(diǎn)迷惑。熟悉了以后會(huì)發(fā)現(xiàn),這句話其實(shí)還是很容易理解的。
??前面說(shuō)到不同存儲(chǔ)引擎的MVCC實(shí)現(xiàn)是不同的,典型的有樂(lè)觀(optimistic)并發(fā)控制和悲觀(pessimistic)并發(fā)控制。下面我們通過(guò)InnoDB的簡(jiǎn)化版行為來(lái)說(shuō)明MVCC是如何工作的。
??InnoDB的MVCC,是通過(guò)在每行記錄后面保存兩個(gè)隱藏的列來(lái)實(shí)現(xiàn)的。這兩個(gè)列,一個(gè)保存了行的創(chuàng)建時(shí)間,一個(gè)保存行的過(guò)期時(shí)間(或刪除時(shí)間)。當(dāng)然存儲(chǔ)的并不是實(shí)際的時(shí)間值,而是系統(tǒng)版本號(hào)(system version number)。每開(kāi)始一個(gè)新的事務(wù),系統(tǒng)版本號(hào)都會(huì)自動(dòng)遞增。事務(wù)開(kāi)始時(shí)刻的系統(tǒng)版本號(hào)會(huì)作為事務(wù)的版本號(hào),用來(lái)和查詢到的每行記錄的版本號(hào)進(jìn)行比較。下面看一下在REPEATABLE READ隔離級(jí)別下, MVCC具體是如何操作的。
SELECT
InnoDB會(huì)根據(jù)以下兩個(gè)條件檢查每行記錄:
??a. InnoDB只查找版本早于當(dāng)前事務(wù)版本的數(shù)據(jù)行(也就是,行的系統(tǒng)版本號(hào)小于或等于事務(wù)的系統(tǒng)版本號(hào)),這樣可以確保事務(wù)讀取的行,要么是在事務(wù)開(kāi)始前已經(jīng)存在的,要么是事務(wù)自身插入或者修改過(guò)的。
??b.行的刪除版本要么未定義,要么大于當(dāng)前事務(wù)版本號(hào)。這可以確保事務(wù)讀取到的行,在事務(wù)開(kāi)始之前未被刪除。只有符合上述兩個(gè)條件的記錄,才能返回作為查詢結(jié)果。
INSERT
InnoDB為新插入的每一行保存當(dāng)前系統(tǒng)版本號(hào)作為行版本號(hào)。
DELETE
InnoDB為刪除的每一行保存當(dāng)前系統(tǒng)版本號(hào)作為行刪除標(biāo)識(shí)。(筆者注:不是完全刪除嗎?)
UPDATE
InnoDB為插入一行新記錄,保存當(dāng)前系統(tǒng)版本號(hào)作為行版本號(hào),同時(shí)保存當(dāng)前系統(tǒng)版本號(hào)到原來(lái)的行作為行刪除標(biāo)識(shí)。(筆者注:原來(lái)行?)
??保存這兩個(gè)額外系統(tǒng)版本號(hào),使大多數(shù)讀操作都可以不用加鎖。這樣設(shè)計(jì)使得讀數(shù)據(jù)操作很簡(jiǎn)單,性能很好,并且也能保證只會(huì)讀取到符合標(biāo)準(zhǔn)的行。不足之處是每行記錄都需要額外的存儲(chǔ)空間,需要做更多的行檢查工作,以及一些額外的維護(hù)工作。
??MVCC只在REPEATABLE READ和READ CONONITTED兩個(gè)隔離級(jí)別下工作。其他兩個(gè)隔離級(jí)別都和MVCC不兼容,因?yàn)镽EAD UNCONP}IITTED總是讀取最新的數(shù)據(jù)行,而不是符合當(dāng)前事務(wù)版本的數(shù)據(jù)行。而SERIALIZABLE則會(huì)對(duì)所有讀取的行都加鎖。
1.5 MySQL的存儲(chǔ)引擎
本節(jié)只是概要地描述MySQL的存儲(chǔ)引擎,而不會(huì)涉及太多細(xì)節(jié)。因?yàn)殛P(guān)于存儲(chǔ)引擎的討論及其相關(guān)特性將會(huì)貫穿全書(shū),而且本書(shū)也不是存儲(chǔ)引擎的完全指南,所以有必要閱讀相關(guān)存儲(chǔ)引擎的官方文檔。
??在文件系統(tǒng)中,MySQL將每個(gè)數(shù)據(jù)庫(kù)(也可以稱之為schema)保存為數(shù)據(jù)目錄下的一個(gè)子目錄。創(chuàng)建表時(shí),MySQL會(huì)在數(shù)據(jù)庫(kù)子目錄下創(chuàng)建一個(gè)和表同名的.. frm文件保存表的定義。例如創(chuàng)建一個(gè)名為MyTabte的表,MySQL會(huì)在MyTable.frm文件中保存該表的定義。因?yàn)镸ySQL使用文件系統(tǒng)的目錄和文件來(lái)保存數(shù)據(jù)庫(kù)和表的定義,大小寫(xiě)敏感性和具體的平臺(tái)密切相關(guān)。在Windows中,大小寫(xiě)是不敏感的,而在類Unix中則是敏感的。不同的存儲(chǔ)引擎保存數(shù)據(jù)和索引的方式是不同的,但表的定義則是在MySQL服務(wù)層統(tǒng)一處理的。(筆者注:看到了frm文件,但是打開(kāi)亂碼,不知道怎么打開(kāi)?)
??可以使用SHOW TABLE STATUS命令(在MySQL 5.0以后的版本中,也可以查詢INFORMATION_ SCHEMA中對(duì)應(yīng)的表)顯示表的相關(guān)信息。例如,對(duì)于mysqt數(shù)據(jù)庫(kù)中的user表:
Name
表名。
Engine
表的存儲(chǔ)引擎類型。在舊版本中,該列的名字叫Type,而不是Engineo
Row format
行的格式。對(duì)于MyISAM表,可選的值為Dynamic, Fixed或者Compressed,Dynamic的行長(zhǎng)度是可變的,一般包含可變長(zhǎng)度的字段,如VARCHAR或BLOBo Fixed的行長(zhǎng)度則是固定的,只包含固定長(zhǎng)度的列,如CHAR和INTEGER。Compressed的行則只在壓縮表中存在,請(qǐng)參考第19頁(yè)“MyISAM壓縮表”一節(jié)。
Rows
表中的行數(shù)。對(duì)于MyISAM和其他一些存儲(chǔ)引擎,該值是精確的,但對(duì)于InnoDB ,該值是估計(jì)值。
Avg_row length
平均每行包含的字節(jié)數(shù)。
Data_tength
表數(shù)據(jù)的大小(以字節(jié)為單位)。
Max_data_tength
表數(shù)據(jù)的最大容量,該值和存儲(chǔ)引擎有關(guān)。
Index_tength
索引的大小(以字節(jié)為單位)。
Data free
對(duì)于MyISAM表,表示已分配但目前沒(méi)有使用的空間。這部分空間包括了之前刪除的行,以及后續(xù)可以被INSERT利用到的空間。
Auto increment
下一個(gè)AUTO INCREMENT的值。
Create time
表的創(chuàng)建時(shí)間。
Update time
表數(shù)據(jù)的最后修改時(shí)間。
Check time
使用CKECK TABLE命令或者myisamchk工具最后一次檢查表的時(shí)間。
Collation
表的默認(rèn)字符集和字符列排序規(guī)則。
Checksum
如果啟用,保存的是整個(gè)表的實(shí)時(shí)校驗(yàn)和。
Create_options
創(chuàng)建表時(shí)指定的其他選項(xiàng)。
Comment
該列包含了一些其他的額外信息。對(duì)于MyISAM表,保存的是表在創(chuàng)建時(shí)帶的注釋。對(duì)于InnoDB表,則保存的是InnoDB表空間的剩余空間信息。如果是一個(gè)視圖,則該列包含“VIEW”的文本字樣。
1.5.1 InnoDB存儲(chǔ)引擎
InnoDB是MySQL的默認(rèn)事務(wù)型引擎,也是最重要、使用最廣泛的存儲(chǔ)引擎。它被設(shè)計(jì)用來(lái)處理大量的短期(short-lived)事務(wù),短期事務(wù)大部分情況是正常提交的,很少會(huì)被回滾。InnoDB的性能和自動(dòng)崩潰恢復(fù)特性,使得它在非事務(wù)型存儲(chǔ)的需求中也很流行。除非有非常特別的原因需要使用其他的存儲(chǔ)引擎,否則應(yīng)該優(yōu)先考慮InnoDB引擎。如果要學(xué)習(xí)存儲(chǔ)引擎,InnoDB也是一個(gè)非常好的值得花最多的時(shí)間去深入學(xué)習(xí)的對(duì)象,收益肯定比將時(shí)間平均花在每個(gè)存儲(chǔ)引擎的學(xué)習(xí)上要高得多。
InnoDB的歷史
InnoDB有著復(fù)雜的發(fā)布?xì)v史,了解一下這段歷史對(duì)于理解InnoDB很有幫助。2008年,發(fā)布了所謂的InnoDB plugin,適用于MySQL 5.1版本,但這是Oracle創(chuàng)建的下一代InnoDB引擎,其擁有者是InnoDB而不是MySQL。這基于很多原因,這些原因如果要一一道來(lái),恐怕得喝掉好幾桶啤酒。MySQL默認(rèn)還是選擇了集成舊的InnoDB引擎。當(dāng)然用戶可以自行選擇使用新的性能更好、擴(kuò)展性更佳的InnoDB plugin來(lái)覆蓋舊的版本。直到最后,在Oracle收購(gòu)TSun公司后發(fā)布的MySQL 5.5中才徹底使用InnoDB plugin替代了舊版本的InnoDB(是的,這也意味著InnoDB plugin已經(jīng)是原生編譯了,而不是編譯成一個(gè)插件,但名字已經(jīng)約定俗成很難更改)。
??這個(gè)現(xiàn)代的InnoDB版本,也就是MySQL 5.1中所謂的InnoDB plugin,支持一些新特性,諸如利用排序創(chuàng)建索引(building index by sorting)、刪除或者增加索引時(shí)不需要復(fù)制全表數(shù)據(jù)、新的支持壓縮的存儲(chǔ)格式、新的大型列值如BLOB的存儲(chǔ)方式,以及文件格式管理等。很多用戶在MySQL 5.1中沒(méi)有使用nnoDB plugin,或許是因?yàn)樗麄儧](méi)有注意到有這個(gè)區(qū)別。所以如果你使用的是MySQL 5.1,一定要使用InnoDB plugin,真的比舊版本的InnoDB要好很多。
??InnoDB是一個(gè)很重要的存儲(chǔ)引擎,很多個(gè)人和公司都對(duì)其貢獻(xiàn)代碼,而不僅僅是Oracle公司的開(kāi)發(fā)團(tuán)隊(duì)。一些重要的貢獻(xiàn)者包括Google, Yasufumi Kinoshita, Percona,Facebook等,他們的一些改進(jìn)被直接移植到官方版本,也有一些由InnoDB團(tuán)隊(duì)重新實(shí)現(xiàn)。在過(guò)去的幾年間,InnoDB的改進(jìn)速度大大加快,主要的改進(jìn)集中在可測(cè)量性、可擴(kuò)展性、可配置化、性能各種新特性和對(duì)Window:的支持等方面。
InnoDB概覽
InnoDB的數(shù)據(jù)存儲(chǔ)在表空間(tablespace)中,表空間是由InnoDB管理的一個(gè)黑盒子,由一系列的數(shù)據(jù)文件組成。在MySQL 4.1以后的版本中,InnoDB可以將每個(gè)表的數(shù)據(jù)和索引存放在單獨(dú)的文件中。InnoDB也可以使用裸設(shè)備作為表空間的存儲(chǔ)介質(zhì),但現(xiàn)代的文件系統(tǒng)使得裸設(shè)備不再是必要的選擇。
??InnoDB采用MVCC來(lái)支持高并發(fā),并且實(shí)現(xiàn)了四個(gè)標(biāo)準(zhǔn)的隔離級(jí)別。其默認(rèn)級(jí)別是REPEATABLE READ(可重復(fù)讀),并且通過(guò)間隙鎖(next-key locking)策略防止幻讀的出現(xiàn)。間隙鎖使得InnoDB不僅僅鎖定查詢涉及的行,還會(huì)對(duì)索引中的間隙進(jìn)行鎖定,以防止幻影行的插入。
??InnoDB表是基于聚簇索引建立的,我們會(huì)在后面的章節(jié)詳細(xì)討論聚簇索引。InnoDB的索引結(jié)構(gòu)和MySQL的其他存儲(chǔ)引擎有很大的不同,聚簇索引對(duì)主鍵查詢有很高的性能。不過(guò)它的二級(jí)索引(secondary index,非主鍵索引)中必須包含主鍵列,所以如果主鍵列很大的話,其他的所有索引都會(huì)很大。因此,若表上的索引較多的話,主鍵應(yīng)當(dāng)盡可能的小。InnoDB的存儲(chǔ)格式是平臺(tái)獨(dú)立的,也就是說(shuō)可以將數(shù)據(jù)和索引文件從Intel平臺(tái)復(fù)制到PowerPC或者Sun SPARC平臺(tái)。
??InnoDB內(nèi)部做了很多優(yōu)化,包括從磁盤(pán)讀取數(shù)據(jù)時(shí)采用的可預(yù)測(cè)性預(yù)讀,能夠自動(dòng)在內(nèi)存中創(chuàng)建hash索引以加速讀操作的自適應(yīng)哈希索引(adaptive hash index),以及能夠加速插入操作的插入緩沖區(qū)(insert buffer)等。本書(shū)后面將更詳細(xì)地討論這些內(nèi)容。
??InnoDB的行為是非常復(fù)雜的,不容易理解。如果使用了InnoDB引擎,筆者強(qiáng)烈建議閱讀官方手冊(cè)中的“InnoDB事務(wù)模型和鎖”一節(jié)。如果應(yīng)用程序基于InnoDB構(gòu)建,則事先了解一下InnoDB的MVCC架構(gòu)帶來(lái)的一些微妙和細(xì)節(jié)之處是非常有必要的。存儲(chǔ)引擎要為所有用戶甚至包括修改數(shù)據(jù)的用戶維持一致性的視圖,是非常復(fù)雜的工作。
??作為事務(wù)型的存儲(chǔ)引擎,InnoDB通過(guò)一些機(jī)制和工具支持真正的熱備份,Oracle提供的MySQL Enterprise Backup, Percona提供的開(kāi)源的XtraBackup都可以做到這一點(diǎn)。MySQL的其他存儲(chǔ)引擎不支持熱備份,要獲取一致性視圖需要停止對(duì)所有表的寫(xiě)入,而在讀寫(xiě)混合場(chǎng)景中,停止寫(xiě)入可能也意味著停止讀取。
1.5.2 MyISAM存儲(chǔ)引擎
在MySQL 5.1及之前的版本,MyISAM是默認(rèn)的存儲(chǔ)引擎。MyISAM提供了大量的特性,包括全文索引、壓縮、空間函數(shù)(GIS)等,但MyISAM不支持事務(wù)和行級(jí)鎖,而且有一個(gè)毫無(wú)疑問(wèn)的缺陷就是崩潰后無(wú)法安全恢復(fù)。正是由于MyISAM引擎的緣故,即使MySQL支持事務(wù)已經(jīng)很長(zhǎng)時(shí)間了,在很多人的概念中MySQL還是非事務(wù)型的數(shù)據(jù)庫(kù)。盡管MyISAM引擎不支持事務(wù)、不支持崩潰后的安全恢復(fù),但它絕不是一無(wú)是處的。對(duì)于只讀的數(shù)據(jù),或者表比較小、可以忍受修復(fù)(repair)操作,則依然可以繼續(xù)使用MyISAM(但請(qǐng)不要默認(rèn)使用MyISAM,而是應(yīng)當(dāng)默認(rèn)使用InnoDB ) (筆者注:不支持事務(wù)?基本不考慮了)
存儲(chǔ)
MyISAM會(huì)將表存儲(chǔ)在兩個(gè)文件中:數(shù)據(jù)文件和索引文件,分別以.MYD和.MYI為擴(kuò)展名。MyISAM表可以包含動(dòng)態(tài)或者靜態(tài)(長(zhǎng)度固定)行。MySQL會(huì)根據(jù)表的定義來(lái)決定采用何種行格式。MyISAM表可以存儲(chǔ)的行記錄數(shù),一般受限于可用的磁盤(pán)空間,或者操作系統(tǒng)中單個(gè)文件的最大尺寸。
??在MySQL 5.0中,MyISAM表如果是變長(zhǎng)行,則默認(rèn)配置只能處理256TB的數(shù)據(jù),因?yàn)橹赶驍?shù)據(jù)記錄的指針長(zhǎng)度是6個(gè)字節(jié)。而在更早的版本中,指針長(zhǎng)度默認(rèn)是4字節(jié),所以只能處理4GB的數(shù)據(jù)。而所有的MySQL版本都支持8字節(jié)的指針。要改變MyISAM表指針的長(zhǎng)度(調(diào)高或者調(diào)低),可以通過(guò)修改表的MAX_ROWS和AVG_ROW_LENGTH選項(xiàng)的值來(lái)實(shí)現(xiàn),兩者相乘就是表可能達(dá)到的最大大小。修改這兩個(gè)參數(shù)會(huì)導(dǎo)致重建整個(gè)表和表的所有索引,這可能需要很長(zhǎng)的時(shí)間才能完成。
MyISAM特性
作為MySQL最早的存儲(chǔ)引擎之一,MyISAM有一些已經(jīng)開(kāi)發(fā)出來(lái)很多年的特性,可以滿足用戶的實(shí)際需求。
1.加鎖與并發(fā)
MyISAM對(duì)整張表加鎖,而不是針對(duì)行。讀取時(shí)會(huì)對(duì)需要讀到的所有表加共享鎖,寫(xiě)入時(shí)則對(duì)表加排他鎖。但是在表有讀取查詢的同時(shí),也可以往表中插入新的記錄(這被稱為并發(fā)插入,CONCURRENT INSERT ) 。
2.修復(fù)
對(duì)于MyISAM表,MySQL可以手工或者自動(dòng)執(zhí)行檢查和修復(fù)操作,但這里說(shuō)的修復(fù)和事務(wù)恢復(fù)以及崩潰恢復(fù)是不同的概念。執(zhí)行表的修復(fù)可能導(dǎo)致一些數(shù)據(jù)丟失,而且修復(fù)操作是非常慢的。可以通過(guò)CHECK TABLE mytable檢查表的錯(cuò)誤,如果有錯(cuò)誤可以通過(guò)執(zhí)行REPAIR TABLE mytable進(jìn)行修復(fù)。另外,如果MySQL服務(wù)器已經(jīng)關(guān)閉,也可以通過(guò)myisamchk命令行工具進(jìn)行檢查和修復(fù)操作。
3.索引特性
對(duì)于MyISAM表,即使是BLOB和TEXT等長(zhǎng)字段,也可以基于其前500個(gè)字符創(chuàng)建索引。MyISAM也支持全文索引,這是一種基于分詞創(chuàng)建的索引,可以支持復(fù)雜的查詢。關(guān)于索引的更多信息請(qǐng)參考第5章。
4.延遲更新索引健(Delayed Key Write)
創(chuàng)建MyISAM表的時(shí)候,如果指定了DELAY KEY WRITE選項(xiàng),在每次修改執(zhí)行完成時(shí),不會(huì)立刻將修改的索引數(shù)據(jù)寫(xiě)入磁盤(pán),而是會(huì)寫(xiě)到內(nèi)存中的鍵緩沖區(qū)(in-memory key buffer),只有在清理鍵緩沖區(qū)或者關(guān)閉表的時(shí)候才會(huì)將對(duì)應(yīng)的索引塊寫(xiě)人到磁盤(pán)。這種方式可以極大地提升寫(xiě)入性能,但是在數(shù)據(jù)庫(kù)或者主機(jī)崩潰時(shí)會(huì)造成索引損壞,需要執(zhí)行修復(fù)操作。延遲更新索引鍵的特性,可以在全局設(shè)置,也可以為單個(gè)表設(shè)置。
MyISAM壓縮表
如果表在創(chuàng)建并導(dǎo)入數(shù)據(jù)以后,不會(huì)再進(jìn)行修改操作,那么這樣的表或許適合采用MyISAM壓縮表。
??可以使用myisampack對(duì)MyISAM表進(jìn)行壓縮(也叫打包pack)。壓縮表是不能進(jìn)行修改的(除非先將表解除壓縮,修改數(shù)據(jù),然后再次壓縮)。壓縮表可以極大地減少磁盤(pán)空間占用,因此也可以減少磁盤(pán)I/O,從而提升查詢性能。壓縮表也支持索引,但索引也是只讀的。
以現(xiàn)在的硬件能力,對(duì)大多數(shù)應(yīng)用場(chǎng)景,讀取壓縮表數(shù)據(jù)時(shí)的解壓帶來(lái)的開(kāi)銷影響并不大,而減少I(mǎi)/O帶來(lái)的好處則要大得多。壓縮時(shí)表中的記錄是獨(dú)立壓縮的,所以讀取單行的時(shí)候不需要去解壓整個(gè)表(甚至也不解壓行所在的整個(gè)頁(yè)面)。
MyISAM性能
MyISAM引擎設(shè)計(jì)簡(jiǎn)單,數(shù)據(jù)以緊密格式存儲(chǔ),所以在某些場(chǎng)景下的性能很好。MyISAM有一些服務(wù)器級(jí)別的性能擴(kuò)展限制,比如對(duì)索引鍵緩沖區(qū)(key cache)的Mutex鎖,MariaDB基于段(segment )的索引鍵緩沖區(qū)機(jī)制來(lái)避免該問(wèn)題。但MyISAM最典型的性能問(wèn)題還是表鎖的問(wèn)題,如果你發(fā)現(xiàn)所有的查詢都長(zhǎng)期處于“Locked"狀態(tài),那么毫無(wú)疑問(wèn)表鎖就是罪魁禍?zhǔn)住?/p>
1.5.3 MySQL內(nèi)建的其他存儲(chǔ)引擎
1.5.4 第三方存儲(chǔ)引擎
1.5.5 選擇合適的引擎
這么多存儲(chǔ)引擎,我們?cè)趺催x擇?大部分情況下,InnoDB都是正確的選擇,所以O(shè)racle在MySQL 5.5版本時(shí)終于將InnoDB作為默認(rèn)的存儲(chǔ)引擎了。對(duì)于如何選擇存儲(chǔ)引擎,可以簡(jiǎn)單地歸納為一句話:“除非需要用到某些InnoDB不具備的特性,并且沒(méi)有其他辦法可以替代,否則都應(yīng)該優(yōu)先選擇InnoDB引擎”。例如,如果要用到全文索引,建議優(yōu)先考慮InnoDB加上Sphinx的組合,而不是使用支持全文素引的MyISAM。當(dāng)然,如果不需要用到InnoDB的特性,同時(shí)其他引擎的特性能夠更好地滿足需求,也可以考慮一下其他存儲(chǔ)引擎。舉個(gè)例子,如果不在乎可擴(kuò)展能力和并發(fā)能力,也不在乎崩潰后的數(shù)據(jù)丟失問(wèn)題,卻對(duì)InnoDB的空間占用過(guò)多比較敏感,這種場(chǎng)合下選擇MyISAM就比較合適。
1.5.6 轉(zhuǎn)換表的引擎
??ALTER TABLE
??上述語(yǔ)法可以適用任何存儲(chǔ)引擎。但有一個(gè)問(wèn)題:需要執(zhí)行很長(zhǎng)時(shí)間。MySQL會(huì)按行將數(shù)據(jù)從原表復(fù)制到一張新的表中,在復(fù)制期間可能會(huì)消耗系統(tǒng)所有的I/O能力,同時(shí)原表上會(huì)加上讀鎖。所以,在繁忙的表上執(zhí)行此操作要特別小心。一個(gè)替代方案是采用接下來(lái)將討論的導(dǎo)出與導(dǎo)入的方法,手工進(jìn)行表的復(fù)制。如果轉(zhuǎn)換表的存儲(chǔ)引擎,將會(huì)失去和原引擎相關(guān)的所有特性。例如,如果將一張InnoDB表轉(zhuǎn)換為MyISAM,然后再轉(zhuǎn)換回InnoDB,原InnoDB表上所有的外鍵將丟失。
??導(dǎo)出與導(dǎo)入
??為了更好地控制轉(zhuǎn)換的過(guò)程,可以使用mysqldump工具將數(shù)據(jù)導(dǎo)出到文件,然后修改文件中CREATE TABLE語(yǔ)句的存儲(chǔ)引擎選項(xiàng),注意同時(shí)修改表名,因?yàn)橥粋€(gè)數(shù)據(jù)庫(kù)中不能存在相同的表名,即使它們使用的是不同的存儲(chǔ)引擎。同時(shí)要注意mysqldump默認(rèn)會(huì)自動(dòng)在CREATE TABLE語(yǔ)句前加上。ROP TABLE語(yǔ)句,不注意這一點(diǎn)可能會(huì)導(dǎo)致數(shù)據(jù)丟失。
??創(chuàng)建與查詢(CREATE和SELECT)
??第三種轉(zhuǎn)換的技術(shù)綜合了第一種方法的高效和第二種方法的安全。不需要導(dǎo)出整個(gè)表的數(shù)據(jù),而是先創(chuàng)建一個(gè)新的存儲(chǔ)引擎的表,然后利用INSERT···SELECT語(yǔ)法來(lái)導(dǎo)數(shù)據(jù):
??數(shù)據(jù)量不大的話,這樣做工作得很好。如果數(shù)據(jù)量很大,則可以考慮做分批處理,每一段數(shù)據(jù)執(zhí)行事務(wù)提交操作,以避免大事務(wù)產(chǎn)生過(guò)多的重復(fù)運(yùn)行以下語(yǔ)句(最小值x和最大值y進(jìn)行相應(yīng)的替換)undo。假設(shè)有主鍵字段針對(duì)id,將數(shù)據(jù)導(dǎo)入到新表:
START TRANSACTION;
INSERT INTO innodb_table SELECT * FROM user WHERE id BETWEEN x AND y;
COMMIT;
??這樣操作完成以后,新表是原表的一個(gè)全量復(fù)制,原表還在,如果需要可以刪除原表。如果有必要,可以在執(zhí)行的過(guò)程中對(duì)原表加鎖,以確保新表和原表的數(shù)據(jù)一致。Percona Toolkit提供了一個(gè)pt-online-schema-change的工具(基于Facebook的在線schema變更技術(shù)),可以比較簡(jiǎn)單、方便地執(zhí)行上述過(guò)程,避免手工操作可能導(dǎo)致的失誤和煩瑣。
1.6 MySQL時(shí)間線(Timeline)
1.7 MySQL的開(kāi)發(fā)模式
1.8 總結(jié)
??MySQL擁有分層的架構(gòu)。上層是服務(wù)器層的服務(wù)和查詢執(zhí)行引擎,下層則是存儲(chǔ)引擎。雖然有很多不同作用的插件API,但存儲(chǔ)引擎API還是最重要的。如果能理解MySQL在存儲(chǔ)引擎和服務(wù)層之間處理查詢時(shí)如何通過(guò)API來(lái)回交互,就能抓住MySQL的核心基礎(chǔ)架構(gòu)的精髓。
??MySQL最初基于ISAM構(gòu)建(后來(lái)被MyISAM取代),其后陸續(xù)添加了更多的存儲(chǔ)引擎和事務(wù)支持。MySQL有一些怪異的行為是由于歷史遺留導(dǎo)致的。例如,在執(zhí)行ALTER TABLE時(shí),MySQL提交事務(wù)的方式是由于存儲(chǔ)引擎的架構(gòu)直接導(dǎo)致的,并且數(shù)據(jù)字典也保存在frm文件中(這并不是說(shuō)InnoDB會(huì)導(dǎo)致ALTER變成非事務(wù)型的。對(duì)于InnoDB來(lái)說(shuō),所有的操作都是事務(wù))。
??當(dāng)然,存儲(chǔ)引擎API的架構(gòu)也有一些缺點(diǎn)。有時(shí)候選擇多并非好事,而在MySQL 5.0和MySQL 5.1中有太多的存儲(chǔ)引擎可以選擇。InnoDB對(duì)于95%以上的用戶來(lái)說(shuō)都是最佳選擇,所以其他的存儲(chǔ)引擎可能只是讓事情變得復(fù)雜難搞,當(dāng)然也不可否認(rèn)某些情況下某些存儲(chǔ)引擎能更好地滿足需求。
??Oracle一開(kāi)始收購(gòu)了InnoDB,之后又收購(gòu)了MySQL,在同一個(gè)屋檐下對(duì)于兩者都是有
利的。InnoDB和MySQL服務(wù)器之間可以更快地協(xié)同發(fā)展。MySQL依然基于GPL協(xié)議開(kāi)放全部源代碼,社區(qū)和客戶都可以獲得堅(jiān)固而穩(wěn)定的數(shù)據(jù)庫(kù),MySQL正在變得越來(lái)越可擴(kuò)展和有用。
??筆者注:三層邏輯架構(gòu),讀寫(xiě)鎖(shared lock,exclusive lock),表鎖行鎖,事務(wù)的ACID(atomicity、consistency、isolation、durabillity),四大隔離級(jí)別,InnoDB默認(rèn)隔離級(jí)別,MVCC基本原理,命令:SHOW TABLE STATUS,以及轉(zhuǎn)換引擎的三種方式。