mysql技術內幕-innodb存儲引擎讀書筆記(上)


第一章、mysql體系結構和存儲引擎

1.1、數據庫和實例的區別
數據庫:物理操作系統或其他形式文件類型的集合。在mysql下數據庫文件可以是frm,myd,myi,ibd結尾的文件。
數據庫實例:由數據庫後臺進程/線程以及一個共享內存區組成。
mysql數據庫是但進程多線程的程序。

1.2、mysql的體系結構
mysql由連接池組件、管理服務和工具組件、sql接口組建、查詢分析器組件、優化器組件、緩存組件、插件是存儲引擎、物理文件。
示意圖見書:P4.

1.3、mysql存儲引擎
1.3.1、innodb存儲引擎,特點支持外鍵、行鎖、非鎖定讀(默認情況下讀取不會產生鎖)、mysql-4.1開始支持每個innodb引擎的表單獨放到一個表空間裏。innodb通過使用MVCC來獲取高併發性,並且實現sql標準的4種隔離級別,同時使用一種被稱成next-key locking的策略來避免換讀(phantom)現象。除此之外innodb引擎還提供了插入緩存(insert buffer)、二次寫(double write)、自適應哈西索引(adaptive hash index)、預讀(read ahead)等高性能技術。
1.3.2、myisam存儲引擎,myisam特點是不支持事物,適合olap應用,myisam表由MYD和MYI組成。mysql-5.0版本之前,myisam默認支持的表大小爲4G,從mysql-5.0以後,myisam默認支持256T的表單數據。myisam只緩存索引數據,mysql-5.1.23版本之前無論32、64位操作系統環境下,緩存索引的緩衝區最大隻能4G,在之後的版本中,64位系統可以支持大於4G的索引緩衝區。
1.3.3、NDB存儲引擎,2003年mysql從索愛公司收購的NDB引擎,NDB的特點是數據放在內存中,mysql-5.1版本開始可以將非索引數據放到磁盤上。NDB之前的缺陷是join查詢是mysql數據庫層完成的,而不是存儲引擎完成的,複雜的join查詢需要巨大的網絡開銷,速度很慢。當前mysql cluster7.2版本中已經解決此問題,join查詢效率提高了70倍。
1.3.4、memeory存儲引擎,將數據放到內存中,默認使用hash索引,不支持text和blob類型,varchara是按照char的方式來存儲的。mysql數據庫使用memory存儲引擎作爲臨時表還存儲中間結果集(intermediate result),如果中間集結果大於memorg表的容量設置,又或者中間結果集包含text和blog列類型字段,則mysql會把他們轉換到myisam存儲引擎表而放到磁盤上,會對查詢產生性能影響。
1.3.5、archive存儲引擎,壓縮能力較強,主要用於歸檔存儲。
1.3.6、federated存儲引擎,不存儲數據,他指向一臺遠程mysql數據庫上的表。
1.3.7、maria存儲引擎,myisam的後續版本,支持緩存數據和索引,行鎖設計,支持mvcc,支持事務和非事務安全的選項,以及更好的BLOG字符類型的處理性能。
1.3.8、其他存儲引擎,sphinx用於全文索引,infobright用於數據倉庫

1.4、各引擎之間的對比
可以通過 show engines查看mysql對存儲引擎的支持情況。

1.5、連接mysql

第二章、innodb存儲引擎

2.1、第一章已經大概介紹innodb的特點
2.2、innodb引擎架構
維護所有進程/線程需要訪問的多個內部數據結構
緩存磁盤上的數據,方便快速的讀取,並且在對磁盤文件的數據進行修改之前在這裏緩存
重做日誌緩存
……….
後臺線程的主要作用是負責刷新內存池中的數據,保證緩衝池中的內存緩存是最近的數據,此外、將已經修改的數據文件刷新到磁盤文件,同時保證數據庫發生異常情況下innodb能恢復到正常運行狀態。
2.2.1、後臺線程
innodb存儲引擎後臺有7個線程,—–4個IO線程,1個master thread,一個lock監控線程,一個錯誤監控線程。(當前5.5版本,默認IO線程是18個,8個讀,8個寫,一個insert buffer thread、一個log thread,加上master線程、lock監控線程、錯誤監控線程一共是21個)。可以通過 show variables like ‘innodb_%io_threads’\G和show engine innodb status\G來查看相關信息。
2.2.2、內存
innodb存儲引擎內存由以下三個部分組成:緩衝池(buffer pool),重做日誌緩存(redo log buffer),額外的內存池(additional memory pool)。分別使用innodb_buffer_pool_size和innodb_log_buffer_size、innodb_additional_mem_pool_size的大小決定。可以使用 show engine innodb status來查看innodb_buffer_pool的事情情況。在BUFFER POOL AND MEMORY裏可以看到存儲引擎緩存池的使用情況,buffer pool size表明一共有多少緩衝幀(buffer frame),每個buffer frame爲16K.buffer pool size表明一共有多少緩衝幀、free buffers表示當前空閒的緩衝幀、database pages表示已經使用的緩存幀、modified db pages表示髒頁的數量。
innodb_buffer_pool_size:具體看,緩衝池中的數據庫類型有:索引頁、數據庫頁、undo頁、插入緩存頁(insert buffer)、自適應hash(adaptive hash index)、innodb存儲的鎖信息(lock info)、數據字典信息(data dictionary)
示意圖見書P24.
注意:在32位windows下innodb_buffer_pool_size可以通過開啓AWE功能突破內存限制,但是會自動禁用自適應hash(adaptive hash index).
innodb_log_buffer_size:一般情況下innodb會每秒刷新log buffer到硬盤,因此保證每秒產生的事務樑在這個緩存大小之內就可以了。
innodb_additional_mem_pool_size:當你的innodb_buffer_pool_size很大的時候,這個值也需要擴大。
2.3、master thread
2.3.1、master thread源碼分析

 void masteor_thread(){
 loop: //主循環,間隔10s
 for(int i=0;i thread_sleep(1) //sleep 1 s
 do log buffer flush to disk //每秒都要刷新日誌緩存到硬盤
 if(last_one_second_iosinnodb_max_dirty_pages_pct){//如果緩存中的髒頁比例大於配置中的innodb_max_dirty_pages_pct就刷新100個髒頁到硬盤
 do buffer pool flush 100 dirty page
 }
 if(no user activity){ //如果當前沒有活躍用戶或者數據庫關閉時,就跳入background loop
 goto backgroud loop
 }
 sleep 1 second if necessary
 }
 //每10秒執行的操作
 if(last_ten_second_ios70%){ //如果緩存中髒頁比例大於70%,就刷新100個髒頁到硬盤,否則只刷新10個
 do buffer pool flush 100 dirty page
 }else{
 buffer pool flush 10 dirty page
 }
 do fuzzy checkpoint //產生一個檢查點
 goto loop:
 background loop: //backupgroud循環
 do full purge //總是刪除bufferpool中無用的undo頁
 do merge 20 insert buffer //總是合併20哥插入緩存
 if not idle://如果不空閒,就跳回主循環,如果空閒就跳入flush loop
 goto loop:
 else
 goto flush loop
 flush loop:
 do buffer pool flush 100 dirty page //總是刷新100個髒頁到硬盤,直到緩存中的髒頁比例小於innodb_max_dirty_pages_pct
 if(buf_get_modified_ratio_pct>innodb_max_dirty_pages_pct){
 goto flush loop
 }
 goto suspend loop //完成刷新髒頁的任務後,跳入suspend loop
 suspend loop:
 suspend_thread() //將master線程掛起,等待事件激活
 waiting event
 goto loop:
 }

 

可以通過show engine innodb status\G命令查看BACKGROUND THREAD段關於mster thread的信息。這裏主循環執行了2857554次,每秒的thread執行了2857350次,每十秒的tread執行了285310次,6239次background thread和6238次flush thread。這裏的每秒和每十秒的比例差不多,證明服務器壓力不是很大。

-----------------
BACKGROUND THREAD
-----------------
srv_master_thread loops: 2857554 1_second, 2857350 sleeps, 285310 10_second, 6239 background, 6238 flush
srv_master_thread log flush and writes: 3040886

 

2.3.2、master thread的潛在問題
1、由於硬件的發展,現在的硬件性能已經提高了很多,如果innodb每秒最大刷新100個髒頁,那麼效率會很低,爲了解決這個問題,innodb plugin提供了一個參數innodb_io_capacity,用來表示磁盤IO的吞吐量,默認值是200,規則如下:
在合併插入緩存時,合併插入緩存的數量爲innodb_io_capacity的5%。
在從緩衝區刷新髒頁時,啥新髒頁的數量爲innodb_io_capacity。
如果你使用的是ssd或者raid10,你可以調高這個參數,知道符合你的硬件。
2、關於innodb_max_dirty_pages_pct值的爭議,如果值過大,內存也很大或者服務器壓力很大,那麼效率很降低,如果設置的值過小,那麼硬盤的壓力會增加,建議是在75-80.並且innodb plugin引進了innodb_adaptive_flushng(自適應的刷新),該值影響每秒刷新髒頁的數量。他會通過innodb_flush_get_desired_flush_rate的函數判斷需要刷新髒頁最合適的量,這個函數是通過重做日誌產生的速度來判斷的。
通過上述改進,innodb master thread的爲代碼如下:

void masteor_thread(){
loop: //主循環,間隔10s
for(int i=0;i thread_sleep(1) //sleep 1 s
do log buffer flush to disk //每秒都要刷新日誌緩存到硬盤
if(last_one_second_iosinnodb_max_dirty_pages_pct){//如果緩存中的髒頁比例大於配置中的innodb_max_dirty_pages_pct就刷新innodb_io_capacity個髒頁到硬盤
do buffer pool flush 100% innodb_io_capacity dirty page
}

if(no user activity){ //如果當前沒有活躍用戶或者數據庫關閉時,就跳入background loop
goto backgroud loop
}

sleep 1 second if necessary
}
//每10秒執行的操作
if(last_ten_second_ios< innodb_io_capacity){ //如果最後10s內IO小於innodb_io_capacity次,那麼就刷新innodb_io_capacity個髒頁到硬盤 do buffer pool flush 100% * innodb_io_capacity dirty page } do merge at most 5 insert buffer //總是合併最多5個插入緩存 do log buffer flush to disk //總是將日誌緩存刷新到磁盤 do full purge //總是刪除buffer_pool中無用的undo頁,一次最多20個 if(buf_get_modified_ratio_pct>70%){ //如果緩存中髒頁比例大於70%,就刷新innodb_io_capacity個髒頁到硬盤,否則只刷新10%*innodb_io_capacity個
do buffer pool flush 100% * innodb_io_capacity dirty page
}else{
buffer pool flush 10% * innodb_io_capacity dirty page
}
do fuzzy checkpoint //產生一個檢查點
goto loop:
background loop: //backupgroud循環
do full purge //總是刪除bufferpool中無用的undo頁
do merge 100% * innodb_io_capacity insert buffer //總是合併innodb_io_capacity個插入緩存
if not idle://如果不空閒,就跳回主循環,如果空閒就跳入flush loop
goto loop:
else
goto flush loop

flush loop:
do buffer pool flush 100% * innodb_io_capacity dirty page //總是刷新innodb_io_capacity個髒頁到硬盤,直到緩存中的髒頁比例小於innodb_max_dirty_pages_pct
if(buf_get_modified_ratio_pct>innodb_max_dirty_pages_pct){
goto flush loop
}
goto suspend loop //完成刷新髒頁的任務後,跳入suspend loop

suspend loop:
suspend_thread() //將master線程掛起,等待事件激活
waiting event
goto loop:
}

 

2.4、關鍵特性,爲innodb提高性能的技術
2.4.1、插入緩存
當一個表有非聚集索引時,對於非聚集索引的葉子節點的插入不是順序的,這時候需要離散的訪問非聚集索引頁,性能就在這裏降低了,這是由於b+樹的原理導致的。插入緩存就是用來解決這個問題的。
對於非聚集索引的插入和更新操作,不是每一次都直接插入索引頁,而是先判斷插入的非聚集索引頁是否在緩存中,如果在就直接插入,如果不在就放入到一個插入緩衝區中,好似欺騙數據庫這個非聚集索引已經插入到葉子節點了。然後再以一定的頻率插入緩存和非聚集索引頁字節點的合併操作。
插入緩存的使用需要滿足以下兩個條件(也就是非唯一的輔助索引):
索引是輔助索引
索引不是唯一的
可以通過show engine innodb status\G查看INSERT BUFFER AND ADAPTIVE HASH INDEX段的信息來了解插入緩存的使用情況。

-------------------------------------
INSERT BUFFER AND ADAPTIVE HASH INDEX
-------------------------------------
Ibuf: size 1, free list len 30, seg size 32, 8069 merges
merged operations:
insert 6063, delete mark 2347, delete 73
discarded operations:
insert 0, delete mark 0, delete 0
Hash table size 8850419, node heap has 1562 buffer(s)
34.71 hash searches/s, 127.48 non-hash searches/s
---
LOG
---
Log sequence number 94417057034
Log flushed up to 94417055332
Last checkpoint at 94415342503
Max checkpoint age 650641675
Checkpoint age target 630309123
Modified age 1714531
Checkpoint age 1714531
0 pending log writes, 0 pending chkp writes
3724702 log i/o's done, 1.39 log i/o's/second

 

seg size顯示了當前插入緩衝的大小爲32*16K,size代表使用了的插入緩衝,free list len代表空閒的插入緩衝。等待完善標記

2.4.2、兩次寫
兩次寫給innodb帶來的是可靠性,主要用來解決部分寫失敗(partial page write)。doublewrite有兩部分組成,一部分是內存中的doublewrite buffer,大小爲2M,另外一部分就是物理磁盤上的共享表空間中聯繫的128個頁,即兩個區,大小同樣爲2M。當緩衝池的張也刷新時,並不直接寫硬盤,而是回通過memcpy函數將髒頁先拷貝到內存中的doublewrite buffer,之後通過doublewrite buffer再分兩次寫,每次寫入1M到共享表空間的物理磁盤上,然後馬上調用fsync函數,同步磁盤。以下命令可以查看doublewrite的使用情況。

mysql> show global status like 'innodb_dblwr%';
+----------------------------+----------+
| Variable_name | Value |
+----------------------------+----------+
| Innodb_dblwr_pages_written | 60511554 |
| Innodb_dblwr_writes | 828734 |
+----------------------------+----------+

這臺DB doublewrite一共寫了60511554個頁,但實際寫入次數爲828734,60511554/828734=73。說明這臺DB的壓力一般。slave上可以通過設置skip_innodb_doublewrite參數關閉兩次寫功能來提高性能,但是master上一定要開啓此功能,保證數據安全。

2.4.3、自適應哈西索引
由於innodb不支持hash索引,但是在某些情況下hash索引的效率很高,於是出現了 adaptive hash index功能,innodb存儲引擎會監控對錶上索引的查找,如果觀察到建立hash索引可以提高性能的時候,則自動建立hash索引。可以通過show engine innodb status\G來查看自適應哈西索引的使用情況。可以使用innodb_adaptive_hash_index來禁用和啓用hash索引,默認開啓。

2.5、啓動、關閉、恢復
在關閉數據庫時,innodb_fast_shutdown影響innodb存儲引擎的行爲。該參數有0、1、2三個參數。0代表mysql關閉時,innodb需要完成所有的full purge和merge insert buffer操作。1是默認值,表示不需要完成上述的full purge和merge insert buffer操作,但是緩衝池的一些數據髒頁還是回刷新到磁盤。2表示不完成full purge和merge insert buffer操作,也不將緩衝池中的髒頁回寫磁盤,而是將日誌都寫入日誌文件。
innodb_force_recovery影響整個innodb存儲引擎的恢復狀況,該值默認爲0,表示當需要恢復時,需要執行所有的恢復操作,當不能進行有效恢復時,如數據頁發生了corruption,mysql數據庫可能宕機,並把錯誤寫入錯誤日誌中。
innodb_force_recovery還可以設置1-6這個幾個值。大的數字包含小數字的影響。
1(SRV_FORCE_IGNORE_CORRUPT):忽略檢查到corrupt頁。
2(SRV_FORCE_NO_BACKGROUND):阻止主線程的運行,如主線程需要執行full purge操作,會導致crash。
3(SRV_FORCE_NO_TRX_UNDO)不知行事務回滾操作
4(SRV_FORCE_NO_IBUF_MERGE)不執行插入緩衝的合併操作
5(SRV_FORCE_NO_UNDO_LOG_SCAN)不查看撤銷日誌undo log。innodb存儲引擎會將未提交的事務視爲已提交
6(SRV_FORCE_NO_LOG_REDO)不執行前滾的操作

2.6、innodb plugin = 新版本的innodb存儲引擎
mysql-5.1使用了插件式的架構。在mysql-5.1.38前,安裝innodb plugin必須下載plugin的文件,再進行一系列的安裝。

第三章、文件

3.1參數文件
my.cnf文件。
3.1.1、什麼是參數
鍵=值
3.1.2、參數類型
分爲global和session

3.2、日誌文件
3.2.1、錯誤日誌
show global variables like ‘log_error’;
3.2.2、慢查詢日誌
show global variables like ‘%long%’;
show global variables like ‘log_slow_queries’; //mysql-5.1.2開始支持微妙級慢查詢。
show global variables like ‘log_queryies_not_using_indexs’; //未使用索引的查詢
mysql-5.1開始可以將慢查詢放入log_output表中。
3.2.3、查詢日誌
mysql-5.1開始可以將查需日誌放入general_log表中
3.2.4、二進制日誌
主要功能
恢復 基於時間點的恢復
複製
以下參數影響二進制日誌
max_binlog_size
binlog_cache_size
sync_binlog
binlog-do-db
binlog-ignore-db
log-slave-update
binlog_format
將binlog_format設置成row,可以支持事務隔離級別爲READ COMMITTED,以獲得更好的併發性。
在使用MIXED格式下,mysql採用STATEMENT格式進行二進制日誌文件的記錄,但是有一些情況下會使用ROW格式,可能的情況如下:
1、表的存儲引擎爲NDB,這個時候DML操作都會以ROW格式記錄
2、使用了uuid()、user(),current_user(),found_rows(),row_count(),等不確定函數。
3、使用了insert delay語句
4、使用了用戶定於的函數(UDF)
5、使用了臨時表(temporary table)

注意:針對系統庫mysql裏面的表發生變化的處理規則如下:
1、如果採用insert,update,delete直接操作表,則日誌根據binlog_format設定的格式記錄。
2、如果使用grant,revoke,set password等DCL語句,那麼無論如何都會使用SBR模式記錄。
3、blockhole引擎不支持row格式,ndb引擎不支持statement格式。

3.3、套件字文件
show variables likea ‘socket’;
3.4、pid文件
show variables like ‘pid_file’;
3.5、表結構定義文件
*.frm
3.6、innodb引擎文件
重做日誌文件,表空間文件
3.6.1、表空間文件
默認會有一個大小10M的tablespace file,名字叫ibdata1.可以通過innodb_data_file_path=/db/ibdata1:2000M;/dr2/db/ibdata2:2000M:autoextend 來配置。這裏使用了兩個文件來組成的表空間,如果他們不在一個磁盤上的話,對IO性能會有所優化。
還有一個innodb_file_per_table,這個參數會讓每個innodb引擎的表都單獨產生一個表空間。
注意:單獨表空間只保存該表的數據,索引和插入緩衝等信息,其餘信息還是保存在共享表空間的(undo頁,系統事務信息,二次寫)。
3.6.2、重做日誌文件(redo log)
redo log是在實例或者介質失敗的時候,用來保證數據完整性。
每個innodb存儲引擎至少有一個重做日誌組,每個重做日誌文件組下至少又2個重做日誌文件,如默認的ib_logfile0、ib_logfile1.爲了得到更高的可靠性,你可以設置多個重做鏡像日誌組(mirrored log groups)。
innodb_log_file_size //指定重做日誌文件的大小
innodb_log_files_in_group //指定重做日誌組中文件的數量,默認爲2
innodb_mirrored_log_groups //指定日誌鏡像文件組的數量,默認爲1
innodb_log_group_home_dir //指定了日誌文件組所在的路徑
還有一個很重要的參數與redo log相關,那就是innodb_flush_log_at_trx_commit的值,對innodb的性能影響很大。他有0,1,2三個值,0代表提交事務時,並不同步寫redo log,而是等master threas每秒寫。1代表commit的時候就將redo log緩存寫入磁盤,2代表commit的時候將redo log緩存異步的寫入磁盤。


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