下面我給出的是 MySQL 的基本架構示意圖,從中你能夠清楚地看到 SQL 語句在 MySQL 的各個功能模塊中的執行過程。html
從圖中能夠看出,不一樣的存儲引擎公用一個server層。mysql
鏈接命令算法
mysql -h$ip -P$port -u$user -p
sql
鏈接保持時間shell
鏈接完成以後,若是沒有後續的動做,這個鏈接就會處於空閒狀態,能夠在show processlist
命令中看到。數據庫
客戶端若是太長時間沒有動靜,鏈接器會主動將其斷連。這個參數是wait_timeout控制的,默認時間是8小時。數組
斷開以後再發送請求的話,就會收到一個錯誤提醒:Lost connection to MySQL server during query。須要客戶端從新鏈接,而後再發送請求。緩存
長鏈接佔用內存問題bash
創建鏈接以後,進行select語句查詢,就會進入查詢緩存階段。每次的查詢會把結果放入緩存,下次有相同的查詢會直接在緩存中獲取便可,無需進入存儲引擎進行查詢。架構
可是,查詢緩存這個事情是弊大於利的,因此不會建議使用。mysql8.0以後直接將該模塊刪除了。
爲啥有弊呢?由於每次存在update操做的狀況下,就會把整個緩存都清除掉,致使緩存命中率特別低。
在8.0以前的版本,能夠直接將query_cache_type設置爲DEMAND,這樣的話,查詢語句默認不使用緩存。在須要使用查詢緩存的地方能夠顯示調用。mysql> select SQL_CACHE * from T where ID=10;
分析器先作「詞法分析」,根據語法規則,判斷輸入的sql語句是否知足mysql語法規則。
優化器是在表中存在多個索引的狀況下,決定用哪一個索引;或者在一個語句有多表關聯(join)的時候,決定各個表的鏈接順序。
MySQL經過分析器知道了你要作什麼,經過優化器知道了該怎麼作,因而就進入了執行器階段,開始執行語句。
與查詢流程不同的是,更新流程還涉及兩個重要的日誌模塊,它們正是咱們今天要討論的主角:redo log(重作日誌)和 binlog(歸檔日誌)。
若是每一次的更新操做都須要寫進磁盤,而後磁盤也要找到對應的那條記錄,而後再更新,整個過程 IO 成本、查找成本都很高。爲了解決這個問題,MySQL 的設計者就用了相似酒店掌櫃粉板的思路來提高更新效率。
而粉板和帳本配合的整個過程,其實就是 MySQL 裏常常說到的 WAL 技術,WAL 的全稱是 Write-Ahead Logging,它的關鍵點就是先寫日誌,再寫磁盤,也就是先寫粉板,等不忙的時候再寫帳本。
具體來講,當有一條記錄須要更新的時候,InnoDB 引擎就會先把記錄寫到 redo log(按我理解這個其實仍是要寫文件,只不過是順序寫,效率高)裏面,並更新內存,這個時候更新就算完成了。同時,InnoDB 引擎會在適當的時候,將這個操做記錄更新到磁盤裏面,而這個更新每每是在系統比較空閒的時候作。
InnoDB 的 redo log 是固定大小的,好比能夠配置爲一組 4 個文件,每一個文件的大小是 1GB,那麼這塊「粉板」總共就能夠記錄 4GB 的操做。從頭開始寫,寫到末尾就又回到開頭循環寫,以下面這個圖所示。
這種機制保證了即便數據庫異常宕機,以前的數據也不會丟失,這個能力成爲crash-safe。
redo log是innodb引擎特有的日誌,而引擎層上面的server層(若是忘了mysql的架構,記得看上一篇的圖示)也有本身的日誌,成爲binlog(歸檔日誌)。
爲啥會有兩份日誌嘛?
由於最開始 MySQL 裏並無 InnoDB 引擎。MySQL 自帶的引擎是 MyISAM,可是 MyISAM 沒有 crash-safe 的能力,binlog 日誌只能用於歸檔。而 InnoDB 是另外一個公司以插件形式引入 MySQL 的,既然只依靠 binlog 是沒有 crash-safe 能力的,因此 InnoDB 使用另一套日誌系統——也就是 redo log 來實現 crash-safe 能力。
這裏面涉及到了兩階段提交。 MySQL經過兩階段提交過程來完成事務的一致性的,也即redo log和binlog的一致性的,理論上是先寫redo log,再寫binlog,兩個日誌都提交成功(刷入磁盤),事務纔算真正的完成。
若是你的 DBA 承諾說半個月內能夠恢復,那麼備份系統中必定會保存最近半個月的全部 binlog,同時系統會按期作整庫備份。這裏的「按期」取決於系統的重要性,能夠是一天一備,也能夠是一週一備。
當須要恢復到指定的某一秒時,好比某天下午兩點發現中午十二點有一次誤刪表,須要找回數據,那你能夠這麼作:
事務就是要保證數據庫一組操做,要麼所有成功,要麼所有失敗。在mysql中,事務是引擎層支實現的,MyISAM不支持事務,InnoDB支持事務。這也是MyISAM被InnoDB取代的重要緣由之一。
在實現上,數據庫會建立一個視圖,訪問的時候以視圖的邏輯結果爲準。
在「可重複讀」級別下,這個視圖是在事務啓動的時候建立的,整個事務存在期間都用這個視圖。
在「讀提交」級別下,這個視圖是在每一個SQL語句開始執行的時候建立的。
在「讀未提交」級別下,直接返回記錄上的最新值。
在「串行化」級別下,直接用加鎖的方式來避免並行訪問。
Oracle的默認級別是「讀提交」。
Mysql的默認級別是「可重複讀」。
查看當前數據庫默認的事務隔離級別:
show variables like 'transaction_isolation';
複製代碼
每條update語句執行的同時,都會同時記錄一條回滾日誌。記錄上的最新值,經過回滾操做,均可以獲得前一個狀態的值。
假設一個值從1按順序改爲了2/3/4,在回滾日誌裏會有相似下面的記錄。
當前該值爲4,不一樣時刻啓動的事務會有不一樣的read-view。同一條記錄在系統中能夠存在多個版本,就是數據庫的多版本併發控制(MVCC)。對於read-view A,要獲得1,就必須將當前值依次執行圖中全部的回滾操做獲得。
當沒有事務再須要用到這些回滾日誌的時候,相應的回滾日誌會被刪除。
能夠在 information_schema 庫的 innodb_trx 這個表中查詢長事務,好比下面這個語句,用於查找持續時間超過 60s 的事務。
select * from information_schema.innodb_trx where TIME_TO_SEC(timediff(now(),trx_started))>60
複製代碼
從應用開發端和數據庫端兩個方面來看問題。
應用開發端
數據庫端
監控information_schema.Innodb_trx表,設置長事務閾值,超過閾值就告警或kill。
mysql -N -uroot -p'密碼' -e "select now(),(UNIX_TIMESTAMP(now()) - UNIX_TIMESTAMP(a.trx_started)) diff_sec,b.id,b.user,b.host,b.db,d.SQL_TEXT from information_schema.innodb_trx a inner join
information_schema.PROCESSLIST b
on a.TRX_MYSQL_THREAD_ID=b.id and b.command = 'Sleep'
inner join performance_schema.threads c ON b.id = c.PROCESSLIST_ID
inner join performance_schema.events_statements_current d ON d.THREAD_ID = c.THREAD_ID;" | while read A B C D E F G H
do
#echo $C
if [ "$C" -gt 5 ]
then
echo date "+%Y-%m-%d %H:%M:%S"
echo "processid[$D] $E@$F in db[$G] hold transaction time $C SQL:$H"
fi
done >> /tmp/longtransaction.txt
複製代碼
使用pt-kill工具,kill長時間的異常事務。參考:www.cnblogs.com/bjx2020/p/9…
在業務開發測試階段,輸出全部的general_log,分析日誌行爲,提前發現。
mysql>set global general_log_file='/tmp/general.log'; #設置路徑
mysql>set global general_log=on; # 開啓general log模式
複製代碼
索引就是爲了提升數據查詢的效率,就像書的目錄同樣。
在InnoDB中,表都是根據主鍵順序以索引的形式存放的,這種存儲方式的表稱爲索引組織表。
在InnoDB中,索引使用B+樹的索引模式。B+ 樹可以很好地配合磁盤的讀寫特性,減小單次查詢的磁盤訪問次數。
左邊是主鍵索引,右邊是普通索引。主鍵索引的葉子節點存的是整行數據,普通索引的葉子節點存的是主鍵的值。
也就是說,基於非主鍵索引的查詢須要多掃描一棵索引樹。所以,咱們在應用中應該儘可能使用主鍵查詢。
B+ 樹爲了維護索引有序性,在插入新值的時候須要作必要的維護。以上面這個圖爲例,若是插入新的行 ID 值爲 700,則只須要在 R5 的記錄後面插入一個新記錄。若是新插入的 ID 值爲 400,就相對麻煩了,須要邏輯上挪動後面的數據,空出位置。
而更糟的狀況是,若是 R5 所在的數據頁已經滿了,根據 B+ 樹的算法,這時候須要申請一個新的數據頁,而後挪動部分數據過去。這個過程稱爲頁分裂。在這種狀況下,性能天然會受影響。
因此使用自增主鍵的重要性就體如今這裏了。每次插入一條新記錄,都是追加操做,都不涉及到挪動其餘記錄,也不會觸發葉子節點的分裂。
顯然,主鍵長度越小,普通索引的葉子節點就越小,普通索引佔用的空間也就越小。
以下所示的表:
mysql> create table T (
ID int primary key,
k int NOT NULL DEFAULT 0,
s varchar(16) NOT NULL DEFAULT '',
index k(k))
engine=InnoDB;
insert into T values(100,1, 'aa'),(200,2,'bb'),(300,3,'cc'),(500,5,'ee'),(600,6,'ff'),(700,7,'gg');
複製代碼
以下所示的查詢,須要執行幾回樹的操做,會掃描多少行:
select * from T where k between 3 and 5
複製代碼
執行流程以下:
在這個例子中,因爲查詢結果所須要的數據只在主鍵索引上有,因此不得不回表。那麼,有沒有可能通過索引優化,避免回表過程呢?
若是執行的語句是select ID from T where k between 3 and 5
,這時只須要查詢ID的值,而ID的值自己已經在k索引樹上,不須要回表。
也就是說,在這個查詢裏面,索引k已經覆蓋了咱們的查詢需求,咱們稱爲覆蓋索引。
又有一個問題:獲取字段和檢索字段都不是主鍵,怎麼使用覆蓋索引。
答案:創建兩個字段的聯合索引便可。
這邊有一個疑問,若是爲每一種查詢都設計一個索引,索引會不會太多了。這個時候能夠藉助最左前綴原則來複用索引。
當已經有(a,b)索引以後,就不須要單獨的a索引了。
根據加鎖範圍,MySQL裏面的鎖大體能夠分爲全局鎖、表級鎖和行級鎖。
顧名思義,全局鎖就是對整個數據庫加鎖。
加鎖方式
Flush tables with read lock
,使得整個數據庫處於只讀狀態。任何更新類的語句所有阻塞。
使用場景
**作全庫邏輯備份。**也就是把整庫每一個表都select出來存成文本。
風險
具體案例
爲何不使用set global readonly=true的方式?
MySQL中表級鎖有兩種:表鎖和元數據鎖MDL(meta data lock)
表鎖
表鎖的語法是 lock tables … read/write。
舉個例子, 若是在某個線程 A 中執行 lock tables t1 read, t2 write; 這個語句,則其餘線程寫 t一、讀寫 t2 的語句都會被阻塞。同時,線程 A 在執行 unlock tables 以前,也只能執行讀 t一、讀寫 t2 的操做。連寫 t1 都不容許,天然也不能訪問其餘表。
InnoDB支持行鎖,因此通常不會用到表鎖。
MDL
InnoDB支持行鎖,MyISAM不支持行鎖,這個也是被InnoDB替代的緣由之一。
事務 B 的 update 語句會被阻塞,直到事務 A 執行 commit 以後,事務 B 才能繼續執行。
在 InnoDB 事務中,行鎖是在須要的時候才加上的,但並不是不須要了就馬上釋放, 而是要等到事務結束時才釋放。
建議:
若是你的事務中須要鎖多個行,要把最可能形成鎖衝突、最可能影響併發度的鎖儘可能日後放。
死鎖:
當併發系統中不同線程出現循環資源依賴,涉及的線程都在等待別的線程釋放資源時,就會致使這幾個線程都進入無限等待的狀態。
解決方案: 一、經過參數 innodb_lock_wait_timeout 根據實際業務場景來設置超時時間,InnoDB引擎默認值是50s。 二、發起死鎖檢測,發現死鎖後,主動回滾死鎖鏈條中的某一個事務,讓其餘事務得以繼續執行。將參數 innodb_deadlock_detect 設置爲 on,表示開啓這個邏輯(默認是開啓狀態)。
如何解決熱點行更新致使的性能問題? 一、若是你能確保這個業務必定不會出現死鎖,能夠臨時把死鎖檢測關閉掉。通常不建議採用 二、控制併發度,對應相同行的更新,在進入引擎以前排隊。這樣在InnoDB內部就不會有大量的死鎖檢測工做了。 三、將熱更新的行數據拆分紅邏輯上的多行來減小鎖衝突,可是業務複雜度可能會大大提升。
innodb支持RC和RR隔離級別實現是用的一致性視圖(consistent read view)
事務在啓動時會拍一個快照,這個快照是基於整個庫的. 基於整個庫的意思就是說一個事務內,整個庫的修改對於該事務都是不可見的(對於快照讀的狀況) 若是在事務內select t表,另外的事務執行了DDL t表,根據發生時間,要嘛鎖住要嘛報錯(參考第六章)
事務是如何實現的MVCC呢?
因爲當前讀都是先讀後寫,只能讀當前的值,因此爲當前讀.會更新事務內的up_limit_id爲該事務的transaction id
概念
當須要更新一個數據頁,若是數據頁在內存中就直接更新,若是不在內存中,在不影響數據一致性的前提下,InnoDB會將這些更新操做緩存在change buffer中。下次查詢須要訪問這個數據頁的時候,將數據頁讀入內存,而後執行change buffer中的與這個頁有關的操做。
優點
將數據從磁盤讀入內存涉及隨機IO的訪問,是數據庫裏面成本最高的操做之一。 change buffer 由於減小了隨機磁盤訪問,因此對更新性能的提高很明顯。
merge
將change buffer中的操做應用到原數據頁上,獲得最新結果的過程,稱爲merge 訪問這個數據頁會觸發merge,系統有後臺線程按期merge,在數據庫正常關閉的過程當中,也會執行merge
持久化
change buffer是能夠持久化的數據。在內存中有拷貝,也會被寫入到磁盤上,順序寫,沒有性能問題。
大小配置
change buffer用的是buffer pool裏的內存,change buffer的大小,能夠經過參數innodb_change_buffer_max_size來動態設置。這個參數設置爲50的時候,表示change buffer的大小最多隻能佔用buffer pool的50%。
使用場景
redo log是WAL機制的實現方式,同change buffer的目的相同吧,都是爲了減小隨機讀寫。
例子:經過一個場景來描述二者的區別
mysql> insert into t(id,k) values(id1,k1),(id2,k2);
其中k是索引。
當前的狀態是k1所在的數據頁在內存(InnoDB buffer pool)中;k2所在的數據頁不在內存中。
insert過程
從這邊能夠看出,在change buffer的參與下,執行的insert語句成本很低。(固然update和delete也是同樣的)
select過程mysql> select * from t where k in (k1,k2)
對於查詢過程來講:
對於更新過程來講:
惟一索引的更新不能使用change buffer
選擇索引是優化器的工做,而優化器選擇索引的目的,是找到一個最優的執行方案,並用最小的代價去執行語句。
在數據庫裏,掃描行數是影響執行代價的因素之一,掃描行數越少,意味着訪問磁盤數據的次數越少,消耗的cpu資源越少。
mysql選錯索引的狀況下,確定就是判斷掃描行數的時候出現了問題。
mysql在真正開始執行語句以前,並不能精確的知道知足這個條件的記錄有多少條,而只能根據統計信息來估算記錄數。
這個統計信息就是索引的「區分度」。顯然,一個索引上不一樣的值越多,這個索引的區分度就越好。而一個索引上不一樣的值的個數,咱們稱之爲「基數」(cardinality)。也就是說,這個基數越大,索引的區分度越好。
show index from t
就能夠看到每一個索引的基數(cardinality)。
mysql使用的是採樣統計的方法。
採樣統計的時候,InnoDB 默認會選擇 N 個數據頁,統計這些頁面上的不一樣值,獲得一個平均值,而後乘以這個索引的頁面數,就獲得了這個索引的基數。
analyze tabel t
analyze table t
從新統計。force index
強行選擇一個正確的索引。字符串通常佔用空間較大,**在對空間比較敏感的系統作操做的時候,而且不會存在範圍查詢的時候。**能夠考慮如下方案