《高性能MySQL》读书笔记
生活随笔
收集整理的這篇文章主要介紹了
《高性能MySQL》读书笔记
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
《High Performance MySQL》真是本經典好書,從應用層到數據庫到硬件平臺,各種調優技巧、常見問題全都有所提及。數據庫的各種概念技巧平時都有接觸,像索引、分區、Sharding等等,但要想真正提高還是得如此系統學習一下。
Chapter 1: MySQL Architecture and History
1.1 Transaction Isolation Level
事務隔離級別真是個老生常談的問題的,但大多材料一提到臟讀、幻讀、重復讀就講得云里霧里,所以還是自己動手實踐能體會最深。
1.2 Implicit and Explicit Locking
InnoDB默認自動根據事務隔離級別管理鎖,同時支持兩種標準SQL未提及的顯示鎖(Explicit Locking):
1.3 Multiversion Concurrency Control
類似于樂觀鎖機制,但一些文章介紹到InnoDB實現不是純粹的MVCC。先標注一下,回頭進行深入源碼研究。 Innodb的實現真算不上MVCC,因為并沒有實現核心的多版本共存,undo log中的內容只是串行化的結果,記錄了多個事務的過程,不屬于多版本共存。但理想的MVCC是難以實現的,當事務僅修改一行記錄使用理想的MVCC模式是沒有問題的,可以通過比較版本號進行回滾;但當事務影響到多行數據時,理想的MVCC據無能為力了。
理想MVCC難以實現的根本原因在于企圖通過樂觀鎖代替二段提交。修改兩行數據,但為了保證其一致性,與修改兩個分布式系統中的數據并無區別,而二提交是目前這種場景保證一致性的唯一手段。二段提交的本質是鎖定,樂觀鎖的本質是消除鎖定,二者矛盾,故理想的MVCC難以真正在實際中被應用,Innodb只是借了MVCC這個名字,提供了讀的非阻塞而已。
Chapter?4: Optimizing Schema and Data Types
4.1 Choosing Optimal Data Types
本章一上來就精辟的提出了關于模式和數據類型的總設計原則,那就是:Smaller is usually better(越小通常越好):因為占用更少磁盤空間,內存以及CPU緩存,所以越小通常代表越快。 Simple is good(簡單的就是好的):因為字符集和排序規則(Collation)使得字符串的比較很復雜,所以我們應當用Integer等內建類型而非字符串來保存日期時間或IP地址。 Avoid NULL if possible(盡可能避免NULL):MySQL對NULL有特殊的處理邏輯,所以NULL會使索引、索引統計、值比較都變得更加復雜。
4.2 Using ENUM Instead Of A String Type
MySQL內部將枚舉保存為整數,通過一張Lookup Table保存枚舉與整數的對應關系。所以使用枚舉非常節省空間(原則1越小越好越快),根據枚舉總個數而定,只會占用1或2個字節。 但是隨之而來的問題是:添加刪除枚舉值都要ALTER TABLE。并且使用Lookup Table進行轉換時也會有開銷,尤其是與CHAR或VARCHAR類型的列做聯接時,但有時這種開銷可以被枚舉節省空間的優勢所抵消。
4.3 Cons of A Normalized Schema
規范化范式(Normalized Schema)不僅增加JOIN數,并且會使本可以屬于一個索引的列分隔到不同的表中。 例如:SELECT ... FROM message INNER JOIN user USING(user_id) ? ? ?WHERE user.account_type = 'premium' ? ? ?ORDER BY message.published DESC LIMIT 10 則有兩種執行計劃:倒序走published索引掃描message表,每行都去user表檢查是否type為'premium',直到找到10行。 走account_type索引掃描user表找到所有type為'premium'的行,進行filesort后返回10行。
上面的問題本質在于:JOIN使我們無法通過一個索引就同時完成排序和過濾。 改為非規范化 =>?SELECT .. FROM user_message? ? ? ?WHERE account_type = 'premium' ? ? ?ORDER BY published DESC LIMIT 10 則(account_type, published)上的索引能高效地完成任務!
4.4 Cache and Summary Tables
這一部分緊接上面關于Normalized和Denormalized Schema的Pros and Cons的討論,從4.4到4.6提出了幾種冗余數據的常用且實用的方法。這幾種技術本質上都是為了加速查詢操作,但代價是拖慢了寫操作,并且會增加開發的復雜度。
緩存表(Cache Table)指那些包含能夠輕松從Schema中獲得的數據的表,即表中的數據是邏輯冗余(Logically Redundant)。匯總表(Summary/Roll-up Table)是說包含通過聚合函數得到的數據的表,例如表中數據是通過GROUP BY得到的。
為什么需要它們呢?最常見的場景就是報表等統計工作。生成這些統計數據要掃描大量數據,實時計算成本很高且很多時候沒有必要。而且查詢這些數據還要加大量組合索引(各種維度的)才能提高性能,然而這些索引又會對平時的更新和插入等操作造成影響。于是常用技術就是添加中間表到其他引擎(利用MyISAM更小的索引和全文檢索能力),甚至其他系統(Lucene或Sphinx)。
有了中間表作為緩存,我們需要定期的更新或者重建它。影子表(Shadow Table)是一種不錯的技術! mysql>?DROP TABLE IF EXISTS my_summary_new, my_summary_old; mysql>?CREATE TABLE my_summary_new LIKE my_summary; mysql>?RENAME TABLE my_summary TO my_summary_old, my_summary_new TO my_summary 只需一條rename操作,我們就可以原子地將影子表替換上去(swap with an atomic rename),并且之前的表也保留下來以防需要回滾。
4.5 Materialized Views
物化視圖即預先計算并真正存儲在磁盤上的視圖(一般視圖是不會實際存儲,在訪問視圖時執行對應的SQL獲得數據)。MySQL沒有物化視圖,但有一個很棒的開源實現Flexviews Tools。它有一些很有用的功能,例如:CDC(Change Data Capture)工具能夠讀取日志(Binary Logs),并提取對應的行變化。 一組幫助定義和管理視圖的存儲過程 將改變反應到物化視圖數據上的工具
具體來說,它利用基于行的日志(Row-based Binary Log)包含了變化行的前后數據,所以Flextviews能夠在無需訪問源表的情況下,知道變化前和變化后的數據,并重新計算物化視圖。這是它相比我們自己維護的Cache表或Summary表的優勢。
4.6 Counter Tables
Web應用一個常見問題就是并發訪問計數表,此書中提出方案來提高并發量??傮w設計思路是:添加更多的槽來分散并發的訪問。與Java的Concurrent并發包中的ConcurrentHashMap的設計理念有些像。
計數表和對應訪問SQL可以簡化如下: mysql>?CREATE TABLE hit_counter(cnt int unsigned not null) ENGINE=InnoDB; mysql>?UPDATE hit_counter SET cnt = cnt + 1;
可以看出,表中的一行計數器數據其實相當于全局鎖,對它的更新將會被串行化。所以,首先建表時加入Slot一列。并初始化100條數據。 CREATE TABLE hit_counter( ? ? ?slot tinyint unsigned not null primary key, ? ? ?cnt int unsigned not null )?ENGINE=InnoDB;
之后將更新和查詢SQL改為: mysql>?UPDATE hit_counter SET cnt = cnt + 1 WHERE slot = RAND() * 100; mysql>?SELECT SUM(cnt) FROM hit_counter;
ps:如果需要每天刷新計數器的話,那么建表時就加入時間列: CREATE TABLE daily_hit_counter( ? ? ?day date not null, ? ? ?slot tinyint unsigned not null primary key, ? ? ?cnt int unsigned not null, ? ? ?primary key(day, slot) )?ENGINE=InnoDB;
pss:如果不想每天都插入初始數據的話,可以用下面的SQL: mysql>?INSERT INTO daily_hit_counter(day, slot, cnt)? ? ? ?VALUES(CURRENT_DATE, RAND() * 100, 1) ? ? ?ON DUPLICATE KEY UPDATE cnt = cnt + 1;
psss:如果想減少計數器的行數來節約空間,那么可以執行一個定期任務來合并所有記錄到Slot 0:
Chapter 5:?Indexing for High Performance
5.1 B-Tree Family
一般我們討論數據庫索引時,其實指的都是B樹索引,MySQL的CREATE TABLE及其他語句中也的確使用這種說法。然而實際上,存儲引擎內部可能會使用不同的存儲結構。例如NDB使用T樹(關于不同的索引類型,T樹就非常適合內存存儲),而InnoDB使用B+樹。
所以準確地說我們使用的是B樹大家族里B樹的各種變形。各種變形的核心是圍繞著內結點出度(例如基于內存的T樹和基于磁盤的B樹)、存儲使用率(B樹和B+樹)等方面進行的。
首先B樹與其他數據結構如紅黑樹、普通AVL樹的最大區別就是:B樹的結點有很多個子結點。而這一點正是為減少磁盤I/O讀取開銷而設計。因為子結點很多,所以樹的總體高度很低,這樣就只需加載少量的磁盤頁就能查找到目標數據。那關于B樹和B+樹的區別呢:B+樹的內結點不存data(即指向key所在數據行的指針),只存key。 B+樹的優勢:
首先引用一個B樹查找的例子:
“下面,咱們來模擬下查找文件29的過程:
根據根結點指針找到文件目錄的根磁盤塊1,將其中的信息導入內存?!敬疟PIO操作 1次】 ? ? 此時內存中有兩個文件名17、35和三個存儲其他磁盤頁面地址的數據。根據算法我們發現:17<29<35,因此我們找到指針p2。 根據p2指針,我們定位到磁盤塊3,并將其中的信息導入內存?!敬疟PIO操作 2次】 ? ? 此時內存中有兩個文件名26,30和三個存儲其他磁盤頁面地址的數據。根據算法我們發現:26<29<30,因此我們找到指針p2。 根據p2指針,我們定位到磁盤塊8,并將其中的信息導入內存。【磁盤IO操作 3次】 ? ? 此時內存中有兩個文件名28,29。根據算法我們查找到文件名29,并定位了該文件內存的磁盤地址。 分析上面的過程,發現需要3次磁盤IO操作和3次內存查找操作。關于內存中的文件名查找,由于是一個有序表結構,可以利用折半查找提高效率。至于IO操作是影響整個B樹查找效率的決定因素。當然,如果我們使用平衡二叉樹的磁盤存儲結構來進行查找,磁盤4次,最多5次,而且文件越多,B樹比平衡二叉樹所用的磁盤IO操作次數將越少,效率也越高。
而B+樹就是這個樣子: ?
Chapter 1: MySQL Architecture and History
1.1 Transaction Isolation Level
事務隔離級別真是個老生常談的問題的,但大多材料一提到臟讀、幻讀、重復讀就講得云里霧里,所以還是自己動手實踐能體會最深。
1.2 Implicit and Explicit Locking
InnoDB默認自動根據事務隔離級別管理鎖,同時支持兩種標準SQL未提及的顯示鎖(Explicit Locking):
- SELECT ... LOCK IN SHARE MODE
- SELECT ... FOR UPDATE
- LOCK/UNLOCK TABLES
1.3 Multiversion Concurrency Control
類似于樂觀鎖機制,但一些文章介紹到InnoDB實現不是純粹的MVCC。先標注一下,回頭進行深入源碼研究。 Innodb的實現真算不上MVCC,因為并沒有實現核心的多版本共存,undo log中的內容只是串行化的結果,記錄了多個事務的過程,不屬于多版本共存。但理想的MVCC是難以實現的,當事務僅修改一行記錄使用理想的MVCC模式是沒有問題的,可以通過比較版本號進行回滾;但當事務影響到多行數據時,理想的MVCC據無能為力了。
理想MVCC難以實現的根本原因在于企圖通過樂觀鎖代替二段提交。修改兩行數據,但為了保證其一致性,與修改兩個分布式系統中的數據并無區別,而二提交是目前這種場景保證一致性的唯一手段。二段提交的本質是鎖定,樂觀鎖的本質是消除鎖定,二者矛盾,故理想的MVCC難以真正在實際中被應用,Innodb只是借了MVCC這個名字,提供了讀的非阻塞而已。
Chapter?4: Optimizing Schema and Data Types
4.1 Choosing Optimal Data Types
本章一上來就精辟的提出了關于模式和數據類型的總設計原則,那就是:
MySQL內部將枚舉保存為整數,通過一張Lookup Table保存枚舉與整數的對應關系。所以使用枚舉非常節省空間(原則1越小越好越快),根據枚舉總個數而定,只會占用1或2個字節。 但是隨之而來的問題是:添加刪除枚舉值都要ALTER TABLE。并且使用Lookup Table進行轉換時也會有開銷,尤其是與CHAR或VARCHAR類型的列做聯接時,但有時這種開銷可以被枚舉節省空間的優勢所抵消。
4.3 Cons of A Normalized Schema
規范化范式(Normalized Schema)不僅增加JOIN數,并且會使本可以屬于一個索引的列分隔到不同的表中。 例如:SELECT ... FROM message INNER JOIN user USING(user_id) ? ? ?WHERE user.account_type = 'premium' ? ? ?ORDER BY message.published DESC LIMIT 10 則有兩種執行計劃:
上面的問題本質在于:JOIN使我們無法通過一個索引就同時完成排序和過濾。 改為非規范化 =>?SELECT .. FROM user_message? ? ? ?WHERE account_type = 'premium' ? ? ?ORDER BY published DESC LIMIT 10 則(account_type, published)上的索引能高效地完成任務!
4.4 Cache and Summary Tables
這一部分緊接上面關于Normalized和Denormalized Schema的Pros and Cons的討論,從4.4到4.6提出了幾種冗余數據的常用且實用的方法。這幾種技術本質上都是為了加速查詢操作,但代價是拖慢了寫操作,并且會增加開發的復雜度。
緩存表(Cache Table)指那些包含能夠輕松從Schema中獲得的數據的表,即表中的數據是邏輯冗余(Logically Redundant)。匯總表(Summary/Roll-up Table)是說包含通過聚合函數得到的數據的表,例如表中數據是通過GROUP BY得到的。
為什么需要它們呢?最常見的場景就是報表等統計工作。生成這些統計數據要掃描大量數據,實時計算成本很高且很多時候沒有必要。而且查詢這些數據還要加大量組合索引(各種維度的)才能提高性能,然而這些索引又會對平時的更新和插入等操作造成影響。于是常用技術就是添加中間表到其他引擎(利用MyISAM更小的索引和全文檢索能力),甚至其他系統(Lucene或Sphinx)。
有了中間表作為緩存,我們需要定期的更新或者重建它。影子表(Shadow Table)是一種不錯的技術! mysql>?DROP TABLE IF EXISTS my_summary_new, my_summary_old; mysql>?CREATE TABLE my_summary_new LIKE my_summary; mysql>?RENAME TABLE my_summary TO my_summary_old, my_summary_new TO my_summary 只需一條rename操作,我們就可以原子地將影子表替換上去(swap with an atomic rename),并且之前的表也保留下來以防需要回滾。
4.5 Materialized Views
物化視圖即預先計算并真正存儲在磁盤上的視圖(一般視圖是不會實際存儲,在訪問視圖時執行對應的SQL獲得數據)。MySQL沒有物化視圖,但有一個很棒的開源實現Flexviews Tools。它有一些很有用的功能,例如:
4.6 Counter Tables
Web應用一個常見問題就是并發訪問計數表,此書中提出方案來提高并發量??傮w設計思路是:添加更多的槽來分散并發的訪問。與Java的Concurrent并發包中的ConcurrentHashMap的設計理念有些像。
計數表和對應訪問SQL可以簡化如下: mysql>?CREATE TABLE hit_counter(cnt int unsigned not null) ENGINE=InnoDB; mysql>?UPDATE hit_counter SET cnt = cnt + 1;
可以看出,表中的一行計數器數據其實相當于全局鎖,對它的更新將會被串行化。所以,首先建表時加入Slot一列。并初始化100條數據。 CREATE TABLE hit_counter( ? ? ?slot tinyint unsigned not null primary key, ? ? ?cnt int unsigned not null )?ENGINE=InnoDB;
之后將更新和查詢SQL改為: mysql>?UPDATE hit_counter SET cnt = cnt + 1 WHERE slot = RAND() * 100; mysql>?SELECT SUM(cnt) FROM hit_counter;
ps:如果需要每天刷新計數器的話,那么建表時就加入時間列: CREATE TABLE daily_hit_counter( ? ? ?day date not null, ? ? ?slot tinyint unsigned not null primary key, ? ? ?cnt int unsigned not null, ? ? ?primary key(day, slot) )?ENGINE=InnoDB;
pss:如果不想每天都插入初始數據的話,可以用下面的SQL: mysql>?INSERT INTO daily_hit_counter(day, slot, cnt)? ? ? ?VALUES(CURRENT_DATE, RAND() * 100, 1) ? ? ?ON DUPLICATE KEY UPDATE cnt = cnt + 1;
psss:如果想減少計數器的行數來節約空間,那么可以執行一個定期任務來合并所有記錄到Slot 0:
Chapter 5:?Indexing for High Performance
5.1 B-Tree Family
一般我們討論數據庫索引時,其實指的都是B樹索引,MySQL的CREATE TABLE及其他語句中也的確使用這種說法。然而實際上,存儲引擎內部可能會使用不同的存儲結構。例如NDB使用T樹(關于不同的索引類型,T樹就非常適合內存存儲),而InnoDB使用B+樹。
所以準確地說我們使用的是B樹大家族里B樹的各種變形。各種變形的核心是圍繞著內結點出度(例如基于內存的T樹和基于磁盤的B樹)、存儲使用率(B樹和B+樹)等方面進行的。
首先B樹與其他數據結構如紅黑樹、普通AVL樹的最大區別就是:B樹的結點有很多個子結點。而這一點正是為減少磁盤I/O讀取開銷而設計。因為子結點很多,所以樹的總體高度很低,這樣就只需加載少量的磁盤頁就能查找到目標數據。那關于B樹和B+樹的區別呢:B+樹的內結點不存data(即指向key所在數據行的指針),只存key。 B+樹的優勢:
- 因為內部結點不存data了,所以在一個磁盤頁上能存更多的key了,樹的高度進一步降低,從而加快key的查找命中。
- 需要全樹遍歷時(如某字段的范圍查詢甚至full scan,這都是很常見而頻繁的查詢操作),只需要對B+樹的葉子結點進行線性遍歷即可,而B樹則需要樹遍歷。而線性遍歷比樹遍歷命中率更高(因為相鄰數據都很近,不會分散在結點的左右子樹中,跨頁的概率能低一些吧)
- 在B樹中查找可能在內部結點結束,而B+樹則必須在葉子結點結束。
首先引用一個B樹查找的例子:
“下面,咱們來模擬下查找文件29的過程:
而B+樹就是這個樣子: ?
總結
以上是生活随笔為你收集整理的《高性能MySQL》读书笔记的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: mongodb 群集图_群集和重叠条形图
- 下一篇: 导图速读《高性能MySQL》