本文來自于拜讀《高性能MySQL(第三版)》時的讀書筆記
作者:安明哲
轉載時請注明部分內容來自《高性能MySQL(第三版)》
MySQL的邏輯構架
最上層:鏈接處理,授權認證,安全等處理
第二層:查詢解析、分析、優化、緩存以及內置函數(如:日期,時間,數學和加密函數)
第三層:包含了存儲引擎,存儲引擎負責數據的存儲和提取。
鏈接管理和安全性
每一個客戶端鏈接都在MySQL服務器進程中擁有一個多線程,在CPU中輪詢運行,服務器會負責緩存線程,因此不需要為每一個新建的鏈接創建或者銷毀線程。
MySQL5.5以后支持線程池插件,可以使用池中少量的線程來服務大量的連接。
客戶端連接到MySQL之后,服務器會對其進行認證,認證基于用戶名,密碼和原始主機信息,如何使用了SSL連接,還可以使用證書認證。一旦連接成功,服務器會繼續驗證客戶端的權限。
通常我們通過一個用戶名和密碼外加一個數據庫的地址連接數據庫,連接數據庫的指令可能如下:
mysql -u username -h 192.168.1.100 -p *****
當我們鏈接MySQL-Server時,他可能會檢查我們的用戶名密碼,除此之外還會檢查我們的主機地址,通過主機地址判斷是否允許我們登錄,比如大部分小型站點會把MySQL和站點文檔放置在一個服務器,而且MySQL拒絕了localhost之外的所有連接(不允許遠程連接),其次我們還可以在MySQL內設置用戶的權限,比如僅允許select(只讀)。
優化與執行
MySQL會解析查詢,并創建內部數據結構,然后進行各種優化,包括重寫查詢、決定表的讀取順序,以及選擇合適的索引等。
對于SELECT語句,MySQL內部會有一個Query Cache,如果這個緩存內能夠找到對應的查詢,MySQL則會省去解析和優化工作,直接返回結果。
并發控制
MySQL共有兩個層面的并發控制,服務器層與存儲引擎層。
讀寫鎖
當有多個連接同時修改一個數據庫的某一個表的某一行的時候,會發生什么結果是不確定的。
這有點類似于多線程編程下的線程同步和死鎖,事實上MySQL問題的本質似乎也就在這里。
解決這類問題的方法就是并發控制,MySQL在處理這類并發操作的時候可以實現一個由兩種類型的鎖組成的索系統來解決問題。這兩種類型的鎖一般被稱為共享鎖和排它鎖。
共享鎖是互不阻塞的,也就是多個用戶可以同時讀取一個資源,所以也叫做讀鎖。
排它鎖是一個寫鎖會阻塞其他寫入和讀取操作,只有這樣才能保證同一時間只會存在呢一個連接在想數據庫內寫入,所以也被稱作寫鎖。
粒度鎖
一種提高共享資源并發性的方式就是讓鎖定得對象更具有選擇性。盡量只鎖定需要修改的部分數據,而不是所有資源。更理想的方式是,只會對要修改的數據片進行精確的鎖定。
那么,理論上鎖定的資源越小,鎖定范圍越精確,那么并發性能就會越高。但是事實上,創建一個數據鎖也會造成系統的開銷,如果系統通過大量的時間來管理鎖,而不是存取數據,系統的性能反而會降低。
表鎖
粒度鎖的精確度是根據需求來決定的,很多時候我們都是在尋找精度和鎖開銷之間的一個平衡點。
表鎖是MySQL中基本的鎖策略,并且是開銷最小的策略。當用戶在對一張表進行寫操作之前,首先獲得寫鎖,于是,它阻塞了其他鏈接的讀取和寫入操作,只有寫鎖接觸的時候,其他鏈接才能獲得讀鎖。
不僅如此,MySQL還設置了鎖的優先級,在操作列隊中MySQL可能會把寫入操作插入到讀取操作之前。
那么,可以想象一下MySQL的后臺實現,可能表鎖的是這樣是實現的(僅僅是本人猜想):
//下面的代碼類似于偽代碼,沒有參考MySQL源碼
//僅僅是本人為了為了理解MySQL表鎖的臆測:
struct table_lock
{
/*一個表鎖的結構體*/
char* table_name;
char* host;
};
//實例化一個鏈接
int* instance = getInstance();
//當執行讀取操作的時候,可能需要傳遞一個table_name
instance->readTable(table_name)
{
//讀取操作時候首先要獲取表鎖的所有權
table_lock.name = table_name;
table_lock.host = instance;
}
行級鎖
行級鎖可以最大程度的支持并發處理(同時也帶來了最大的鎖開銷)。行級鎖只在存儲引擎中實現。
行級鎖比表鎖更加精確,他把鎖的對象精確到了對象的某一行,但也就意味著需要創建更多的鎖。
事務
事務其實就是一個獨立的工作單元。如果數據庫引擎能夠完成事務中的每一項操作,那么全組的SQL語句都會被執行,如果任何一條語句因為崩潰或者其他原因無法執行,那么所有語句都不執行。
最簡單的例子就是我們銀行的轉賬系統,一個轉賬操作實際上是從用戶賬戶表里減去對A的賬戶余額進行修改,同時再去修改B的賬戶余額,最后再在記錄表被記錄下這一條操作。SQL語句大致如下:
START TRANSACTION;
SELECT balance FROM checking WHERE customer_id = 1010001;
UPDATE checking SET balance = balance-200.00 WHERE customer_id = 1010001;
UPDATE checking SET balance = balance+200.00 WHERE customer_id = 1010002;
INSERT INTO record VALUES('1010001', '1010002', 200.00);
COMMIT;
這個時候如果執行到第三條語句的時候發現1010002這個賬戶已經被凍結了,無法接受轉賬,這個時候按照正常的邏輯,數據庫內部10010001這個用戶平白無故的就少去了200元,但是10010002并沒有收到。
使用事務就完美的解決了這個問題,如果一條語句執行失敗,沒關系,最終沒有commit之前,MySQL是不會進行寫入操作的。
ACID
那么有一個專業的名詞來描繪這種需求,他叫做ACID:
A---atomicity(原子性):一個事務必須被視為一個不可分割最小工作單元,事務中的操作只要有一個失敗則全部回滾(可以理解為,一件事情,要么全做,有一點調點不具備那么全部都不做。)
C---Consistency(一致性):在一個轉賬事務中,A賬戶的減少必定對應著B賬戶的增加,這個狀態轉換的過程必須保持一致,那么這就是事務的一致性。
I---Isolation(隔離性):通常來說,一個事物所做的修改在最終提交之前,對其他事務是不可兼得。也就是說,事務內部的操作是不會被外部所看到的,只有最終提交之后,我們才能在銀行系統中看到兩個賬戶金額的變化(這個時候如何還有一個事務在執行同樣的轉賬操作,那么們是相互隔離的,這看起來并不嚴謹,事實這里還設計一個隔離級別的問題,這個以后再談)。
D---durability(持久性):一旦失誤提交,所有的修改會永久的保存在數據庫中,即使系統崩潰,服務器被損壞,只要硬盤還在,數據就依舊存在。
事務的ACID看起來很簡單,但是在應用邏輯中要實現這一點非常難,因為還有相當一部分涉及到用戶體驗的考慮,你必須保證數據同步的問題,保證數據的一致性和持久性同時還要讓用戶覺察不到這么復雜的工作。而且,事務所做的操作正如鎖一樣,需要更多的系統資源,你還需要更強的CPU、更大的內存和更多的磁盤空間。