mysql 必知必會【沈劍——公衆號架構師之路】

大神地址:https://mp.weixin.qq.com/s/pWHCieOwAdCrz8cauduWlQ
大神公衆號:架構師之路

第一章 數據庫索引

1.1 數據庫索引

數據庫索引用於加速查詢
雖然哈希索引是O(1),樹索引是O(log(n)),但SQL有很多“有序”需求,故數據庫使用樹型索引
InnoDB不支持哈希索引
數據預讀的思路是:磁盤讀寫並不是按需讀取,而是按頁預讀,一次會讀一頁的數據,每次加載更多的數據,以便未來減少磁盤IO
局部性原理:軟件設計要儘量遵循“數據讀取集中”與“使用到一個數據,大概率會使用其附近的數據”,這樣磁盤預讀能充分提高磁盤IO
數據庫的索引最常用B+樹:
(1)很適合磁盤存儲,能夠充分利用局部性原理,磁盤預讀;
(2)很低的樹高度,能夠存儲大量數據;
(3)索引本身佔用的內存很小;
(4)能夠很好的支持單點查詢,範圍查詢,有序性查詢;

1.2 MyISAM和InnoDb

1.2 MyISAM和InnoDb

1.2.1 MyISAM

MyISAM的索引與行記錄是分開存儲的,叫做非聚集索引(UnClustered Index)。

其主鍵索引與普通索引沒有本質差異:
有連續聚集的區域單獨存儲行記錄
主鍵索引的葉子節點,存儲主鍵,與對應行記錄的指針
普通索引的葉子結點,存儲索引列,與對應行記錄的指針
畫外音:MyISAM的表可以沒有主鍵。

主鍵索引與普通索引是兩棵獨立的索引B+樹,通過索引列查找時,先定位到B+樹的葉子節點,再通過指針定位到行記錄。

舉個例子,MyISAM:
t(id PK, name KEY, sex, flag);

表中有四條記錄:
1, shenjian, m, A
3, zhangsan, m, A
5, lisi, m, A
9, wangwu, f, B

其B+樹索引構造如上圖:
行記錄單獨存儲
id爲PK,有一棵id的索引樹,葉子指向行記錄
name爲KEY,有一棵name的索引樹,葉子也指向行記錄

1.2.2 InnoDb

InnoDB的索引*InnoDB的主鍵索引與行記錄是存儲在一起的,故叫做聚集索引(Clustered Index):
沒有單獨區域存儲行記錄
主鍵索引的葉子節點,存儲主鍵,與對應行記錄(而不是指針)
畫外音:因此,InnoDB的PK查詢是非常快的。
因爲這個特性,InnoDB的表必須要有聚集索引:

  • (1)如果表定義了PK,則PK就是聚集索引;
  • (2)如果表沒有定義PK,則第一個非空unique列是聚集索引;
  • (3)否則,InnoDB會創建一個隱藏的row-id作爲聚集索引;
    ** 聚集索引,也只能夠有一個,因爲數據行在物理磁盤上只能有一份聚集存儲。
    InnoDB的普通索引可以有多個,它與聚集索引是不同的:
    普通索引的葉子節點,存儲主鍵(也不是指針)**
    對於InnoDB表,這裏的啓示是:
    (1)不建議使用較長的列做主鍵,例如char(64),因爲所有的普通索引都會存儲主鍵,會導致普通索引過於龐大;
    (2)建議使用趨勢遞增的key做主鍵,由於數據行與索引一體,這樣不至於插入記錄時,有大量索引分裂,行記錄移動;

    仍是上面的例子,只是存儲引擎換成InnoDB:
    t(id PK, name KEY, sex, flag);

表中還是四條記錄:
1, shenjian, m, A
3, zhangsan, m, A
5, lisi, m, A
9, wangwu, f, B

其B+樹索引構造如上圖:
id爲PK,行記錄和id索引樹存儲在一起
name爲KEY,有一棵name的索引樹,葉子存儲id

當:
select * from t where name=‘lisi’;

會先通過name輔助索引定位到B+樹的葉子節點得到id=5,再通過聚集索引定位到行記錄。
畫外音:所以,其實掃了2遍索引樹。

三,總結
MyISAM和InnoDB都使用B+樹來實現索引:
MyISAM的索引與數據分開存儲
MyISAM的索引葉子存儲指針,主鍵索引與普通索引無太大區別
InnoDB的聚集索引和數據行統一存儲
InnoDB的聚集索引存儲數據行本身,普通索引存儲主鍵
InnoDB一定有且只有一個聚集索引
InnoDB建議使用趨勢遞增整數作爲PK,而不宜使用較長的列作爲PK

第二章 InnoDB併發如此高,原因竟然在這?

InnoDB併發如此高,原因竟然在這?

2.1 併發控制

爲啥要進行併發控制?
併發的任務對同一個臨界資源進行操作,如果不採取措施,可能導致不一致,故必須進行併發控制(Concurrency Control)。

技術上,通常如何進行併發控制?
通過併發控制保證數據一致性的常見手段有:
鎖(Locking)
數據多版本(Multi Versioning)

2.2 鎖

如何使用普通鎖保證一致性?
普通鎖,被使用最多:
(1)操作數據前,鎖住,實施互斥,不允許其他的併發任務操作;
(2)操作完成後,釋放鎖,讓其他任務執行;
如此這般,來保證一致性。

普通鎖存在什麼問題?
簡單的鎖住太過粗暴,連“讀任務”也無法並行,任務執行過程本質上是串行的。

於是出現了共享鎖與排他鎖:
共享鎖(Share Locks,記爲S鎖),讀取數據時加S鎖
排他鎖(eXclusive Locks,記爲X鎖),修改數據時加X鎖

共享鎖與排他鎖的玩法是:
共享鎖之間不互斥,簡記爲:讀讀可以並行
排他鎖與任何鎖互斥,簡記爲:寫讀,寫寫不可以並行

可以看到,一旦寫數據的任務沒有完成,數據是不能被其他任務讀取的,這對併發度有較大的影響。
畫外音:對應到數據庫,可以理解爲,寫事務沒有提交,讀相關數據的select也會被阻塞。

有沒有可能,進一步提高併發呢?
即使寫任務沒有完成,其他讀任務也可能併發,這就引出了數據多版本。

2.3 數據多版本

數據多版本是一種能夠進一步提高併發的方法,它的核心原理是:
(1)寫任務發生時,將數據克隆一份,以版本號區分;
(2)寫任務操作新克隆的數據,直至提交;
(3)併發讀任務可以繼續讀取舊版本的數據,不至於阻塞;

如上圖:

  1. 最開始數據的版本是V0;
  2. T1時刻發起了一個寫任務,這是把數據clone了一份,進行修改,版本變爲V1,但任務還未完成;
  3. T2時刻併發了一個讀任務,依然可以讀V0版本的數據;
  4. T3時刻又併發了一個讀任務,依然不會阻塞;

可以看到,數據多版本,通過“讀取舊版本數據”能夠極大提高任務的併發度。

提高併發的演進思路,就在如此:
普通鎖,本質是串行執行
讀寫鎖,可以實現讀讀併發
數據多版本,可以實現讀寫併發
畫外音:這個思路,比整篇文章的其他技術細節更重要,希望大家牢記。

好,對應到InnoDB上,具體是怎麼玩的呢?

2.4 redo, undo,回滾段

在進一步介紹InnoDB如何使用“讀取舊版本數據”極大提高任務的併發度之前,有必要先介紹下redo日誌,undo日誌,回滾段(rollback segment)。

爲什麼要有redo日誌?
數據庫事務提交後,必須將更新後的數據刷到磁盤上,以保證ACID特性。磁盤隨機寫性能較低,如果每次都刷盤,會極大影響數據庫的吞吐量。

優化方式是,將修改行爲先寫到redo日誌裏(此時變成了順序寫),再定期將數據刷到磁盤上,這樣能極大提高性能。
畫外音:這裏的架構設計方法是,隨機寫優化爲順序寫,思路更重要。

假如某一時刻,數據庫崩潰,還沒來得及刷盤的數據,在數據庫重啓後,會重做redo日誌裏的內容,以保證已提交事務對數據產生的影響都刷到磁盤上。

一句話,redo日誌用於保障,已提交事務的ACID特性。

爲什麼要有undo日誌?
數據庫事務未提交時,會將事務修改數據的鏡像(即修改前的舊版本)存放到undo日誌裏,當事務回滾時,或者數據庫奔潰時,可以利用undo日誌,即舊版本數據,撤銷未提交事務對數據庫產生的影響。
畫外音:更細節的,
對於insert操作,undo日誌記錄新數據的PK(ROW_ID),回滾時直接刪除;
對於delete/update操作,undo日誌記錄舊數據row,回滾時直接恢復;

他們分別存放在不同的buffer裏。

一句話,undo日誌用於保障,未提交事務不會對數據庫的ACID特性產生影響。

什麼是回滾段?
存儲undo日誌的地方,是回滾段。

undo日誌和回滾段和InnoDB的MVCC密切相關,這裏舉個例子展開說明一下。

栗子:
t(id PK, name);

數據爲:
1, shenjian
2, zhangsan
3, lisi

此時沒有事務未提交,故回滾段是空的。

接着啓動了一個事務:
start trx;
delete (1, shenjian);
update set(3, lisi) to (3, xxx);
insert (4, wangwu);
並且事務處於未提交的狀態。

可以看到:
(1)被刪除前的(1, shenjian)作爲舊版本數據,進入了回滾段;
(2)被修改前的(3, lisi)作爲舊版本數據,進入了回滾段;
(3)被插入的數據,PK(4)進入了回滾段;

接下來,假如事務rollback,此時可以通過回滾段裏的undo日誌回滾。
畫外音:假設事務提交,回滾段裏的undo日誌可以刪除。

可以看到:
(1)被刪除的舊數據恢復了;
(2)被修改的舊數據也恢復了;
(3)被插入的數據,刪除了;

事務回滾成功,一切如故。

四、InnoDB是基於多版本併發控制的存儲引擎
《大數據量,高併發量的互聯網業務,一定要使用InnoDB》提到,InnoDB是高併發互聯網場景最爲推薦的存儲引擎,根本原因,就是其多版本併發控制(Multi Version Concurrency Control, MVCC)。行鎖,併發,事務回滾等多種特性都和MVCC相關。

MVCC就是通過“讀取舊版本數據”來降低併發事務的鎖衝突,提高任務的併發度。

核心問題:
舊版本數據存儲在哪裏?
存儲舊版本數據,對MySQL和InnoDB原有架構是否有巨大沖擊?
通過上文undo日誌和回滾段的鋪墊,這兩個問題就非常好回答了:
(1)舊版本數據存儲在回滾段裏;
(2)對MySQL和InnoDB原有架構體系衝擊不大;

InnoDB的內核,會對所有row數據增加三個內部屬性:
(1)DB_TRX_ID,6字節,記錄每一行最近一次修改它的事務ID;
(2)DB_ROLL_PTR,7字節,記錄指向回滾段undo日誌的指針;
(3)DB_ROW_ID,6字節,單調遞增的行ID;

InnoDB爲何能夠做到這麼高的併發?
回滾段裏的數據,其實是歷史數據的快照(snapshot),這些數據是不會被修改,select可以肆無忌憚的併發讀取他們。

快照讀(Snapshot Read),這種一致性不加鎖的讀(Consistent Nonlocking Read),就是InnoDB併發如此之高的核心原因之一。

這裏的一致性是指,事務讀取到的數據,要麼是事務開始前就已經存在的數據(當然,是其他已提交事務產生的),要麼是事務自身插入或者修改的數據。

什麼樣的select是快照讀?
除非顯示加鎖,普通的select語句都是快照讀,例如:
select * from t where id>2;

這裏的顯示加鎖,非快照讀是指:
select * from t where id>2 lock in share mode;
select * from t where id>2 for update;

redolog參數配置
show variables like ‘%innodb_flush_log_at_trx_commit%’;

在這裏插入圖片描述
沈劍大神mysql 事務提交

親眼所見系列:[大神說應該選2 ,原因如上連接]
主庫 innodb_flush_log_at_trx_commit=1  強一致性
從庫 innodb_flush_log_at_trx_commit=0  性能最好,每隔一秒刷新寫入,數據庫崩潰可能有1秒數據丟失
innodb_flush_log_at_trx_commit=2 折中

總結
(1)常見併發控制保證數據一致性的方法有鎖,數據多版本;
(2)普通鎖串行,讀寫鎖讀讀並行,數據多版本讀寫並行;
(3)redo日誌保證已提交事務的ACID特性,設計思路是,通過順序寫替代隨機寫,提高併發;
(4)undo日誌用來回滾未提交的事務,它存儲在回滾段裏;
(5)InnoDB是基於MVCC的存儲引擎,它利用了存儲在回滾段裏的undo日誌,即數據的舊版本,提高併發;
(6)InnoDB之所以併發高,快照讀不加鎖;
(7)InnoDB所有普通select都是快照讀;

畫外音:本文的知識點均基於MySQL5.6。

2.5 兩種引擎對比

知識點:

  • MyISAM只支持表鎖,InnoDB可以支持行鎖。
  • MyISAM不支持事務,InnoDB支持事務。
  • MyISAM不支持外鍵,InnoDB支持外鍵。
  • MyISAM會直接存儲總行數,InnoDB則不會,需要按行掃描。

InnoDB的行鎖是實現在索引上的,而不是鎖在物理行記錄上。潛臺詞是,如果訪問沒有命中索引,也無法使用行鎖,將要退化爲表鎖。
啓示:InnoDB務必建好索引,否則鎖粒度較大,會影響併發。

在大數據量,高併發量的互聯網業務場景下,對於MyISAM和InnoDB

  • 有where條件,count(*)兩個存儲引擎性能差不多
  • 不要使用全文索引,應當使用《索引外置》的設計方案
  • 事務影響性能,強一致性要求才使用事務
  • 不用外鍵,由應用程序來保證完整性
  • 不命中索引,InnoDB也不能用行鎖

結論

  • 在大數據量,高併發量的互聯網業務場景下,請使用InnoDB:
  • 行鎖,對提高併發幫助很大
  • 事務,對數據一致性幫助很大
  • 這兩個點,是InnoDB最吸引人的地方。

第三章 七種鎖

3.1 自增鎖

自增鎖是一種特殊的表級別鎖(table-level lock),專門針對事務插入AUTO_INCREMENT類型的列。最簡單的情況,如果一個事務正在往表中插入記錄,所有其他事務的插入必須等待,以便第一個事務插入的行,是連續的主鍵值。
與此同時,InnoDB提供了innodb_autoinc_lock_mode配置,可以調節與改變該鎖的模式與行爲。

3.2 共享/排他鎖

InnoDB併發插入,居然使用意向鎖?
在InnoDB裏當然也實現了標準的行級鎖(row-level locking),共享/排它鎖:
(1)事務拿到某一行記錄的共享S鎖,纔可以讀取這一行;
(2)事務拿到某一行記錄的排它X鎖,纔可以修改或者刪除這一行;

修改就會將該行鎖住

3.3 意向鎖

InnoDB併發插入,居然使用意向鎖?
它會與共享鎖/排它鎖互斥,其兼容互斥表如下:
S X
IS 兼容 互斥
IX 互斥 互斥

意向鎖分爲:
意向共享鎖(intention shared lock, IS),它預示着,事務有意向對錶中的某些行加共享S鎖
意向排它鎖(intention exclusive lock, IX),它預示着,事務有意向對錶中的某些行加排它X鎖

舉個例子:
select … lock in share mode,要設置IS鎖;
select … for update,要設置IX鎖;

3.4 插入意向鎖

InnoDB併發插入,居然使用意向鎖?
插入意向鎖,是間隙鎖(Gap Locks)的一種(所以,也是實施在索引上的),它是專門針對insert操作的。
它的玩法是:
多個事務,在同一個索引,同一個範圍區間插入記錄時,如果插入的位置不衝突,不會阻塞彼此。

  • InnoDB使用共享鎖,可以提高讀讀併發;
  • 爲了保證數據強一致,InnoDB使用強互斥鎖,保證同一行記錄修改與刪除的串行性;
  • InnoDB使用插入意向鎖,可以提高插入併發;

3.5 記錄鎖鎖定索引記錄

  • InnoDB的索引與行記錄存儲在一起,這一點和MyISAM不一樣;
  • InnoDB的聚集索引存儲行記錄,普通索引存儲PK,所以普通索引要查詢兩次;
  • select * from t where id=1 for update;
  • select * from t where id=1; 快照讀,不加鎖

3.6 間隙鎖鎖定間隔,防止間隔中被其他事務插入

select * from t
where id between 8 and 15
for update;

3.7 臨鍵鎖鎖定索引記錄+間隔,防止幻讀;

表中有四條記錄:
1, shenjian, m, A
3, zhangsan, m, A
5, lisi, m, A
9, wangwu, f, B

PK上潛在的臨鍵鎖爲:
(-infinity, 1]
(1, 3]
(3, 5]
(5, 9]
(9, +infinity]

第四章 四種隔離級別

大神地址:4種事務的隔離級別,InnoDB如何巧妙實現?
可以看到,併發的事務可能導致其他事務:

  • 讀髒
  • 不可重複讀
  • 幻讀

按照SQL92標準,InnoDB實現了四種不同事務的隔離級別:

  • 讀未提交(Read Uncommitted)
  • 讀提交(Read Committed, RC)
  • 可重複讀(Repeated Read, RR)
  • 串行化(Serializable)

不同事務的隔離級別,實際上是一致性與併發性的一個權衡與折衷。

4.1 讀未提交(Read Uncommitted)

  • 這種事務隔離級別下,select語句不加鎖。
    此時,可能讀取到不一致的數據,即“讀髒”。這是併發最高,一致性最差的隔離級別。

4.2 串行化(Serializable)

  • 這種事務的隔離級別下,所有select語句都會被隱式的轉化爲select … in share mode(意向鎖)
  • 這可能導致,如果有未提交的事務正在修改某些行,所有讀取這些行的select都會被阻塞住。
  • 這是一致性最好的,但併發性最差的隔離級別。
    在互聯網大數據量,高併發量的場景下,幾乎不會使用上述兩種隔離級別。

4.3 可重複讀(Repeated Read, RR)

這是InnoDB默認的隔離級別,在RR下:

  • 普通的select使用快照讀(snapshot read),這是一種不加鎖的一致性讀(Consistent Nonlocking Read),底層使用MVCC來實現,
  • 加鎖的select(select … in share mode / select … for update), update, delete等語句,它們的鎖,依賴於它們是否在唯一索引(unique index)上使用了唯一的查詢條件(unique search condition),或者範圍查詢條件(range-type search condition):
  • 在唯一索引上使用唯一的查詢條件,會使用記錄鎖(record lock),而不會封鎖記錄之間的間隔,即不會使用間隙鎖(gap lock)與臨鍵鎖(next-key lock)
  • 範圍查詢條件,會使用間隙鎖與臨鍵鎖,鎖住索引記錄之間的範圍,避免範圍間插入記錄,以避免產生幻影行記錄,以及避免不可重複的讀

4.4 讀提交(Read Committed, RC)

這是互聯網最常用的隔離級別,在RC下:

  • 普通讀是快照讀;
  • 加鎖的select, update, delete等語句,除了在外鍵約束檢查(foreign-key constraint checking)以及重複鍵檢查(duplicate-key checking)時會封鎖區間,其他時刻都只使用記錄鎖;
  • 此時,其他事務的插入依然可以執行,就可能導致,讀取到幻影記錄。

4.5 總結

併發事務之間相互干擾,可能導致事務出現讀髒,不可重複度,幻讀等問題
InnoDB實現了SQL92標準中的四種隔離級別

  • 讀未提交:select不加鎖,可能出現讀髒;
  • 讀提交(RC):普通select快照讀,鎖select /update /delete 會使用記錄鎖,可能出現不可重複讀;
  • 可重複讀(RR):普通select快照讀,鎖select /update /delete 根據查詢條件情況,會選擇記錄鎖,或者間隙鎖/臨鍵鎖,以防止讀取到幻影記錄;
  • 串行化:select隱式轉化爲select … in share mode,會被update與delete互斥;
    InnoDB默認的隔離級別是RR,用得最多的隔離級別是RC

第五章 主鍵與唯一索引

總結,對於主鍵與唯一索引約束:

  • 執行insert和update時,會觸發約束檢查
  • InnoDB違反約束時,會回滾對應SQL
  • MyISAM違反約束時,會中斷對應的SQL,可能造成不符合預期的結果集
  • 可以使用 insert … on duplicate key 來指定觸發約束時的動作
  • 通常使用 show warnings; 來查看與調試違反約束的ERROR

第六章 緩衝池

【58沈劍大神】緩衝池(buffer pool),這次徹底懂了!!!

6.1 讀緩衝

總結:
(1)緩衝池(buffer pool)是一種常見的降低磁盤訪問的機制;
(2)緩衝池通常以頁(page)爲單位緩存數據;
(3)緩衝池的常見管理算法是LRU,memcache,OS,InnoDB都使用了這種算法;
(4)InnoDB對普通LRU進行了優化:

  • 將緩衝池分爲老生代和新生代,入緩衝池的頁,優先進入老生代,頁被訪問,才進入新生代,以解決預讀失效的問題
  • 頁被訪問,且在老生代停留時間超過配置閾值的,才進入新生代,以解決批量數據訪問,大量熱數據淘汰的問題
    參數:innodb_buffer_pool_size
    介紹:配置緩衝池的大小,在內存允許的情況下,DBA往往會建議調大這個參數,越多數據和索引放到內存裏,數據庫的性能會越好。
    innodb_buffer_pool_size數值的單位是字節(b),1kb=1024b,圖中63GB左右。

參數:innodb_old_blocks_pct
介紹:老生代佔整個LRU鏈長度的比例,默認是37,即整個LRU中新生代與老生代長度比例是63:37。
畫外音:如果把這個參數設爲100,就退化爲普通LRU了。

參數:innodb_old_blocks_time
介紹:老生代停留時間窗口,單位是毫秒,默認是1000,即同時滿足“被訪問”與“在老生代停留時間超過1秒”兩個條件,纔會被插入到新生代頭部。

6.2 寫緩衝

寫緩衝(change buffer),這次徹底懂了!!!
什麼時候適合使用寫緩衝,如果:
(1)數據庫大部分是非唯一索引;
(2)業務是寫多讀少,或者不是寫後立刻讀取;
可以使用寫緩衝,將原本每次寫入都需要進行磁盤IO的SQL,優化定期批量寫磁盤。
畫外音:例如,賬單流水業務。

參數:innodb_change_buffer_max_size
介紹:配置寫緩衝的大小,佔整個緩衝池的比例,默認值是25%,最大值是50%。
畫外音:寫多讀少的業務,才需要調大這個值,讀多寫少的業務,25%其實也多了。

參數:innodb_change_buffering
介紹:配置哪些寫操作啓用寫緩衝,可以設置成all/none/inserts/deletes等。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章