《高性能MYSQL》
MYSQL的架構和歷史
1.1 MYSQL邏輯架構
 第一層:連接處理,授權認證,安全 等等
 第二層:查詢解析、分析、優化、緩存 以及 所有的內置函數。包含跨存儲引擎的功能:存儲過程、觸發器、視圖等。
 第三層:存儲引擎。負責MySQL中數據的存儲和提取。
1.2并發控制
無論何時,只要有多個查詢需要在同一時刻修改數據,都會產生并發控制的問題。
 討論mysql兩個層面的并發控制:存儲引擎層 與 服務器層
讀寫鎖
 在處理并發寫或并發讀寫時,可以通過由兩種類型的鎖組成的鎖系統來解決問題,這兩種鎖就是 共享鎖(讀鎖) 和 排它鎖(寫鎖)。讀鎖是共享的,互相不阻塞,寫鎖是排它的,只有一個線程能進行寫操作,其他讀鎖和寫鎖都是阻塞的。
并且,寫鎖擁有更高的優先級。在一個鎖隊列中,寫鎖可以插到讀鎖的前面。
** 鎖粒度**
 一種理想的鎖方式是,盡量只鎖定需要修改的資源,而不是所有資源。鎖定的數據量越小,并發程度越高。
但是鎖也是需要時空開銷的,判斷是否有鎖、加鎖、釋放鎖的操作都需要額外的開銷,如果鎖粒度太小,雖然并發程度高,但系統花大量資源去管理鎖,而不是存取數據,也是得不償失。
鎖策略
 就是在 鎖的開銷 和 數據安全性 之間尋求平衡。大多數據庫都是行級鎖 ,而mysql提供了更多鎖的可能性。每種存儲引擎都可以實現自己的鎖策略和鎖粒度。將鎖粒度固定在某一級別,可以為特定的應用場景提供更好的性能,但同時也會失去對一些應用場景的支持。但好在mysql支持多個存儲引擎。
表鎖
 表鎖是mysql中最基本、開銷最小的鎖策略。盡管存儲引擎可以設計管理自己的鎖,但mysql服務器還是利用表鎖來實現不同的目的。例如:會為alter table之類的語句使用表鎖,而忽略存儲引擎的鎖機制。
行級鎖
 行級鎖是mysql中支持并發量最大、開銷最大的鎖策略。行級鎖只在存儲引擎層實現,服務器層沒有實現。并且服務器層完全不了解存儲引擎層的鎖實現。
1.3 事務
事務是一組原子性的操作,是一個獨立執行單元。事務內的語句,要么全部執行成功,要么全部執行失敗。
事務的四個標準特征(ACID)
- 原子性(atomicity):一個事務必須被視為不可分割的最小工作單元,整個事務的所有操作要么全部提交成功,要么全部失敗回滾。
- 一致性(consistency):數據庫總是保持一致性的狀態
- 隔離性(isolation):通常來說,一個事務在沒有最終提交以前,對其它事務是不可見的。
- 持久性(durability):一旦事務提交,則其所做的唏噓該就會永久保存到數據庫中。
隔離級別
- 未提交讀(READ UNCOMMITTED):事務中的修改,即使沒有提交,對其他事務也都是可見的。臟讀問題(事務1改,事務2讀,事務1回滾)。
- 提交讀(READ COMMITTED):大多數數據庫的默認隔離級別是提交讀(MySQL不是)。事務從開始直到提交之前,所作的修改對其他事務都是不可見的。也叫不可重復讀。不可重復讀問題(事務1讀,事務2改,事務1讀)。
- 可重復讀(REPEATABLE READ):保證在同一個事務中多次讀取同樣記錄的結果是一致的。可重復讀是MySQL的默認事務隔離級別。幻讀(事務1范圍操作,事務2插入數據,事務1讀,產生幻行)
- 可串行化(SERIALIZABLE):非常需要數據的一致性和接受沒有并發的情況性,才考慮該級別。
死鎖
 當兩個或多個事物占用著自己的資源,而都在等待對方占用的資源時,會形成死鎖。
一種是在事務開始前檢測死鎖的循環依賴,若有可能導致死鎖,則報錯。
 一種是發生死鎖時,設置最長等待時間,大于這個時間則放棄資源(不推薦,性能變差)。
 一種是發生死鎖時,將擁有最少行級寫鎖的事務回滾。
1.4 多版本并發控制
以InnoDB的MVCC為例,InnoDB以 在每行記錄后面保存兩個隱藏的列 來實現MVCC。這兩個列,一列保存 這一行的創建時間,一列保存 這一行的過期時間。但存儲的并不是真正的時間,而是 系統版本號。每開始一個事務,系統版本號就會自增。事務通過自身的系統版本號與這兩個隱藏的列對比,來操作數據。
在可重復度的隔離級別下,InnoDB的MVCC的具體操作如下:
- select InnoDB查找 創建系統版本號 <= 事務系統版本號的行,即在事務開始前就存在的行,或者由事務自身插入或修改的行。同時該行的過期系統版本號要么未定義,要么 > 事務系統版本號。
- insert 新插入的每一行 創建時間都是當前事務的系統版本號。
- delete 刪除的每一行 過期時間都是當前事務的系統版本號。
- update 插入一條新數據,創建時間是當前事務的系統版本號,將原來行的過期時間置為當前事務的系統版本號。
MVCC只在 提交讀 和 可重復讀 兩個隔離級別下工作,因為未提交讀總是能讀取到最新的行,而不是符合當前事務版本的行,而串行化會對所有行加鎖。
MyISAM和InnoDB的區別
事務: MyISAM不支持事務,InnoDB支持事務。
 鎖粒度: MyISAM只支持表鎖。InnoDB支持行鎖。
 存儲: MyISAM存三個文件(.frm .MYD .MYI),支持動態,靜態和壓縮三種存儲格式。InnoDB存一個文件(.frm)。
 外鍵: MyISAM不支持外鍵,InnoDB支持外鍵。
 索引: MyISAM是索引+行指針,InnoDB是聚簇+非聚簇(這個點決定了其主鍵的必要性)。
 安全: MyISAM不支持崩潰安全回復,InnoDB支持(redo_log)。
 行數統計: MyISAM中維護一個計數器記錄總行數,select(*)時很快,而InnoDB需要全表掃描,所以較慢。
Schema與數據類型優化
官方文檔地址: Mysql查詢優化
4.1選擇優化的數據類型
-  更小的通常更好。 
 ? 應該盡量使用可以正確存儲數據的最小類型,更小的數據類型通常更快,因為他們占用更少的磁盤,內存和CPU緩存,并且處理時需要的CPU周期更少。
-  簡單就好 
 ? 更簡單的數據類型的操作通常需要更少的CPU周期。例如,整型數字比字符操作代價更低,因為字符集和校對規則(排序規則)使字符比較相對整型數字比較更復雜。比如,應使用INTERGER存儲IP地址(inet_aton)
-  盡量避免NULL 
 ? 通常情況下,最好指定列為NOT NULL。如果查詢中包含可為NULL的列,對MySQL來說更難優化,因為可為NULL的列使得索引,索引統計和值比較非常復雜,可為NULL的列會使用更多的存儲空間,當可謂NULL的列被索引時,每個索引記錄需要一個額外的字節。但是把可為NULL的列改成NOT NULL帶來的性能提升比較小,但如果計劃在列上創建索引,就應該避免設計成可為NULL的列。
4.1.1整數類型
| TINYINT | 8 | [-2^7,2^7-1] | 
| SMALLINT | 16 | [-2^15,2^15-1] | 
| MEDIUMINT | 24 | [-2^23,2^23-1] | 
| INT | 32 | [-2^31,2^31-1] | 
| BIGINT | 64 | [-2^63,2^63-1] | 
INT(11)只是指定顯示字符的范圍。不會限制值得范圍。
4.1.1.2實數類型
實數是帶有小數部分的數字,可以使用DECIMAL存儲比BIGINT還大的整數。
 DECIMAL類型用于存儲精確的小數,支持精確計算。
4.1.3字符串類型
varchar類型用于存儲可變長字符串,比定長更節省空間。
 char 定長字符串,MySQL在存儲時會去除char尾部的空格。
 blob
 ? 采用二進制的方式存儲,沒有排序規則和字符集。包含tinyblob,blob,mediumblob,longblob
 text
 ? 采用字符串的方式存儲,有排序規則和字符集,包含tinytext,text,mediumtext,longtext。
4.1.4日期和時間類型
DATETIME和TIMESTAMP
 ? 現在推薦使用DATETIME,范圍更大,與時區無關,占用8個字節
 datetime:大范圍的值 1001 9999 s YYYYMMDDHHMMSS 與時區無關 8字節。
 timestamp:1970 2038,1970 1 1以來的秒數,時區 4字節 。
4.2MySQL 模式設計的陷阱
太多的列
 太多的關聯
 NULL值
4.3范式和反范式
? 在范式化的數據庫中,每個事實數據只會出現一次,
 ? 反范式化的數據庫中,信息是冗余的,可能會存儲在多個地方。
范式優點:
 ? 范式化的更新操作更快,只需要更改較少的數據。
 ? 范式化的表更小,可以更好的放在內存里,執行操作會更快。
 ? 沒有多余的數據,可以減少distinct或GROUP BY的操作。
 范式缺點:
 ? 通常需要關聯,關聯代價昂貴,也可能使一些索引策略無效。
反范式優點:
 ? 所有的數據都在一張表中,可以避免關聯。
 ? 不關聯的時候即使全表掃描,也是順序IO。
 反范式缺點:
 ? 冗余的多余數據,更新更慢
 ? 表大,放到內存中,占用大,容易擠出熱數據
4.4緩存表和匯總表
4.5 加快Alter table 的速度
? ALTER TABLE操作對特大表來說,是個大問題。
 只修改frm(表結構)文件
創建高性能的索引
索引是存儲引擎用于快速找到記錄的一種數據結構。
5.1 索引基礎
要理解MySQL中索引是如何工作的,最簡單的方法就是去看看一本書的 “索引” 部分:如果想在一本書中找到某個特定主題,一般會先看書的“索引”,找到對應的頁碼。
在MySQL中, 存儲引擎用類似的方法使用索引, 其先在索引中找到對應值, 然后根據匹配的索引記錄找到對應的數據行。 假如要運行下面的查詢:
mysql> SELECT first_name FRom sakila.actor WHERE actor_id = 5;如果在actor_id列上建有索引, 則MySQL將使用該索引找到actor_id為5的行, 也就是說, MySQL先在索引上按值進行查找, 然后返回所有包含該值的數據行。
索引可以包含一個列或多個列的值。如果索引包含多個列,那么列的順序也很重要。因為MYSQL只能高效地使用索引的最左前綴列。
索引結構中字段先后不受制于查詢中相等判斷條件表達式字段的順序,而受制于非等條件判斷表達式。
索引的類型
 在MySQL中, 索引是在存儲引擎層而不是服務器層實現的。
B-Tree索引
- 全值匹配:指和索引中所有的列進行匹配。例如匹配key的所有字段(last_name,first_name,dob)。
- 匹配最左前綴:只使用索引的第一列。例如只使用last_name.
- 匹配列前綴:也可匹配某列的值開頭部分。比如以J開頭的人。這里只使用索引的第一列。
- 匹配范圍值:查找姓在Allen到Barry之間的人。這里只使用索引的第一列。
- 精確匹配某一列并范圍匹配另一列:第一列last_name精確匹配,第二列first_name范圍匹配。
- 只訪問索引的查詢:即查詢只需要訪問索引。
B-Tree索引限制
- 如果不是按照索引的最左列開始查找,則無法使用索引。如上面例子的索引無法查找名稱為BIll的人。也無法查找某個特定生日的人。
 不能跳過索引中的列。無法查找姓為Smath并且在特定日期出生的人,因為跳過了first_name 列。
- 如果查詢中有某個列的范圍查詢,則其右邊所有的列都無法使用索引優化查找。例如查詢Where last_name = ‘Smath’ and first_name like ‘%J’ and dob=‘1970-02-01’.這個查詢只能使用索引的前兩列,因為like是一個范圍條件。
哈希索引
 哈希索引使用哈希表實現,只有精確匹配索引所有列的查詢才有效。
哈希索引限制
- 哈希索引只能包含哈希值和行指針。所以不能用索引的值避免讀行。
- 哈希索引并不是按照索引值順序存儲的,所以無法進行排序。
- 哈希索引不支持部分索引匹配列查找。
- 哈希索引只支持等值比較查詢。
- 訪問哈希索引非常快,除非有哈希沖突。當哈希出現沖突時,會進行連表存儲。
- 哈希沖突時,索引重建
 代價會很高。
InnoDB引擎有一個特殊的功能叫做 “自適應哈希索引(adaptivehash index)"。當InnoDB注意到某些索引值被使用得非常頻繁時,它會在內存中基千B-Tree索引之上再創建一個哈希索引,這樣就讓B-Tree索引也具有哈希索引的一些優點,比如快速的哈希 查找。
5.2 索引的優點
5.3 高性能的索引策略
獨立的列
slelect actor_id from actor where actor_id +1=5actor_id +1=5無法被解析成actor_id = 4。所以要將索引列單獨存放在比較符合的一側。
前綴索引和索引選擇性
 有時候需要索引很長的字符列, 這會讓索引變得大且慢。
 通常可以索引開始的部分字符,這樣可以大大節約索引空間, 從而提高索引效率。但這樣也會降低索引的選擇性。索引的選擇性是指,不重復的索引值(也稱為基數,cardinality)和數據表的記錄總數(#T)的比值,范圍從1/#T到1之間。索引的選擇性越高則查詢效率越高,因為選擇性高的索引可以讓MySQL在查找時過濾掉更多的行。唯一索引的選擇性是1, 這是最好的索引選擇性,性能也是最好的。
不重復索引值/記錄總數接近0.031就可以使用了。
slelect count(DISTINT city) /count(*) from city_dome. ALERT TABLE city ADD KEY (city(7)).多列索引
 單列索引會引起索引的合并,并不是最優策略。
選擇合適的索引列順序
- 當不需要考慮排序和分組時,將選擇性最高的列放在前面通常是很好的。這時候索引的作用只是用于優化WHERE條件的查找。
通過執行
slelect sum(customer_id=30),sum(staff_id=584) from payment哪個列的基數小,需要把customer_id放在最前列。
前綴索引的條件值基數比正常值高的時候,索引基本沒什么用。比如索引的列滿足全表所有的行。
聚簇索引
聚簇索引不是一種單獨的索引類型,而是一種數據存儲方式。InnoDB的聚簇索引保存了B-Tree索引和數據行。
 
覆蓋索引
 如果索引包含所需要查詢字段的值,成為覆蓋索引。
 覆蓋索引好處:
- 索引條目通常遠小于數據行大小, 所以如果只需要讀取索引, 那MySQL就會極大地減少數據訪問量。
- 因為索引是按照列值順序存儲的(至少在單個頁內是如此), 所以對千1/0密集型的范圍查詢會比隨機從磁盤讀取每一行數據的I/O要少得多。
- 由于InnoDB的聚簇索引, 覆蓋索引對lnnoDB表特別有用。InnoDB的二級索引在葉子節點中保存了行的主鍵值,所以如果二級主鍵能夠覆蓋查詢, 則可以避免對主鍵索引的二次查詢。
覆蓋索引缺點:
 1、插入速度嚴重依賴于插入順序,按照主鍵的順序插入是加載數據頁到InnoDB表中速度最快的方式。但是如果不按照主鍵順序加載數據,那么在加載完成后最好使用 optimize table 命令重新組織一下表。
2、更新聚簇索引列的代價很高,因為會強制InnoDB將每個被更新的行移動到新的位置。插入的時候會面臨頁分裂的問題。頁分裂會導致表占用更多的磁盤空間。
3、二級索引可能比想象的大,因為二級索引的葉子結點保存了引用行的主鍵
4、二級索引訪問需要兩次索引查找,要回表,對于innodb,自適應hash索引能夠減少這樣的重復工作。
二級索引查找行,需要找到葉子節點所對應的主鍵值,再去聚簇索引對應的值
 使用了覆蓋索引 EXPLAIN的Extra 列會顯示Using index。
在Innodb中按主鍵順序插入行
 隨機插入缺點:
- 寫入的目標頁可能不在內存緩存區,那么插入記錄的時候需要先從磁盤讀取目標頁到內存中。這會導致大量的隨機IO.如果是順序插入,由于是插入到上一個記錄的后面,則大多數情況下(不需要開辟新頁的情況)磁盤頁是已經加載到內存了的。
- 因為寫入是亂序的,InnoDB可能需要不斷的的做頁分裂操作,以便為新的行分配空間。而頁分裂會導致移動大量的數據,而且一次分裂至少要修改三個頁而不是一個頁。
- 由于頻繁的分頁,頁面會變得稀疏并被不規則的填充,最后會導致數據碎片。
順序的主鍵什么時候會造成更壞的結果?
對于高并發工作負載,在InnoDB中按主鍵順序插入可能會造成明顯的爭用。主鍵的上屆會變成“熱點”。因為所有的插入都發生在這里,所以并發插入可能導致間隙鎖競爭。另一個熱點可能是AUTO_INCREMENT鎖機制;如果遇到這個問題,則可能需要考慮重新設計表或者應用,或者更改innodb_autoinc_lock_mode配置。如果你的服務器版本還不支持innodb_autoinc_lock_mode參數,可以升級到新版本的InnoDB,可能對這種場景會工作得更好。
假如索引覆蓋了where 條件中的字段,但不是整個查詢涉及的字段,還是會回表獲取數據行。
select * from products where actor='SEAN' and title like '%APOLLO%'可以使用延遲關聯解決:
SELECT* FROMproducts JOIN (SELECTproduct_idFROMproductsWHEREactor = 'SEAN'AND title LIKE '%APOLLO%' ) t1 ON t1.product_id = products.id使用索引掃描來做排序
 MySQL 有兩種方式可以生成有序的結果:通過排序操作; 或者按索引順序掃描注。 如果 EXPLAIN 出來的 type 列的值為 “index”, 則說明 MySQL 使用了索引掃描來做排序(不 要和 Extra 列的 “Using index” 搞混淆了)。
只有當索引的列順序和ORDER BY子句的順序完全一致, 井且所有列的排序方向(倒序或正序)都一樣時,MySQL才能夠使用索引來對結果做排序注。
 如果查詢需要關聯多張表,則只有當ORDE R BY 子句引用的字段全部為第一個表時,才能使用索引做排序。ORDE R BY 子句和查找型查詢的限制是一樣的:需要滿足索引的最左前綴的要求;否則,MySQL都需要執行排序操作,而無法利用索引排序。
壓縮索引
 MyISAM使用前綴壓縮來減少索引的大小。
 冗余和重復索引
 重復索引是沒有必要的。
 冗余索引可以滿足不同條件的查詢。
冗余索引和重復索引有一些不同。 如果創建了索引(A, B), 再創建索引 (A) 就是冗余索引,因為這只是前一個索引的前綴索引。 因此索引(A, B) 也可以當作索引 (A) 來使用(這種冗余只是對B-Tree索引來說的)。 但是如果再創建索引,(B, A), 則不是冗余索引,索引 (B)也不是,因為 B不是索引 (A, B) 的最左前綴列。 另外,其他不同類型的索引(例如哈希索引或者全文索引) 也不會是B-Tree索引的冗余索引,而無論覆蓋的索引列是什么。
索引和鎖
 InnoDB 在二級索引上使用共享鎖,主鍵索引使用排他鎖。
查詢性能優化
6.1 為什么查詢會慢
如果把查詢看做一個任務的話,它是由一系列子任務構成的。每個子任務執行都會消耗一定的時間。
 如果要優化查詢,實際上要優化其子任務,要么清除其中一些子任務,要么減少子任務的執行次數,要么讓任務運行得更快。
6.2 慢查詢基礎:優化數據訪問
- 確認應用程序是否在檢索大量超過需要的數據。這通常意味著訪問了太多的行或列。
- 確認MySQL服務器層是否在分析大量超過需要的數據行。
是否向數據庫請求了不必要的數據
- 查詢不需要的記錄
 一個常見的錯誤是常常會誤以為MySQL會只返回需要的數據,實際上MySQL卻是先返回全部結果集再進行計算。最簡單的辦法鎖加上LIMIt。
- 多表關聯時返回全部列
 查詢所有在電影Academy Dinosaur中出現的演員,千萬不要按下面的寫法編寫查詢。
- 總是取出全部列
 每次看到SELECT *的時候都需要要用懷疑的眼光審視,是不是真的需要返回全部的列?很可能不是必須的。取出全部的列,會讓優化器無法完成索引覆蓋掃描這類優化,還會為服務器帶來額外的I/O、內存和CPU消耗。
- 重復查詢相同的數據
 初次查詢時將數據緩存,在需要時在緩存中取出來。
MySQL是否在掃描額外的記錄
 對于MySQL, 最簡單的衡量查詢開銷的三個指標如下:
- 響應時間
- 掃描的行數
- 返回的行數
響應時間
 響應時間是兩個部分之和:服務時間和排隊時間。
 掃描的行數和返回的行數
 分析查詢時,查看該查詢掃描的行數是非常有幫助的。 這在一定程度上能夠說明該查詢找到需要的數據的效率高不高。
 理想情況下掃描的行數和返回的行數應該是相同的。
掃描行數與訪問類型
 explain的type的顯示了訪問類型,速度從慢到快,分別是:
 all(全表掃描)、index(索引掃描)、range(范圍掃描)、ref(范圍索引)、ref_eq(唯一性索引)、const(常數引用)。
6.3 重構查詢的方式
一個復雜查詢還是多個簡單查詢
 設計查詢的時候需要考慮是否需要將多個復雜查詢拆分為多個簡單查詢。
 切分查詢
 定時刪除數據時,一次刪除可能需要鎖定很多數據,可以拆分多次執行。
 ** 分解關聯查詢**
6.4 查詢執行的基礎
MYSQL執行查詢的過程:
 
 MySQL執行查詢的過程:
 (1)客戶端發送一條查詢給服務器
 (2)服務器先檢查查詢緩存,如果命中了緩存,則立即返回存儲在緩存中的結果,否則進入下一個階段。
 (3)服務器端進行SQL解析、預處理,再由優化器生成對應的執行計劃
 (4)將結果返回給客戶端。
查詢緩存
 在解析一個查詢語句之前,如果查詢緩存是打開的,那會優先檢測這個查詢是否命中緩存中的數據。
 查詢優化處理
 首先,mysql會將sql語句解析成語法樹,然后驗證語法是否錯誤。
 語法樹合法后會被優化器轉成執行計劃。
 MySQL如何執行關聯查詢
 mysql對于任何關聯都執行嵌套循環關聯操作,即先在一個表中循環取出單條數據,然后再嵌套循環到下一個表中尋找匹配的行,依次進行。
 
6.7 優化特定類型的查詢
優化count()查詢
 count()可以統計某個列的數值,也可以統計行數。
 在統計列值是要求列是非空的(不統計NULL)。count(*) 統計行數。
優化limit分頁
 偏移量非常大的時候,例如LIMIT 10000,20可能需要查詢100020條結果,然后返回20條。
 可以使用書簽記錄上一次查詢的位置,那么下次查詢就會從書簽位置開始掃描。
復制
1.2 復制如何工作
MySQL復制數據的三個步驟:
1.在主庫上把數據更改記錄到二進制日志中(二進制日志事件);
 2.備庫將主庫上的日志復制到自己的中繼日志中;
 3.備庫讀取中繼日志中的事件,將其重放到備庫數據之上。
第一步就是在主庫上記錄二進制日志。在每個事務更新數據完成之前,主庫將數據更新事件記錄到二進制日志記錄中。MySQL會按照事務的提交順序,而非每條語句的執行順序記錄二進制日志。在記錄二進制日志完成后,主庫會告訴存儲引擎提交事務。
 下一步備庫主庫的二進制日志復制到其本地中繼日志。首先,備庫啟動一個工作線程(I/O線程)。I/O線程跟主庫上建立一個普通的連接,然后再主庫啟動一個二進制轉儲線程讀取主庫的二進制日志中的事件,如果已經跟上主庫,它會睡眠并等待主庫產生新的事件。I/O線程將這些事件寫入中繼日志。
 SQL slave thread(SQL從線程)處理該過程的最后一步。SQL線程從中繼日志讀取事件,并重放其中的事件而更新slave的數據,使其與主庫中的數據一致。只要該線程與I/O線程保持一致,中繼日志通常會位于OS的緩存中,所以中繼日志的開銷很小。
總結
以上是生活随笔為你收集整理的《高性能MYSQL》的全部內容,希望文章能夠幫你解決所遇到的問題。
 
                            
                        - 上一篇: 为什么张扬的人别人很讨厌_为什么每个人总
- 下一篇: spring mvc学习(33):原生a
