MySQL深入學習筆記
本文是閱讀《MySQL技術內幕-InnoDB儲存引擎》第二版,《MySQL DBA修煉之道》,以及《MySQL王者晉級之路》所記錄的筆記.
InnoDB體系架構
執行緒
- 主執行緒(Master Thread):將緩衝池中的資料非同步重新整理到磁碟.
- IO執行緒(IO Thread):處理非同步IO的請求以及回撥.
- 清理執行緒(Purge Thread):回收已經使用但並未分配的Undo頁.
- 頁清理執行緒(Page Cleaner Thread):負責髒頁的重新整理操作
記憶體
LRU列表用來管理已經讀取的頁,如果LRU列表裡讀取的頁被修改,那麼這個時候記憶體和磁碟的資料不一致就會產生髒頁
髒頁由FlushList進行管理.FreeList管理空閒頁的列表,如果後續FreeList列表空間不足,從LRU列表進行淘汰再進行分配空間.
髒頁通過CheckPoint機制重新整理到磁碟.
- 緩衝池:緩衝池可以將索引頁,資料頁,undo頁,插入快取,自適應雜湊索引,InnoDB儲存的鎖資訊,資料字典等資訊放入到記憶體,加速查詢以及修改.緩衝池不可能完全承載檔案資料,因此緩衝池當中的資料需要根據改進的LRU演算法進行淘汰.
- 重做日誌緩衝:儲存重做日誌
- 額外的記憶體池:儲存內部資料結構
Check Point 機制
CheckPoint由主執行緒按照1s或者10s重新整理一定比例的髒頁到磁碟.
CheckPoint目的是為了:縮短資料庫恢復時間,釋放緩衝池空間,重做日誌不可用時重新整理磁碟.
InnoDB核心設計
以下為InnoDB特性,為資料庫可靠性,高效能提供了保證.
InsertBuffer
為了解決非聚集索引的離散插入,刪除或更新的問題的問題,InsertBuffer誕生.
注意官方後續將InsertBuffer改名為ChangeBuffer,顯然ChangeBuffer更合適一些.
眾所周知,非聚集索引插入會引起隨機IO,因為非聚集索引和聚集索引的順序可能是不一致的,當一條資料插入時是資料頁是順序的但是對於非聚集索引就可能不是了,因此需要將插入操作合併,以減少隨機IO.
前提條件:
索引是輔助索引.
索引是非唯一索引(因為InnoDB不能保證插入新值的唯一性).
InsertBuffer是存在於共享表空間中(ibdata1),本質是一個B+樹,當一輔助索引插入到非聚集索引頁當中,先判斷這個頁在緩衝池如果在直接插入,若不存在構造一個search key插入到B+樹頁子節點,search key包含space id(表空間),offset(頁中偏移量).
如下情況會進行Merge Insert Buffer :
- 輔助索引被讀取到緩衝池,InsertBuffer中的該輔助索引頁的相關記錄會插入輔助索引頁
- MasterThread
- 輔助索引頁無可用空間
這時候隨機插入變為順序寫入,效能極大提升.
DoubleWrite
當一種情況寫入16KB的頁之寫入一半發生故障後,該頁被損壞不能直接從重做日誌恢復.
因此兩次寫保證InnoDB寫入檔案的可靠性.
思路:將頁複製一個副本,防止寫入失效時頁損壞.進行恢復時,先通過的頁的副本還原該頁,然後進行重做日誌恢復.
DoubleWriteBuffer提供2M的共享表空間,儲存寫入頁的資訊.
先將髒頁寫入到DoubleWriteBuffer記憶體中(memcpy),然後寫入共享表空間中(此時為順序寫,需要進行fsync),然後在將資料頁寫入相應的表空間檔案.
AdaptiveHash
InnoDB會根據資料的訪問頻率和模式(聯合索引 (a, b),where a=X 與 where a=X and b=X是不同模式)來自動建立Hash索引.
但是有一些侷限性就是隻適合 =
條件.
AIO與重新整理鄰近頁
通過核心非同步IO可以提升磁碟效率.
寫入髒頁時會判斷臨近的髒頁如果存在會一起同過AIO執行.
日誌
查詢日誌
查詢日誌記錄對應主機查詢的所有請求記錄日誌,這個一般不被開啟.
set global general_log = off
慢查詢日誌
通過配置long_query_time
慢查詢時間,log_slow_queries
開啟慢查詢.
# Time: 180927 0:17:41
# [email protected]: schema_sync2[schema_sync2] @ [10.12.26.10] Id: 24454
# Query_time: 1.472336 Lock_time: 0.000084 Rows_sent: 679 Rows_examined: 679 Logical_reads:1000 Phiscal_reads:20
select * from orders;
二進位制日誌
二進位制儲存格式有三種:
1.statement:按照語句儲存,可能會導致主從資料不一致
2.row:按記錄儲存,保證資料一致性,二進位制日誌比較安全主要是由於包含了變化之前的資料資訊
3.mixed:混合模式相容效率與儲存一致性的一種方式,當SQL中包含有SQL_FOUND_ROWS(),UUID()等函式會轉為row模式
### UPDATE `tjy`.`idx_tjy_orders_apply_order_id`
### WHERE
### @1=1527782400004279 /* LONGINT meta=0 nullable=0 is_null=0 */
### @2=480939048 /* LONGINT meta=0 nullable=0 is_null=0 */
### @3=0 /* LONGINT meta=0 nullable=0 is_null=0 */
### SET
### @1=1527782400004279 /* LONGINT meta=0 nullable=0 is_null=0 */
### @2=480939048 /* LONGINT meta=0 nullable=0 is_null=0 */
### @3=12701324 /* LONGINT meta=0 nullable=0 is_null=0 */
重做日誌
重做日誌檔案儲存InnoDB儲存引擎的事務重做日誌.
配合redo log buffer進行寫入,先寫入快取然後寫入日誌檔案.
為了高效恢復資料庫,節省磁碟空間,重做日誌採用了迴圈寫檔案方式.
保證事務的ACID中永續性,innodb_flush_log_at_trx_commit
需要設定為1,redo log buffer實時同步到磁碟.當然也可以設定為0,2,代表著不同的意思,根據實際生產環境需要設定.
undo日誌
undo日誌儲存的是事務回滾的邏輯日誌.
與redo日誌不同的是undo日誌存放在資料庫中一個特殊段內,存放於共享表空間內.
表
索引組織表:表都是按照主鍵順序進行存放.如果表沒有定義主鍵,判斷是否有非空的唯一索引,如果沒有自動建立一個6位元組大小的指標.
設定inno_db_file_per_table
後儲存空間每個表設定獨立的表空間.
表名.idb:儲存表資料,索引和插入緩衝點陣圖等資訊.
表名.frm:表結構關係.
段
表空間由各個段組成,常見的段有資料段,索引段,回滾段等.資料段為B+樹的葉子節點,索引段為B+樹非葉子節點.
區
區是由連續的頁組成,每個區的大小固定為1MB.
頁
預設大小為16KB,頁型別分為資料頁,undo頁,事務資料頁等.
行
MySQL是面向行儲存的,每個頁最大儲存16KB/2-200行 .
行記錄格式
- Compact格式:頁中存放的資料越多效能越高,列中NULL值不佔用儲存空間.
- Redundant格式:老版本格式,CHAR型別的NULL值需要佔用儲存空間,而VARCHAR型別不需要佔用儲存空間.
以上格式中行溢位:針對行溢位資料,如果一個頁無法儲存兩個行記錄,溢位的資料會放置到Uncompressed BLOB頁,資料頁只會儲存前768位元組,並存儲一個指標指向BLOB頁中的資料.
之後InnoDB進行優化,之前的Compact和Redundant格式稱為Antelope.Compressed和Dynamic稱為Barracuda.
Barracuda格式採取完全行溢位格式,行記錄只儲存20位元組的指標,真正的資料存放在OffPage.
其中Compressed格式:會對行資料進行zlib演算法的壓縮.
資料頁
除此之外資料頁最外層包含了頁頭資訊(FileHeader),校驗頁完整性寫入磁碟(FileTrailer)
每個資料頁包括Infimum 和Supremum ,這兩個用於標記該資料頁中行記錄邊界.
實際儲存行記錄的內容(UserRecord)與記錄刪除後空間會被加入到空閒連結串列(FreeSpace)內.
PageDirectory用於存放記錄相對位置,將頁載入到記憶體然後,通過PageDirectory二分查詢對應記錄.
分割槽表
支援四種方式進行分割槽:
1.HASH:對分割槽列進行hash,通過對指定的分割槽數求餘數放入到對應的分割槽
2.Range:根據建表語句對應的範圍建立分割槽
3.List:根據指定列的值,放入到不同分割槽
4.Key:和hash分割槽類似,不同之處是使用使用者定義函式進行分割槽
5.Column:形式是以Range,List類似但是以上四種不支援非整型,但是Column支援字串分割槽,DATE,DATETIME.
子分割槽
在Range,List分割槽的基礎上再進行Hash,key分割槽.
每個子分割槽數量必須相同.
分割槽表的侷限性
在OLTP中的使用限制比較多,例如使用非分割槽欄位查詢,會引起IO次數增加,從而影響效能.
分割槽功能不夠強大,這種方案比較適合OLAP業務比如:銷售記錄的表,涉及到日期或
還有一點就是沒有索引表的概念,當涉及到非分割槽列的查詢可以避免全表掃描.
索引
聚集索引:按照主鍵構造B+樹,葉子節點存放整張表的行記錄.
輔助索引:葉子節點包含主鍵資訊(聚集索引建),以及索引建值資訊.
聯合索引:多了進行索引,按照索引定義順序進行存放.因此就會有一些問題:a,b,c建立的索引,當條件包含a=X
,a=X and b=X
, a=X and b=X c=X
時會使用索引.因為索引的排序是按照先排a再排b再排c.因此單獨使用b,或者b,c都不是有序的.
覆蓋索引:覆蓋索引從輔助索引就可以查到記錄.
MRR優化:索引都是按照索引的大小順序進行排序.所以當通過輔助索引查到主鍵的值可能是離散的,離散的值意為著效能很差.MRR(Multi-Range Read)會按照主鍵順序進行排序後再查詢,減少資料隨機訪問.
ICP優化:除了MRR優化外還有ICP(INDEX CONDITION PUSHDOWN),取索引的同時會對where條件的進行過濾.
自適應雜湊索引經雜湊函式對映到一個雜湊表中.對於=
的條件效能提升很大.關於自適應雜湊索引的使用情況可以通過SHOW ENGINE INNODB STATUS
進行檢視.
InnoDB支援全文索引,但是侷限性很嚴重,只支援Latin文字.
使用建議:
1.where條件不要對索引使用表示式,如left(A, 1) = "c"
2.主鍵最好自增整型
3.索引基數儘量趨近於1
4.使用更短索引或者字首索引
5.索引列適當,如果太多降低更新能
6.唯一索引效能更好
7.覆蓋索引可以減少查詢IO
8.利用索引排序
複製
MySQL複製實現:
1.主庫將資料變更資訊寫入binlog
2.從庫IO執行緒請求主庫將變更資訊從binlog寫入relay log中繼日誌
3.從庫SQL執行緒把中繼日誌應用到當前資料庫
MySQL支援級聯複製.
半同步複製
半同步主庫在傳送一個事務提交會阻塞到至少一個半同步從庫已經"接收到事務事件"為止,否則會發生超時.
半同步從庫在寫入事件到中繼日誌,重新整理到磁碟後才確認接收到事務事件.
半同步複製區別與"全同步複製"的是,當至少一個從庫收到事務事件時半同步複製並不等待從庫的提交.
如果沒有從庫接收到事務事件會發生事務超時,這時候會轉換到非同步複製模式,如果一個半同步從庫追趕上從庫,將會切到半同步複製.
表設計
三正規化原則:
第一:每一列都是不可分割的資料項
第二:所有資料都要和該資料表的主鍵有完全依賴關係
第三:非建屬性之間無關
三正規化原則使用有些侷限性,實際可能更多使用的是反正規化設計原則.
在設計時儘量用ER模型圖:
矩形框:表示實體,在框中記入實體名
菱形框:表示聯絡,在框中記入聯絡名
橢圓形框:表示實體或聯絡的屬性,將屬性名記入框中。對於主屬性名,則在其名稱下劃一下劃線
連線:實體與屬性之間;實體與聯絡之間;聯絡與屬性之間用直線相連,並在直線上標註聯絡的型別。(對於一對一聯絡,要在兩個實體連線方向各寫1; 對於一對多聯絡,要在一的一方寫1,多的一方寫N;對於多對多關係,則要在兩個實體連線方向各寫N,M。)
使用explian
explain在實際的開發當中有著重要的作用,可以分析當前SQL的效能.
欄位 | 取值 | 解釋 |
---|---|---|
id | - | 表名SQL語句執行的順序,如果id值相同那麼執行順序從上到下,如果不同那麼值越大越先被執行 |
select type | SIMPLE | 不包含UNION以及子查詢 |
PRIMARY | 包含複雜查詢,當前最外層查詢 | |
DERIVED | 當前查詢為衍生查詢(from語句之後的子查詢) | |
SUBQUERY | 當前SQL中SELECT或WHERE包含子查詢 | |
UNION | SELECT出現在UNION之後 | |
UNION RESULT | 從UNION的表中取結果的SELECT | |
table | SQL語句查詢的表 | |
type | ALL | 指明當前的SQL全表掃描 |
index | 使用索引進行全表掃描 | |
range | 索引範圍掃描 | |
ref | 使用索引等值查詢 | |
eq_ref | 使用唯一索引查詢 | |
system | 系統優化後的查詢,轉化為常量 | |
const | system特例,查詢中只有一行 | |
null | 效能最高的查詢 | |
possible_keys | - | 可能使用的索引 |
keys | - | 真正使用的索引 |
key_len | - | 索引的長度 |
ref | - | 連線查詢的欄位 |
rows | - | 掃描的行數 |
extra | Using where; | 使用條件過濾 |
Using filesort | SQL中沒有使用索引欄位而導致使用檔案排序,當SQL結果集較小會使用記憶體進行檔案排序,如果結果集太大那麼,會使用臨時檔案進行排序. | |
Using Index | 使用索引 | |
Using Index Condition | 使用Index Condition Pushdown,將資料過濾放置到儲存引擎層,提升查詢效能 | |
Using Temporary | Union,使用衍生查詢或者是order by與group by欄位不一致等導致使用臨時表的情況,還有一些其他可能出現情況請點選,實際要避免這類情況的發生. |
配置引數
我們完全可以透過InnoDB的配置引數,來理解InnoDB設計的精髓.
這些配置其實是InnoDB設計的體現.
引數 | 說明 |
---|---|
innodb_buffer_pool_size | buffer_pool實際大小 |
innodb_buffer_pool_instances | buffer_pool個數 |
query_cache_size | 儘可能的小些,推薦設定為0 |
max_connections | 最大的連線數 |
innodb_log_file_size | redo日誌檔案大小, 不能設定太小會導致不停切換檔案async checkpoint,設定太大會導致資料庫恢復時間太長 |
innodb_adaptive_hash_index | 是否開啟自適應hash索引,預設開啟加速等值查詢速度 |
innodb_change_buffering | 預設值是all,一般情況不需要改變,change buffer 請見InsertBuffer |
innodb_read_ahead_threshold | 是否開啟緩衝池線性預讀,預設開啟ON,在緩衝池當中的連續訪問會非同步從磁碟預讀多個頁到緩衝池 |
innodb_thread_concurrency | 最大併發執行緒數量,一般不推薦修改,除非上下文切換是瓶頸 |
innodb_read_io_threads,innodb_write_io_threads | 資料頁read/write IO執行緒數,根據部署環境可適當增大 |
innodb_io_capacity | 設定InnoDB整個IO吞吐量,預設值200,涉及到IO相關的任務(例如:重新整理髒頁,寫入插入快取),SSD磁碟或者RAID磁碟都可以講該值提高 |
innodb_max_dirty_pages_pct | 超出髒頁重新整理的百分比,如果超出就重新整理髒頁 |
innodb_adaptive_flushing | 根據redo log 產生的速度以及髒頁的數量來控制重新整理髒頁的數量,預設開啟 |
innodb_rollback_segments | 回滾段個數的配置,InnoDB支援128回滾段,其中32保留用於臨時表的事務.預設最大支援96K左右併發,如果設計臨時表事務最大支援32K併發. |
innodb_purge_threads | Purge thread的個數,如果DML操作只執行在少數表或者單個表,那麼可以設定的低一些防止與其它執行緒形成競爭.如果是很多個表的DML操作比較多可以適當增大 |
innodb_old_blocks_pct | 0-95, 預設將讀取的新頁插入到LRU列表末端的37%的位置,防止一些掃描表或索引的操作將LRU中的熱資料沖刷掉. |
innodb_old_blocks_time | 毫秒數,預設插入innodb_old_blocks_pct的隔1000毫秒後再插入LRU列表熱端. |
補充
update是原子操作嗎
實際上來說Update是原子操作.
1.如果開啟了autocomit模式,那麼所有的語句都在事務當中,每個語句組成一個單一的事務,當語句執行成功後事務自動進行提交.官方文件
2.UPDATE,DELETE,鎖定讀 (select for update)語句一般會根據具體條件使用的索引加鎖,對於非唯一索引會鎖住掃描的範圍(next-key),而對於唯一索引搜尋偉一行會使用record lock.如果在事務當中執行,事務的隔離級別也會影響鎖的範圍官方文件
所以類似:UPDATE sku SET stock = stock - 1 where id = 10
這樣的語句是安全可靠的.
CHAR型別和VARCHAR型別
在可變長度字符集如UTF8下,CHAR型別和VARCHAR型別實際底層儲存行沒有區別.
MySQL不支援物化檢視
MySQL支援檢視,但是不存在實際儲存.
物化檢視可以通過觸發器簡單實現.
RAID
Redundant Array of Independent Array:獨立磁碟冗餘陣列:將幾個磁碟組合成一個邏輯的扇區.
增加資料容錯性,增加磁碟容量.
可以極大提升效能,提供並行IO能力.
- RAID0:將多個磁碟組成一個邏輯磁碟,並行IO,查詢寫入效能高.
- RAID1:將N個磁碟組成互為映象,容錯性高.
- RAID10和RAID01:結合了RAID0和RAID1的特性,效能和容錯性高.
排序和索引
當我們使用主鍵查詢並用索引列排序肯能會導致一個問題,就是使用檔案排序:
檔案排序是一個非常高昂的操作,需要儘量避免.
mysql> explain select id,status from orders where id > 1000 order by create_time;
+----+-------------+--------+-------+---------------+---------+---------+------+----------+-----------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+--------+-------+---------------+---------+---------+------+----------+-----------------------------+
| 1 | SIMPLE | orders | range | PRIMARY | PRIMARY | 8 | NULL | 49403851 | Using where; Using filesort |
+----+-------------+--------+-------+---------------+---------+---------+------+----------+-----------------------------+
上述例子中create_time,與id都有是索引列,但是MySQL確採用了檔案排序.這是新手通常會犯的一個錯誤.
儘量使用以下兩點避免該問題:
- 索引列和order by列儘量一致
- 對連線查詢,order by 列要用首張表的列
索引的Cardinality
Cardinality用於指示索引的唯一特性,與索引欄位效能有直接關係.
由於show index from
table 中Cardinality不能準確更新當前索引的基數,其更新策略 :
1.表中1/16資料變化
2.20億資料被修改.
可以使用count(DISTINC index_col)/count(*)
來實時檢測當前唯一index列的個數.