mysql的工作流程:
1.mysql 架構
mysql 分為 server 層和存儲引擎
1.1.server層
連接器:管理連接權限驗證
查詢緩存:命中緩存直接換回查詢結果
分析器:分析語法
優化器:生成執行計划,選擇索引
執行器:操作索引返回結果
1.2. 存儲引擎
存儲引擎負責數據的存儲和提取;其架構是插件式的。innodb 在 mysql5.5.5 版本開始成為 mysql 默認存儲引擎。
各存儲引擎比對:
InnoDB:支持事務,支持外鍵,InnoDB 是聚集索引,數據文件是和索引綁在一起的,必須要有主鍵,通過主鍵索引效率很高。但是輔助索引需要兩次查詢,先查詢到主鍵,然后再通過主鍵查詢到數據,不支持全文索引。
MyISAM:不支持事物,不支持外鍵,MyISAM 是非聚集索引,數據文件是分離的,索引保存的是數據文件的指針。主鍵索引和輔助索引是獨立的,查詢效率上 MyISAM 要高於 InnnDB ,因此做讀寫分離的時候一般選擇用 InnoDB 做主機,MyISAM 做從機
Memory:有比較大的缺陷使用場景很少;文件數據都存儲在內存中,如果 mysqld 進程發生異常,重啟或關閉機器這些數據都會消失。
1.3.sql 的執行過程
第一步客戶端連接上 mysql 數據庫的連接器,連接器獲取權限,維持管理連接;連接完成后如果你沒有后續的指令這個連接就會處於空閑狀態,如果太長時間不使用這個連接這個連接就會斷開,這個空閑時長默認是 8 小時,由 wait_timeout 參數控制。
第二步你往 mysql 數據庫發送了一條 sql ,這個時候查詢緩存開始工作,看看之前有沒有執行過這個 sql ,如果有則直接返回緩存數據到客戶端,只要對表執行過更新操作緩存都會失效,因此一些很少更新的數據表可考慮使用數據庫緩存,對頻繁更新的表使用緩存反而弊大於利。使用緩存的方法如以下 sql ,通過 SQL_CACHE 來指定:
select SQL_CACHE * from table where xxx=xxx
第三步當未命中緩存的時候,分析器開始工作;分析器判斷你是 select 還是 update 還是 insert ,分析你的語法是否正確。
第四步優化器根據你的表的索引和 sql 語句決定用哪個索引,決定 join 的順序。
第五步執行器執行 sql ,調用存儲引擎的接口,掃描遍歷表或者插入更新數據。
2 mysql 日志
2.1 mysql 日志介紹
mysql 有兩個重要日志—— redolog 和 binlog ,redolog 是獨屬於 innodb 的日志,binlog 則是屬於 server 層的日志。下面介紹這兩個日志有什么用:當我們更新數據庫數據的時候,這兩個日志文件也會被更新,記錄數據庫更新操作。
redolog 又稱作重做日志,用於記錄事務操作的變化,記錄的是數據修改之后的值,不管事務是否提交都會記錄下來。它在數據庫重啟恢復的時候被使用,innodb 利用這個日志恢復到數據庫宕機前的狀態,以此來保證數據的完整性。redolog 是物理日志,記錄的是某個表的數據做了哪些修改,redolog 是固定大小的,也就是說后面的日志會覆蓋前面的日志。
binlog 又稱作歸檔日志,它記錄了對 MySQL 數據庫執行更改的所有操作,但是不包括 SELECT 和 SHOW 這類操作。binlog 是邏輯日志,記錄的是某個表執行了哪些操作。binlog 是追加形式的寫入日志,后面的日志不會被前面的覆蓋。
2.2 數據更新過程
我們執行一個更新操作是這樣的:讀取對應的數據到內存—>更新數據—>寫 redolog 日志—> redolog 狀態為 prepare —>寫 binlog 日志—>提交事務—> redolog 狀態為 commit ,數據正式寫入日志文件。我們發現 redolog 的提交方式為“兩段式提交”,這樣做的目的是為了數據恢復的時候確保數據恢復的准確性,因為數據恢復是通過備份的 binlog 來完成的,所以要確保 redolog 要和 binlog 一致。
6執行計划和慢查詢日志
6.1 執行計划
在查詢 sql 之前加上 explain
可查看該條 sql 的執行計划,如:
EXPLAIN SELECT * FROM table
這條 sql 會返回這樣一個表:
id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | extra | |
---|---|---|---|---|---|---|---|---|---|---|---|---|
1 | simple |
這個表便是 sql 的執行計划,我們可以通過分析這個執行計划來知道我們 sql 的運行情況。現對各列進行解釋:
1)id:查詢中執行 select 子句或操作表的順序。
2)select_type:查詢中每個 select 子句的類型(簡單 到復雜)包括:
-
-
-
SIMPLE:查詢中不包含子查詢或者UNION;
-
PRIMARY:查詢中包含復雜的子部分;
-
SUBQUERY:在SELECT或WHERE列表中包含了子查詢,該子查詢被標記為SUBQUERY;
-
DERIVED:衍生,在FROM列表中包含的子查詢被標記為DERIVED;
-
UNION:若第二個SELECT出現在UNION之后,則被標記為UNION;
-
UNION RESULT:從UNION表獲取結果的SELECT被標記為UNION RESULT;
-
-
3) type:表示 MySQL 在表中找到所需行的方式,又稱“訪問類型”,包括:
-
-
-
ALL:Full Table Scan, MySQL 將遍歷全表以找到匹配的行;
-
index:Full Index Scan,index 與 ALL 區別為 index 類型只遍歷索引樹;
-
range:索引范圍掃描,對索引的掃描開始於某一點,返回匹配值域的行,常見於 between < > 等查詢;
-
ref:非唯一性索引掃描,返回匹配某個單獨值的所有行。常見於使用非唯一索引即唯一索引的非唯一前綴進行的查找;
-
eq_ref:唯一性索引掃描,對於每個索引鍵,表中只有一條記錄與之匹配。常見於主鍵或唯一索引掃描;
-
onst 和 system:當 MySQL 對查詢某部分進行優化,並轉換為一個常量時,使用這些類型訪問。如將主鍵置於 where 列表中,MySQL 就能將該查詢轉換為一個常量,system 是 const 類型的特例,當查詢的表只有一行的情況下, 使用 system;
-
NULL:MySQL 在優化過程中分解語句,執行時甚至不用訪問表或索引。
-
-
4)possible_keys:指出 MySQL 能使用哪個索引在表中找到行,查詢涉及到的字段上若存在索引,則該索引將被列出,但不一定被查詢使用。
5)key:顯示 MySQL 在查詢中實際使用的索引,若沒有使用索引,顯示為 NULL。
6)key_len:表示索引中使用的字節數,可通過該列計算查詢中使用的索引的長度。
7)ref:表示上述表的連接匹配條件,即哪些列或常量被用於查找索引列上的值。
8)rows:表示上述表的連接匹配條件,即哪些列或常量被用於查找索引列上的值。
9)Extra:其他重要信息 包括:
-
-
-
Using index:該值表示相應的 select 操作中使用了覆蓋索引;
-
Using where:MySQL 將用 where 子句來過濾結果集;
-
Using temporary:表示 MySQL 需要使用臨時表來存儲結果集,常見於排序和分組查詢;
-
Using filesort:MySQL 中無法利用索引完成的排序操作稱為“文件排序”。
-
-
6.2 慢查詢日志
mysql 支持慢查詢日志功能—— mysql 會將查詢時間過長的 sql 相關信息寫入日志。這個查詢時間閥值由參數 long_query_time
指定, long_query_time
的默認值為 10,運行 10S 以上的查詢 sql 會被記錄到慢查詢日志中。默認情況下,Mysql 數據庫並不啟動慢查詢日志,需要我們手動來設置這個參數。慢查詢日志支持將日志記錄寫入文件,也支持將日志記錄寫入數據庫表。
可通過以下 sql 查看慢查詢日志是否開啟:
show variables like '%slow_query_log%';
通過以下 sql 開啟慢查詢:
set global slow_query_log=1;
使用 sql 修改慢查詢日志設置只對當前數據庫生效,如果 MySQL 重啟后則會失效。如果要永久生效,就必須修改配置文件 my.cnf。
通過以下 sql 查看修改慢查詢的閾值:
show variables like 'long_query_time%';
et global long_query_time=4;
7 主從備份
7.1 主從備份原理
主從復制是指一台服務器充當主數據庫服務器,另一台或多台服務器充當從數據庫服務器,主服務器中的數據自動復制到從服務器之中。通過這種手段我們可以做到讀寫分離,主庫寫數據,從庫讀數據,從而提高數據庫的可用。MySQL 主從復制涉及到三個線程,一個運行在主節點(log dump thread),其余兩個(I/O thread, SQL thread)運行在從節點。
主節點 binary log dump 線程:
當從節點連接主節點時,主節點會創建一個 logdump
線程,用於發送 binlog
的內容。在讀取 binlog
中的操作時,此線程會對主節點上的 binlog
加鎖,當讀取完成,甚至在發動給從節點之前,鎖會被釋放。
從節點I/O線程: 用於從庫將主庫的 binlog
復制到本地的 relay log
中,首先,從庫庫會先啟動一個工作線程,稱為IO工作線程,負責和主庫建立一個普通的客戶端連接。如果該進程追趕上了主庫,它將進入睡眠狀態,直到主庫有新的事件產生通知它,他才會被喚醒,將接收到的事件記錄到 relay log
(中繼日志)中。
從節點 SQL 線程:
SQL 線程負責讀取 relay log
中的內容,解析成具體的操作並執行,最終保證主從數據的一致性。
7.2 主從備份延遲
主備延遲最直接的表現是,備庫消費中繼日志( relay log
)的速度,比主庫生產 binlog
的速度要慢。可能導致的原因有:
-
-
大事務,主庫上必須等事務執行完成才會寫入 binlog,再傳給備庫,當一個事物用時很久的時候,在從庫上會因為這個事物的執行產生延遲。
-
從庫壓力大。
-
主備延遲當然是不好的,那么有哪些辦法盡量減小主備延遲呢?有下面幾個辦法:
-
-
一主多從——多接幾個從庫,讓這些從庫來分擔讀的壓力。這樣方法適用於從庫讀壓力大的時候。
-
通過 binlog 輸出到外部系統,比如 Hadoop 這類系統,讓外部系統提供統計類查詢的能力
-
8 分布式事務
這里不再對分布式事物的概念做普及,直接介紹兩種分布式事務: XA 分布式事務和 TCC 分布式事務。
8.1 XA 分布式事務
XA 是兩階段提交的強一致性事物。在 MySQL 5.7.7 版本中,Oracle 官方將 MySQL XA 一直存在的一個 “bug” 進行了修復,使得MySQL XA 的實現符合了分布式事務的標准。
XA 事務中的角色:
-
-
-
資源管理器(resource manager):用來管理系統資源,是通向事務資源的途徑。數據庫就是一種資源管理器。資源管理還應該具有管理事務提交或回滾的能力。
-
事務管理器(transaction manager):事務管理器是分布式事務的核心管理者。事務管理器與每個資源管理器(resource manager)進行通信,協調並完成事務的處理。事務的各個分支由唯一命名進行標識。
-
-
XA 規范的基礎是兩階段提交協議:
在第一階段,交易中間件請求所有相關數據庫准備提交(預提交)各自的事務分支,以確認是否所有相關數據庫都可以提交各自的事務分支。當某一數據庫收到預提交后,如果可以提交屬於自己的事務分支,則將自己在該事務分支中所做的操作固定記錄下來,並給交易中間件一個同意提交的應答,此時數據庫將不能再在該事務分支中加入任何操作,但此時數據庫並沒有真正提交該事務,數據庫對共享資源的操作還未釋放(處於鎖定狀態)。如果由於某種原因數據庫無法提交屬於自己的事務分支,它將回滾自己的所有操作,釋放對共享資源上的鎖,並返回給交易中間件失敗應答。
在第二階段,交易中間件審查所有數據庫返回的預提交結果,如所有數據庫都可以提交,交易中間件將要求所有數據庫做正式提交,這樣該全局事務被提交。而如果有任一數據庫預提交返回失敗,交易中間件將要求所有其它數據庫回滾其操作,這樣該全局事務被回滾。
mysql 允許多個數據庫實例參與一個全局的事務。MySQL XA 的命令集合如下:
-- 開啟一個事務,並將事務置於 ACTIVE 狀態,此后執行的 SQL 語句都將置於該是事務中。
XA START xid
-- 將事務置於 IDLE 狀態,表示事務內的 SQL 操作完成。
XA END xid
-- 事務提交的准備動作,事務狀態置於 PREPARED 狀態。事務如果無法完成提交前的准備操作,該語句會執行失敗。
XA PREPARE xid
-- 事務最終提交,完成持久化。
XA COMMIT xid
-- 事務回滾終止
XA ROLLBACK xid
-- 查看 MySQL 中存在的 PREPARED 狀態的 xa 事務。
XA RECOVER
MySQL 在 XA 事務中扮演的是參與者的角色,被事務協調器所支配。XA 事務比普通本地事務多了一個 PREPARE
狀態,普通事務是 begin-> commit 而分布式事務是 begin->PREPARE 等其他數據庫事務都到 PREPARE 狀態的時候再 PREPARE->commit。分布式事務 sql 示例:
xa start 'aaa';
insert into table(xxx) values(xxx);
xa end 'aaa';
xa prepare 'aaa';
xa commit 'aaa';
XA 事務存在的問題:
-
單點問題:事務管理器在整個流程中扮演的角色很關鍵,如果其宕機,比如在第一階段已經完成,在第二階段正准備提交的時候事務管理器宕機,資源管理器就會一直阻塞,導致數據庫無法使用。
-
同步阻塞:在准備就緒之后,資源管理器中的資源一直處於阻塞狀態,直到提交完成才能釋放資源。
-
數據不一致:兩階段提交協議雖然為分布式數據強一致性所設計,但仍然存在數據不一致性的可能,比如在第二階段中,假設協調者發出了事務 commit 的通知,但是因為網絡問題該通知僅被一部分參與者所收到並執行了 commit 操作,其余的參與者則因為沒有收到通知一直處於阻塞狀態,這時候就產生了數據的不一致性。
8.2 TCC 分布式事務
TCC 又被稱作柔性事務,通過事務補償機制來達到事務的最終一致性,它不是強一致性的事務。TCC 將事務分為兩個階段,或者說是由兩個事務組成的。相對於 XA 事務來說 TCC 的並發性更好,XA 是全局性的事務,而 TCC 是由兩個本地事務組成。
假設我們購買一件商品,后台需要操作兩張表——積分表加積分而庫存表扣庫存,這兩張表存在於兩個數據庫中,使用 TCC 事務執行這一事務:
1)TCC 實現階段一:Try
在 try 階段並不是直接減庫存加積分,而是將相關數據改變為預備的狀態。庫存表先鎖定一個庫存,鎖定的方式可以預留一個鎖定字段,當這個字段為一的時候表示這個商品被鎖定。積分表加一個數據,這個數據也是被鎖定狀態,鎖定方式和庫存表一樣。其 sql 形如:
update stock set lock=1 where id=1;
nsert into credits (lock,...) values (1,...)
這兩條 sql 如果都執行成功則進入 Confirm 階段,如果執行不成功則進入 Cancel 階段
2)TCC 實現階段二:Confirm
這一階段正式減庫存加積分訂單狀態改為已支付。執行 sql 將鎖定的庫存扣除,為累加積分累加,以及一些其他的邏輯。
3)TCC 實現階段三:Cancel
當 try 階段執行不成功,就會執行這一階段,這個階段將鎖定的庫存還原,鎖定的積分刪除掉。退回到事務執行前的狀態。
TCC 事務原理很簡單,使用起來卻不簡單。首先 TCC 事務對系統侵入性很大,其次是讓業務邏輯變得復雜。在實際使用中我們必須依賴 TCC 事務中間件才能讓 TCC 事務得以實現。通常一個 TCC 事務實現大概是這樣子的:某個服務向外暴露了一個服務,這個服務對外正常調用,其他服務並不能感知到 TCC 事務的存在,而其服務內部,分別實現了 Try,Confirm,Cancel 三個接口,注冊到 TCC 中間件上去。當調用這個服務的時候,其事務操作由該服務和 TCC 中間件共同完成。
而 TCC 事務中間件還要做好其他事情,比如確保 Confirm 或者 Cancel 執行成功,如果發現某個服務的 Cancel 或者 Confirm 一直沒成功,會不停的重試調用他的 Cancel 或者 Confirm 邏輯,務必要他成功!即使在嘗試多次后無法成功也能通知到系統需要人工排查異常。TCC 事務還要考慮一些異常情況的處理,比如說訂單服務突然掛了,然后再次重啟,TCC 分布式事務框架要能夠保證之前沒執行完的分布式事務繼續執行。TCC 分布式事務框架還需要做好日志的記錄,保存下來分布式事務運行的各個階段和狀態,以便系統上線后能夠排查異常,恢復數據。目前開源的 TCC 事務框架有:Seata
ByteTCC
tcc-transaction
等。