MySQL架構
[TOC]
1. 邏輯架構
- 第一層為客戶端的連線認證,C/S都有此架構
- 第二層為伺服器層,包含MySQL的大多數核心服務功能
- 第三層包含了儲存引擎,伺服器通過API與其通訊,API規避了不同儲存引擎的差異,不同儲存引擎也不會互相通訊,另外儲存引擎不會去解析SQL(InnoDB是例外,它會解析外來鍵定義,因為伺服器本身沒有實現該功能)
1.1 連線管理及安全性
- 每個客戶端在伺服器程式中擁有一個執行緒
伺服器會負責快取執行緒,不需要為每一個新建的連線建立或銷燬執行緒(5.5以後版本提供了執行緒池,可使用少量執行緒來服務大量連線)
伺服器基於使用者名稱、原始主機資訊和密碼對客戶端進行認證,連線成功後會驗證某個特定操作的許可權。
1.2 優化和執行
- MySQL會解析查詢,建立內部資料結構(解析樹),並對其進行各種優化(重寫查詢、決定表的讀取順序、選擇適合的索引)
- 使用者可以通過特殊的關鍵字提示(hint)優化器,影響MySQL的決策過程。也可以請求優化器解釋(explain)優化過程的各個因素,便於使用者重構查詢和schema,修改相關配置
- 優化器不關心表使用的儲存引擎,但是儲存引擎對優化查詢有影響。優化器會請求儲存引擎提供容量或某個具體操作的開銷資訊,已經表資料的統計資訊等。
- 對於SELECT語句,在解析查詢前,伺服器會先檢查查詢快取(Query Cache)。
2. 併發控制
兩個層面的併發控制: 伺服器層和儲存引擎層
2.1 讀寫鎖
- 共享鎖(shared lock), 讀鎖(read lock):共享,相互不阻塞
- 排他鎖(exclusive lock), 寫鎖(write lock):排他(會阻塞其它的讀寫鎖)
2.2 鎖粒度
- 一種提供共享資源的併發性:讓鎖定物件更有選擇性。儘量只鎖定需要修改的部分資料\資料片,鎖定的資料量越少,併發程度越高。
- 鎖的各種操作(獲得鎖,檢查鎖是否解除,釋放鎖)需要消耗資源。
- 鎖策略:
- 表鎖(table lock):
- 最基本、開銷最小的策略。伺服器層實現
- 鎖定整張表。寫鎖阻塞其它鎖,讀鎖互不阻塞。
- 特定場景下,表鎖也可能有良好的效能。如READ LOCAL表鎖支援某些型別的併發寫操作。寫鎖比讀鎖有更高優先順序,可能會被插入到讀鎖佇列的前面。
- 儲存引擎可以管理自己的鎖,伺服器層還是會使用各種有效的表鎖去實現不同的目的。如,伺服器會為ALTER TABLE等語句使用表鎖,而忽略儲存引擎的鎖機制。
- 行級鎖(row lock):
- 最大程度地支援併發處理,也帶來了最大的鎖開銷。
- 只在儲存引擎實現(InnnoDB和XtraDB等)
- 表鎖(table lock):
3. 事務
- 事務是一組原子性的SQL查詢,或者說一個獨立的工作單元。
- 事務的ACID:
- 原子性(atomicty):一個不可分割的最小工作單元。
- 一致性(consistency):資料庫總是從一個一致性狀態轉換到另一個一致性的狀態。
- 隔離性(isolation):一個事務所做的修改在最終提交以前,對其他事務是不可見的。
- 永續性(durability):事務提交後,其所做的修改會永久儲存到資料庫中。(沒有100%的永續性永續性保證策略)
3.1 隔離線的隔離級別
SQL標準中的四種隔離級別:
- READ UNCOMMITED(未提交讀):
- 事務中的修改,即使沒有提交,對其它事務也是可見的。
- 事務讀取未提交的資料,稱為髒讀(Dirty READ)。
- 效能並不比其它級別好太多,但確少了很多好處,不推薦。
- READ COMMITED(提交讀)、不可重複讀(nonrepeatable read):
- 大多數資料庫的預設級別,但MySQL不是。
- 一個事務只能讀取已提交的事務所做的修改,換句話說,一個事務從開始直到提交之前,所做的任何修改對其它事務都是不可見的。
- 可能會導致虛讀,如事務A兩次讀取資料,事務B在這之間修改了資料,這兩次讀取會有不一樣的結果,即可讀取其它事務的增刪改
- REPEATABLE READ(可重複讀):
- MySQL預設級別。
- 解決髒讀問題,保證在同一個事務中多次讀取同樣記錄的結果是一致的。
- 可能會導致幻讀(Phantom Read),事務A讀取並修改資料,事務B也在該範圍修改了資料(插入或刪除),事務A再次讀取該範圍的資料,發現了幻行(Phantom Row),即可讀取其它事務的增刪
- SERIALIZABLE(可序列化):
- 最高階別的隔離級別,強制事務序列執行。
- 在讀取的每一行資料上都加鎖,導致大量的超時和鎖爭用問題。
隔離級別 | 髒讀可能性 | 不可重複讀可能性 | 幻讀可能性 | 加鎖讀 |
---|---|---|---|---|
未提交讀 | Yes | Yes | Yes | No |
提交讀 | No | Yes | Yes | No |
可重複讀 | No | No | Yes | No |
可序列化 | No | No | No | Yes |
3.2 死鎖
- 死鎖是指兩個或多個事務在同一個資源上相互佔用,並請求鎖定對方佔用的資源,從而導致惡性迴圈的現象。
- 產生原因:
- 真正的資料衝突
- 儲存引擎的實現方式
- 解決方案(部分或完全回滾其中一個事務):
- 死鎖檢測。InnoDB處理死鎖的方法:將持有最少行級的排它鎖的事務進行回滾(比較簡單的回滾演算法)
- 死鎖超時機制,超時後放棄對鎖的請求
3.3 事務日誌(預寫式日誌,Write-Ahead Logging)
- 使用事務日誌,儲存引擎修改表資料,只需修改其記憶體拷貝,再將該行為記錄到持久在硬碟的事務日誌中。
- 大多數儲存引擎的實現方案,修改資料需寫兩次磁碟(第一次為日誌記錄,第二次為資料)
- 優點:
- 提高事務的效率
- 速度快。採用追加方式,寫日誌的操作是磁碟一小塊區域的順序IO,而不是多區域的隨機IO。
3.4 MySQL中的事務
事務型的儲存引擎:MySQL的如InnoDB和NDB Cluster,第三方的如XtraDB和PBXT。
自動提交(AUTOCOMMIT):
MySQL的預設模式。如不顯示地開始一個事務,每個查詢都被當作一個事務執行提交操作。
在當前連線可以通過設定AUTOCOMMIT變數來啟用或禁用自動提交模式。當禁用時,所有的查詢都是在一個事務中,直到顯式地執行COMMIT或ROLLBACK,該事務結束後又開始另一個新的事務。
mysql> SHOW VARIABLES LIKE 'AUTOCOMMIT';
mysql> SET AUTOCOMMIT = 1;複製程式碼對非事務型地表,如MyISAM或者記憶體表,不會有任何影響。因為這類表沒有COMMIT或ROLLBACK的概念,也就是說一直處於AUTOCOMMIT模式。
在資料定義語言(DDL)中,如ALTER TABLE、LOCK TABLES等導致大量資料改變的操作,會在執行之前強制COMMIT提交當前事務。
MySQL可以在當前會話設定隔離級別,新的隔離級別會在下一個事務開始的事後生效。
mysql> SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITED;複製程式碼
MySQL能夠識別所有的4個ANSI隔離級別,InnoDB引擎支援所有的隔離級別。
在事務中混合使用儲存引擎
- MySQL伺服器層不管理事務,事務是由下層的儲存引擎實現的。所以在同一個事務,使用多種儲存引擎不可靠。
- 如在事務中混合使用儲存引擎,如InnoDB和MyISAM,正常情況下不會有問題,但需要回滾時,非事務型的表上的變更無法撤銷,導致資料庫處於不一致的狀態,而且MySQL對非事務型的表操作不會有提示。
- 總結:最好不要在一個事務中混合使用儲存引擎
隱式和顯式鎖定
隱式鎖定:
- InnoDB採用兩階段鎖定協議(two-phase locking protocol)。事務執行過程中,隨時都可以執行鎖定,鎖只在COMMIT或ROLLBACK時被釋放,並且所有的鎖都在同一時間釋放。InnoDB會根據隔離級別在需要的時候自動加鎖。
顯式鎖定:
MySQL可通過特定語句進行顯式鎖定,這些語句不屬於SQL規範,儘量避免使用
SELECT ... LOCK IN SHARE MODE
SELECT ... FOR UPDATE複製程式碼
其它鎖定
- MySQL支援LOCK TABLES和UNLOCK TABLES語句,這是在伺服器層實現的,和儲存引擎無關。並不能代替事務處理。在InnoDB使用,會嚴重影響效能,效果沒有它的行級鎖好
- 建議:除了事務中禁用AUTOCOMMIT,可以使用LOCK TABLES之外,其餘任何使用都不要顯式地執行,不管使用地是什麼儲存引擎。
4. 多版本併發控制(MVVC)
MySQL的大多數儲存引擎,Oracle以及PostgreSQL都實現MVVC,但實現的機制不盡相同,典型的有樂觀併發控制和悲觀併發控制。
目的:
- 鄙棄簡單地行級鎖,可視為行級鎖的變種,提升併發效能。
- 在很多情況下避免了加鎖操作,開銷更低。
- 實現了非阻塞的讀操作,寫操作也只鎖定必要的行
實現原理:
- 儲存資料在某個時間點的快照來實現。
- 不管執行多長時間,每一個事務看到的資料都是一致的。
- 根據事務開始時間的不同,每一個事務對同一張表,同一個時刻看到的資料可能是不一樣的。
InnoDB簡化版的MVVC工作原理:
通過在每行記錄後面儲存兩個隱藏的列,一列儲存了行的建立時間,一個儲存行的過期時間。儲存的並不是實際的時間值,而是系統版本號。每一個新的事務,系統版本號都會自動遞增。事務開始時刻的系統版本號會作為事務的版本號,用來和查詢到的每行記錄的版本號進行比較。
只在REPEATABLE READ和READ COMMITED兩個隔離級別下工作。
優點:大多數讀操作不用加鎖,讀操作簡單,效能很好,也保證只會讀到符合標準的行。
缺點:每行記錄都需要額外的儲存空間,需要更多的行檢查及額外的維護工作。
在REPETABLE READ隔離級別下,MVVC的工作:
SELECT:
根據以下兩個條件檢查每行記錄,符合的記錄才能返回查詢結果
- 只查詢版本號小於等於當前事務版本號的資料行。確保資料行是在事務開始前已經存在的,或者是事務本身插入或者修改過的。
- 行的刪除版本號要麼未定義,要麼大於當前事務版本號。確保資料行在事務讀取前未被刪除。
INSERT:
為新插入的每一資料行儲存當前系統版本號為行版本號
DELETE
為刪除的每一行儲存當前系統版本號作為行刪除標識。
UPDATE:
插入一條新的記錄,儲存當前系統版本號為行版本號,儲存當前系統版本號到原來的行作為行刪除標識。
5. MySQL儲存引擎
在檔案系統中,MySQL將每個資料庫(schemaa)儲存為資料目錄下的一個子目錄,將資料庫的每個表在該子目錄下建立一個和表同名的.frm檔案儲存表的定義。
-- 表路徑:/MySQL_data/MyDB/Mytalbe.frm
mysql> SHOW TABLE STATUS LIKE 'MyTABLE'\G;複製程式碼
5.1 InnoDB儲存引擎
MySQL預設事務型儲存引擎,擁有良好的效能和自動崩潰恢復特性。
- 設計目的:處理大量的短期(short-lived)事務(短期事務大部分情況是正常提交的,很少被回滾)
- 概覽:
- 資料儲存在表空間(tablespace)中,由InnoDB管理的黑盒子,有一系列的資料檔案組成。
- 採用MVVC支援高併發,實現四個標準的隔離級別,預設為REPEATABLE READ,並且通過間隙鎖(next-key locking)策略使得InnoDB鎖定查詢涉及的行,還會對索引中的間隙進行鎖定,防止幻讀出現。
- 基於聚簇索引建立,對主鍵查詢有很高的效能。但是二級索引(secondary index,非主鍵索引)必須包含主鍵列,如主鍵索引過大,其它的所有索引都會很大。
- 從磁碟讀取資料採用可預測性預讀、自動在記憶體中建立hash索引以加速讀操作的自適應索引(adaptive hash index)、加速插入操作的插入緩衝區(insert buffer)
- 通過一些機制和工具支援真正的熱備份
- 建議:有空閱讀官方手冊中的"InnoDB事務模型和鎖"一節
5.2 MyISAM儲存引擎
MySQL5.1及之前版本的預設儲存引擎。支援全文索引、壓縮、空間函式(GIS),不支援事務和行級鎖,並且崩潰後無法安全恢復。對於只讀資料,或者表比較小,可以忍受修復(repair)操作,可以考慮MyISAM。
- 儲存:表以.MYD和.MYI的資料檔案和索引檔案儲存在檔案系統。
- 特性:
- 加鎖與併發:對整張表而不是特定行加鎖。讀取時對讀到的表加共享鎖,寫入時則加排它鎖。支援併發插入(CONCURRENT INSERT),在讀取查詢的同時,也可以插入新的資料。
- 修復:與事務恢復以及崩潰恢復是不同的概念。速度慢,可能會導致資料丟失。通過
CHECK TABLE mytable
檢查表的錯誤,REPAIR TABLE mytable
進行修復。 - 索引特性:支援全文索引,這是基於分詞建立的索引。即使是BOLB和TEXT等長欄位,也可以基於前500個字元建立索引。
- 延遲更新索引鍵(Delayed Key Write):如指定了DELAY_KEY_WRITE,每次修改執行完成時,不會將修改的索引資料寫入磁碟而是寫到記憶體中的鍵快取區(in-memory key buffer),只有在清理鍵快取區或關閉表的時候才會寫入磁碟。可極大提升寫入效能,但可能在資料庫或主機崩潰時造成索引損壞而執行修復操作。
- 壓縮表:只進行讀操作可採用壓縮表,極大減少磁碟佔用空間以及IO,從而提升查詢效能。
- 效能:設計簡單,資料以緊密格式儲存,在某些場景下的效能很好。最典型的效能問題為表鎖。
5.3 MySQL內建的其它儲存引擎
- Archive引擎:非事務型對告訴插入和壓縮做優化的引擎。支援INSERT、SELECT和索引,每次SELECT都需要全表掃描,並阻止其它SELECT執行,以實現一致性讀;支援行級鎖和專用緩衝區,實現高併發插入。適合日誌和資料採集類應用。
- Blackhole引擎:沒有實現任何的儲存機制,因為它丟棄所有插入的資料,不做儲存,但是伺服器會記錄Blackhole表的日誌,所以可以用於複製資料到備庫,或者簡單地記錄到日誌。適合特殊的複製架構和日誌稽核,但並不推薦。
- CSV引擎:將CSV檔案作為MySQL表來處理,但不支援索引。適合作為一種資料交換的機制。
- Federated引擎:訪問其它MySQL伺服器的代理,建立一個遠端MySQL的客戶端連線,將查詢傳到遠端伺服器執行,然後提取或傳送需要的資料。
- Memory引擎:
- 所有資料儲存在記憶體中,不需要磁碟IO,比MyISAM快一個數量級。
- 支援Hash索引,但是是表級鎖,因此併發寫入效能低
- 不支援BOLB和TEXT的列,並且每行的長度是固定的。
- 適合快速地訪問資料,並且這些資料不會修改,重啟以後丟失也沒關係(資料會丟失,表結構仍保留)。
- 應用場景:
- 用於查詢(lookup)或者對映(mapping)表
- 用於快取週期性聚合資料(periodically aggregated data)的結果
- 用於儲存資料分析中產生的中間資料
- MySQL執行查詢中需要使用臨時表來儲存中間結果,使用的就是Memory表,如果資料太大或者含有BLOB或TEXT欄位,則使用MyISAM表。
- Memory表並不是
CREATE TEMPORARY mytable
建立的表,後者可以使用任何的儲存引擎。
- Merge引擎:棄用
- NDB叢集引擎:參加MySQL叢集
5.4 選擇合適的儲存引擎
- 除非需要用到某些InnoDB不具備的特性,並且沒有其它辦法可以代替,否則都應該優先選擇InnoDB引擎。
- 不要混合使用多種儲存引擎。
- 考慮因素:
- 事務
- 備份
- 崩潰恢復
- 特有的特性
- 應用場景:
- 日誌型應用:
- 使用MyISAM或Archive儲存引擎
- 採用主備架構,用於讀寫分離
- 日誌記錄表的明顯包含日期資訊
- 日誌型應用:
5.5 轉換表的引擎
轉換引擎會失去與原引擎相關的特性。
ALTER TABLE
mysql> ALTER TABLE mytable ENGINE = InnoDB;
需執行很長實際,資料庫將資料從原表複製到一個新表,會消耗掉系統所有的IO能力。
匯出和匯入
使用mysqldump,修改其中的CREATE TABLE語句。
建立和查詢(CREATE和SELECT)
mysql> CREATE TABLE innodb_table LIKE myisam_table;
mysql> ALTER TABLE innodb_table ENGINE=InnoDB;
mysql> INSERT INTO innodb_table SELECT * FROM myisam_table;
-- 如資料量過大,可分批處理。
mysql> START TRANSACTION;
mysql> INSERT INTO innodb_table SELECT * FROM myisam_table where id BETWEEN x AND y;
mysql> COMMIT;
-- 如需要可對原表加鎖,保證資料一致性。複製程式碼