一、什麼是MySQL?
MySQL是現在市面上用的最廣的關係型數據庫。簡單理解也就是存放數據的倉庫。關於關係型數據庫和非關係型數據庫可以點擊這裏查看之前博客。
1、邏輯架構圖
最上層服務不是MySQL所獨有的,大多數基於網絡的客戶端/服務器都通過這種方式實現。其中每個客戶端連接都會在服務器進程中擁有一個線程,該客戶端的查詢操作在這個線程中執行,其中服務器會緩存線程,因此不需要對每一條客戶端連接都創建或銷燬線程。新版的MySQL提供了一個API,支持線程池插件,可以使用少量線程服務大量的客戶端連接。
第二層架構是MySQL最重要的一部分,MySQL的大多數功能如:解析、分析、優化、緩存、函數等等都在這一層實現。
第三層包括存儲引擎,存儲引擎負責數據的存儲和讀取。其中它通過API和服務器通信,不同存儲引擎之間不會相同通信,並且絕大多數存儲引擎不會解析SQL,InnoDB存儲引擎因爲需要解析外鍵,因此它可以解析SQL。
2、併發控制
任何時刻,只要有多個查詢在同一時刻修改數據,都會產生併發控制問題。MySQL分別在服務器層和存儲引擎層保證併發。
2.1、鎖
爲了防止兩個線程同時操作同一塊資源,可以在一個線程執行過程中將這塊資源“鎖”住,被鎖住的資源只有自己可以操作,這樣可就可以保證併發的安全,這也就是鎖的意義。
2.1.1、讀寫鎖
從數據庫中讀取數據是不會產生併發問題的,即使同一時刻有多條線程讀取也不會什麼問題,因爲讀取不會修改數據,但是如果一個線程在讀取,另一個線程在修改,這樣就會產生無法控制的結果,可能讀取到修改前的數據,也可能讀取到修改後的數據。
解決這種問題其實也非常簡單,我們可以定義兩種類型的鎖:共享鎖和排他鎖,也可以稱爲讀鎖和寫鎖。
- 讀鎖是共享的,或者說相互不阻塞的,也就是說即使同時有多個線程讀取同一塊資源,也互不干擾
- 寫鎖是排他的,或者說相互阻塞的,也就是說一個寫鎖會阻塞其他讀鎖和寫鎖
2.1.2、鎖策略
我們知道數據庫加鎖是需要額外耗費資源的,判斷是否加鎖,獲取鎖,釋放鎖等操作都是需要額外的開銷。鎖的粒度越大,這部分開銷越少,但是相對應系統的性能也就越差,鎖的粒度越小,鎖開銷也就越大,系統性能也就越好。
鎖策略就是在開銷和性能之間尋找一個平衡點。
MySQL提供多種鎖策略選擇。不同的鎖策略選擇會給系統帶來不同的性能,一般情況下,在不同的應用場景下使用不同的鎖策略以保證好的性能。現在有兩種比較流行的鎖策略:表鎖 和 行級鎖。
表鎖:表鎖是MySQL中最基本的鎖策略,並且是開銷最小的鎖。顧名思義,表鎖的處理方式爲:它會鎖定整張表,一個用戶對錶進行寫操作(插入、刪除、更新)前,需要先獲取寫鎖,這會阻止其他用戶對該表的讀寫操作。
行級鎖:行級鎖可以最大程度的支持併發處理(同時帶來大量的鎖開銷)。行級鎖的處理方式爲,它會鎖定當前用戶正在操作的那一行,保證同一時刻只有一個用戶可以操作這一行。行級鎖只在存儲引擎層實現,MySQL服務層沒有實現。其中InnoDB支持該鎖策略。
3、事務
事務就是一組原子性的SQL查詢,或者說一個獨自的工作單元。如果數據庫儲存引擎能夠成功執行該組查詢的所有SQL語句,那麼就執行該組查詢,如果其中一條語句因爲崩潰或其他原因無法執行,那麼所有語句都不會執行。也就是說:事務內的語句,要麼全部執成功,要麼全部執行失敗。
3.1、ACID
ACID分別表示原子性(atomicity)、一致性(consistency)、隔離性(isolation)和持久性(duarbility),一個運行良好的事務必須具體這些標準特徵。
- 原子性:一個事務必須被視爲不可分割的最小單元,整個事務要麼全部提交成功,要麼全部失敗回滾
- 一致性:數據庫總是從一個一致性狀態轉移到另一個一致性狀態
- 隔離性:一個事務所做的修改操作在最終提交之前,對其他事務是不可見的
- 持久性:一旦事務提交,則其所做的修改就會永久性的保存在數據庫中,此時即使系統崩潰,修改數據也不會丟失
事務的ACID特性可以保證事務的正常運行。爲了滿足這些特性,數據庫需要更大的系統開銷。用戶可以根據業務是否需要事務處理,來選擇合適的存儲引擎,對於不需要事務的業務場景,不支持事務的存儲引擎會帶來更好的性能。即使存儲引擎不支持事務,用戶可以通過LOCK TABLES 語句爲應用提供一定程度的保存,這也正是MySQL存儲引擎可以發揮優勢的地方。
3.1.2、事務的隔離級別
事務的隔離性遠比想象中複雜。MySQL標準中定義了四種隔離級別,每一種級別都規定了一個事務的修改,哪些在事務內和事務間可見,哪些是不可見的。較低的隔離級別通常可以執行更高的併發,系統開銷也就越小,相應的系統安全性越低。常見的隔離級別有以下四種:
- READ UNCOMMITTED(未提交讀):事務中的修改,即使沒有提交,對其他事務也是可見的。事務可以讀取其他事務未提交的數據,這也被稱爲“髒讀”。這個級別會導致很多問題,並且性能提升不會很大,一般很少應用。
- READ COMMITTED(提交讀):大多數數據庫系統默認的隔離級別(MySQL不是)。事務只能讀取到其他事務已經提交的修改。這個隔離級別有時候也叫不可重複讀,因爲兩次執行相同的查詢,可能得到不一樣的結果,
- REPEATABLE READ(可重複讀):該隔離級別解決了不可重複讀的問題,它可以保證兩次執行相同的查詢,讀取到的數據是相同的。但是理論上,可重複讀無法解決幻讀的問題。所謂幻讀是指,讀取一定範圍的數據時,兩次執行相同的查詢,可能會讀取到條數不同的數據。InnoDB存儲引擎通過多版本併發控制(MVCC)解決了幻讀的問題,關於MVCC點擊這裏查看之前的博客。
- SERIALIZABLE(可串行化):該隔離級別是隔離性最高的,它通過強制事務串行執行,避免了幻讀的問題。SERIALIZABLE會在讀取的每一行數據上都加鎖,所以可能導致大量的超時和鎖爭用的問題,只有非常需要確保數據一致性並且接受沒有併發的情況下,才考慮使用該級別。
隔離級別 | 髒讀 | 不可重複讀 | 幻讀 | 加鎖讀 |
---|---|---|---|---|
讀未提交 | Yes | Yes | Yes | No |
讀已提交 | No | Yes | Yes | No |
可重複讀 | No | No | Yes(MVCC可解決該問題) | No |
可串行化 | No | No | No | Yes |
3.1.3、死鎖
死鎖是指兩個或多個事務在同一資源上相互佔用,並請求鎖定對方佔用的資源,從而導致惡劣循環的現象。當多個事務視圖以不同的順序鎖定資源時,就可能會產生死鎖。
爲了解決這個問題,數據庫系統實現了各種死鎖檢測和死鎖超時機制。不同存儲引擎使用不同的處理方式,InnoDB存儲引擎檢測到死鎖的循環依賴後,將持有最少行級排它鎖的事務進行回滾。
3.1.4、事務日誌
事務日誌可以提交事務的執行效率。使用事務日誌,存儲引擎在修改表的數據時,只需要修改其內存拷貝,再把修改行爲及記錄在事務日誌中,而不用每次都持久化到磁盤中。事務日誌使用的是追加的方式,因此寫日誌的操作是磁盤上一小塊區域內的順序I/O,相對隨機I/O來說快得多。事務日誌持久化完成後,後臺可以慢慢同步到磁盤中。目前大多數存儲引擎都是這樣實現的,這樣每次修改數據需要寫兩次磁盤。
需要注意的一點是,即使在事務日誌持久化磁盤過程中系統崩潰,在存儲引擎重啓時這部分資源也會恢復,不會產生丟失修改數據問題。
3.1.5、MySQL中的事務
MySQL中事務默認使用AUTOCOMMIT模式,也就是說,如果不是顯示的開始一個事務,則每個查詢都被當作一個事務執行提交操作。MySQL中常用的事務型存儲引擎有 InnoDB。
MySQL服務層不管理事務,事務是由下層不同的存儲引擎來實現的,所以同一個事務中,不可能使用多個存儲引擎。假如現在同一個事務操作MyISAM表和InnoDB表,假如事務正常執行不會有什麼問題。如果事務中出現問題,MyISAM表因爲不支持事務不能回滾就會造成無法估計的結果。
3.1.6、顯示和隱式鎖定
InnoDB等事務型存儲引擎,在事務執行過程中,InnoDB會根據隔離級別在需要的時候進行加鎖操作,這種InnoDB自動執行的鎖稱爲隱式鎖定。除了InnoDB自動加鎖,也可以通過以下語句進行顯式鎖定:
- SELECT .... LOCK IN SHARE MODE
- SELECT .... FOR UPDATE
MySQL也支持 LOCK TABLES 和 UNLOCK TABLES 等語句,這些語句是在服務層實現的,和存儲引擎無關。一般情況下,如果使用了事務型存儲引擎,不建議使用這些語句,目前事務型存儲引擎的鎖工作的很多,使用這些語句不但對於安全性沒有提升反而會嚴重影響效率。
4、存儲引擎
如上面邏輯架構圖所示,存儲引擎是負責數據存儲和讀取的模塊,它通過爲上層服務層提供接口工作
4.1、常見的存儲引擎
MySQL中最常見的存儲引擎有以下兩種,InnoDB 和 MyISAM (默認存儲引擎)。下面簡單的介紹下兩種存儲引擎的:
InnoDB:
- 支持事務,通過MVCC支持高併發,並且實現了上述四種隔離界別
- 基於聚簇索引建立
- 支持可預測性預讀,支持hash索引
- 支持熱備份
- 較好的容災機制
MyISAM:
- 數據已緊密格式存儲,某些場景下性能好
- 不支持事務,不支持行級鎖
- 支持全文索引
- 容災機制較差
4.2、存儲引擎的選擇
大部分情況下,選擇InnoDB存儲引擎都是正確的情況。除非需要用到某些InnoDB不具備的特性,並且沒有其他方法解決時才考慮使用其他存儲引擎。舉個例子:如果不在乎可拓展能力和併發能力,也不在乎崩潰後數據的丟失問題,並且對InnoDB佔用空間過多比較敏感,那麼就可以使用MyISAMc存儲引擎。
4.3、存儲引擎的轉換
常見的存儲引擎的轉換有以下三種:
- ALTER TABLE:ALTER TABLE mytable ENGINE = InnoDB 。這種轉化方法需要執行很長的事件,MySQL會按行把數據複製到一張新的表中,在複製期間可能消耗所有的系統I/O能力,同時原表會加讀鎖。這種轉化方式會導致轉換表失去所有的特性,如InnoDB 轉 MyISAM,MyISAM 轉 InnoDB 不會恢復原來表中的外鍵
- 導出和導入:使用工具導出原表的所有數據,創建新表使用新的存儲引擎,導出數據插入新表。這裏需要注意MySQL中同一個庫不能有同名的表,不同存儲引擎也不行。如果原表數據較大,導出和導入操作會比較慢
- 創建與查詢:創建新表,使用要轉換的存儲引擎。使用 INSERT ... SELECT 語句來完成,可以通過控制範圍,每次轉換一部分數據。在操作過程中,可以對原表加鎖,保證數據一致