天天看點

innodb purge操作

Purge搜集的幾個感覺不錯的文章

一、

​​ http://blog.sina.com.cn/s/blog_aed82f6f010195me.html​​

DML操作都會生成Undo頁,系統需要定期對這些undo頁進行清理,也就是所謂purge操作。在5.5之前這些都是在master線程中完成,但5.5及之後的版本可以通過innodb_purge_threads來控制是否使用獨立線程進行purge操作。

undolog以及purge線程如何運作做介紹

1.undo log

Undolog儲存了記錄修改前的鏡像。其中insertundo records在事務復原時會被丢棄,而updateundo records會被用到rollback,MVCC以及Purge操作中。

delete操作:DELETEFROM t WHERE c = 1,innodb不會立刻移除相應的記錄,在innodb的官方部落格上是這麼解釋的:

  1. It marks the record as deleted by setting a bit in the control bits of the record.
  2. Stores the before image of the modified columns to the UNDO log
  3. Updates the system columnsDB_TRX_ID and DB_ROLL_PTR in the clustered index record.DB_TRX_ID identifies the transaction that made the last change, and DB_ROLL_PTR points to the new UNDO log record. This UNDO log record contains the old values of DB_TRX_ID and DB_ROLL_PTR,possibly pointing to an older transaction and undo log entry.

在每個記錄上有兩個重要的系統資料列,用于多版本的控制:

DB_TRX_ID:最近修改這個記錄的事務ID

DB_ROLL_PTR:指向由于最近的更新建立的復原段

undorecord上也會指向舊的undo記錄,進而形成了一條更新鍊,通過這個更新鍊,不同的事務可以找到其對應的版本的undo資訊,組合成就版本記錄。

undolog和标記删除的記錄可能被其他事務所使用。當沒有事務需要這些資料時,标記删除的記錄和相關的undolog records可以被purge掉。

2.purge

Purge操作會克隆最老舊的read_view,這個read_view中持有控制資訊以限制哪些由其他事務引起的變化是可見的。然後以相反的方式讀取從最老的到最近的undolog記錄;如果目前運作的事務都沒有引用這些記錄,則分析這些記錄并從索引上移除被标記删除的記錄。

有兩個相關的控制參數:

Innodb_purge_threads:控制是否使用獨立purge線程

Innodb_purge_batch_size:表示一次完成多少個undolog page;但這個值有一個有趣的副作用是會影響到undolog的釋放,因為總是在128輪purge後釋放undolog page,在5.5及之後版本,開始支援128個復原段。

一個大緻的堆棧如下:

#0 row_purge (thr=0xabd4668, node=0xabd46d0) at/home/yinfeng/Percona-Server-5.5.18-rel23.0/storage/innobase/row/row0purge.c:757

#1 row_purge_step (thr=0xabd4668) at/home/yinfeng/Percona-Server-5.5.18-rel23.0/storage/innobase/row/row0purge.c:805

#2 0x0856bb0e in que_thr_step (thr=0xabd4668) at/home/yinfeng/Percona-Server-5.5.18-rel23.0/storage/innobase/que/que0que.c:1259

#3 que_run_threads_low (thr=0xabd4668) at/home/yinfeng/Percona-Server-5.5.18-rel23.0/storage/innobase/que/que0que.c:1319

#4 que_run_threads (thr=0xabd4668) at/home/yinfeng/Percona-Server-5.5.18-rel23.0/storage/innobase/que/que0que.c:1356

#5 0x08492922 in trx_purge (limit=20) at/home/yinfeng/Percona-Server-5.5.18-rel23.0/storage/innobase/trx/trx0purge.c:1194

#6 0x0848766d in srv_purge_thread (arg=0x0) at/home/yinfeng/Percona-Server-5.5.18-rel23.0/storage/innobase/srv/srv0srv.c:3897

#7 0xb76d3d31 in start_thread (arg=0x8d907b70) at pthread_create.c:304

#8 0xb75ed0ce in clone () at../sysdeps/unix/sysv/linux/i386/clone.S:130

Purge操作的入口函數為srv_purge_thread,主要工作在一個大while循環裡。主要處理函數

trx_purge。

Trx_purge的參數也就是Innodb_purge_batch_size

主要流程如下:

1).rw_lock_x_lock(&purge_sys->latch);

2)判斷DML操作是否需要被delay,隻有當參數innodb_max_purge_lag被設定為一個大于0的值時,才會去判斷。

3)擷取目前最老舊的事務read_view(read_view_oldest_copy_or_open_new)

4) thr =que_fork_start_command(purge_sys->query); //不是很明白,留待後續研究

5)主函數調用流程:

que_run_threads->que_run_threads_low-> que_thr_step-> row_purge_step->row_purge

que開頭的函數都定義在que0que.cc檔案中,看起來是根據查詢計劃圖來排程相應的子產品函數。

比如對于purge操作,會在que_thr_step裡調用row_purge_step,而對于commit,會在row_purge_step裡調用trx_commit_step。這部分内容還不是很了解,後面會跟進分析

row_purge到底做了什麼吧

1)trx_purge_fetch_next_rec,從曆史undolog清單中選擇一條記錄

id的復原段(purge_sys->rseg)

trx_purge_get_rseg_with_min_trx_id

——從復原段中讀取記錄

trx_purge_read_undo_rec

purge_sys->n_pages_handled做加1,如果已經purge的記錄超過了Innodb_purge_batch_size,則設定purge_sys->state= TRX_STOP_PURGE

2)row_purge_parse_undo_rec

undolog裡解析出行引用資訊和其他資訊,傳回值為true表明需要執行purge操作.

trx_undo_rec_get_pars獲得undo記錄的類型,主要包括以下幾個類型:

TRX_UNDO_INSERT_REC:

freshinsert into clustered index

TRX_UNDO_UPD_EXIST_REC:

updateof a non-delete-marked record

TRX_UNDO_UPD_DEL_REC

updateof a delete marked record to a not delete marked record; also the

fieldsof the record can change

TRX_UNDO_DEL_MARK_REC

deletemarking of a record; fields do not change

TRX_UNDO_CMPL_INFO_MULT

compilationinfo is multiplied by this and ORed to the type above

TRX_UNDO_UPD_EXTERN

Thisbit can be ORed to type_cmpl to denote that we updated externalstorage fields: used by purge to free the external storage *

purge操作時,需要:

row_mysql_freeze_data_dictionary(trx);

create/droptable操作,因為會對dict_operation_lock加S鎖,這是個全局鎖。當create/drop表的時候需要X鎖,一些背景操作例如Purge、rollback、外鍵檢查需要S鎖,在一個事務裡,我們可以通過

trx_struct::dict_operation_lock_mode來檢視是否擁有該鎖

trx_undo_rec_get_row_ref(從undolog中獲得邏輯記錄資訊??)

trx_undo_update_rec_get_update(基于undolog建立updatevector??)

updateundo log record中建立一個partialrow;It contains the

columnswhich occur as ordering in any index of the table

3)對一個标記删除的記錄做purge操作

row_purge_del_mark

4)row_purge_upd_exist_or_extern()

Purgesan update of an existing record. Also purges an update of a deletemarked record if that record contained an externally stored field.

purge,會依次檢視二級索引,調用row_build_index_entry建立一個老的記錄,然後調用row_purge_remove_sec_if_poss從二級索引種将舊記錄删除。

row_purge_remove_clust_if_poss來删除主鍵上的記錄

6)row_mysql_unfreeze_data_dictionary,釋放在dict_operation_lock上的S鎖

7)清理操作

參考資料:

1.​​http://blogs.innodb.com/wp/2011/07/allow-undo-logs-to-reside-in-their-own-tablespace/​​

2.​​http://blogs.innodb.com/wp/2011/04/mysql-5-6-multi-threaded-purge/​​

3.percona5.5.18

二、

參數innodb_fast_shutdown提醒了我一直沒研究過的innodb特性之一——insert buffer。首先簡述這個參數,然後談談insert buffer的實作。

Innodb_fast_shutdown告訴innodb在它關閉的時候該做什麼工作。有三個值可以選擇:

1.  0表示在innodb關閉的時候,需要purge all, merge insert buffer,flush dirty pages。這是最慢的一種關閉方式,但是restart的時候也是最快的。後面将介紹purge all,merge insert buffer,flush dirty pages這三者的含義。

2.  1表示在innodb關閉的時候,它不需要purge all,merge insert buffer,隻需要flush dirty page。

3.  2表示在innodb關閉的時候,它不需要purge all,merge insert buffer,也不進行flush dirty page,隻将log buffer裡面的日志flush到log files。是以等下進行恢複的時候它是最耗時的。

那麼在mysql restart的時候它的恢複流程(也稱作crash recovery)是怎麼樣的呢?

1.   如果在上次關閉innodb的時候是在innodb_fast_shutdown=2或是mysql crash這種情況,那麼它會利用redo log重做那些已經送出了的事務。

2.   接下來的操作就是這麼幾個:

a>     Rollback uncompleted transitions 取消那些沒有送出的事務

b>     Purge all 清除無用的undo頁

c>      Merge insert buffer 合并插入緩沖

下面詳解purge all、merge insert buffer、flush dirty page

1.   Purge all

 這個操作主要是删除那些無用的undo頁。對于delete操作,innodb是通過先将要删除的那一行标記為删除,而不是馬上清除這一行,因為innodb實作了MVCC,這些undo段用來實作MVCC機制。MVCC也就是常說的多版本控制,鎖不阻塞讀,讀也不阻塞寫,這樣大大提高了并發性。那麼在一緻性讀的時候,怎麼才能找到和事務開始的那個版本呢?對于主鍵索引,每個行都有一個事務ID和一個undo ID,這個undo ID指向了這行的先前版本的位置。對于非主鍵索引,也就是常說的secondary index,是通過先找主鍵索引再找到undo段。而對于update操作,則是先标記删除,然後insert一個新的行,接下來如果有一緻性讀,那麼查找old version的行的原理和delete操作是一樣的,詳情見[1]。現在接着說purge all操作,随着DML的操作越來越多,那麼復原段必然也會越來越多導緻占用了許多磁盤空間,那麼innodb就會定期删除一些無用的undo頁,首先,innodb重新開機的時候必然undo頁都會無效是以會進行purge all操作,另外,随着時間的推移必然一些事務已經完成,它們已不再需要某些undo頁,那麼這些undo在mysqld running的時候也會定期的進行清除,主要是在master thread中進行,雖然mysql5.5裡面增加了一個參數innodb_purge_threads來進行purge工作,但是這個參數的預設值是0,手冊上解釋說這個功能在mysql5.5中還不完善,增加它的目的隻是表明這是innodb的發展方向。

2.   Merge insert buffer

 Insert buffer是innodb的一個特性之一,在非聚簇、且不是唯一索引(即非主鍵索引、非唯一索引)的情況下,如果插入的索引行所屬的頁在buffer pool中就直接更新這個頁,否則它會将這個索引行插入到insert buffer中,然後定期對這個insert buffer進行合并(合并的本質工作就是将insert buffer中的資訊更新到真正的索引檔案中去)。因為innodb的secondary index是非聚簇的,那麼插入很有可能帶來大量的随機I/O,而如果利用insert buffer對一些屬于相同頁的行進行合并,那麼就會減少随機IO進而提高性能。但是這裡需要注意的是,insert buffer和doublewrite buffer是類似的概念,他實際上屬于system tablespace中的一部分[2],正由于它也是持久化存儲,那麼在伺服器當機或是重新開機之後這些資訊不會丢失,是以也就有了在前面介紹innodb_fast_shutdown時所說:在innodb重新開機時,可能需要進行merge insert buffer。那麼在什麼情況下需要對insert buffer進行merge操作呢?

a>     在innodb restart的時候

b>     master thread會定期的進行merge操作

c>       每次讀取secondary index page時,如果所需頁不在buffer pool,而這些頁在insert buffer中的時候,這時需要先對insert buffer進行合并,然後才能被讀取。為什麼這樣呢?因為所有插入的索引行所屬的頁如果不在buffer pool中,而又在insert buffer中,那麼它一定代表了頁的最新狀态(不了解?因為每次插入索引行的時候,如果所需頁不在

buffer pool中就直接插入到insert buffer中,而一旦insert buffer merge後相關的行也就不在insert buffer更新secondary index page了)。這時或許你會問那麼為什麼不直接讀取insert buffer中的頁然後繼續操作而一定要合并(更新到索引檔案)呢?因為在innodb中是資料檔案(也就是主鍵索引)和索引檔案緩存的,在insert buffer中讀取了需要的頁後,那麼必然就會在buffer pool中緩存了這個頁,而如果這個頁還留在insert buffer中卻不更新到secondary index page去,那麼,第一,這将不能保證索引檔案得到更新;第二,insert buffer的空間會被占用。而如果這一步将insert buffer 合并後,不但減小了insert buffer的使用空間,而且将這merge操作完成了一部分,減小了以後merge的負擔(不是有句話叫做今日事今日畢麼),不過這也減慢了讀的操作,因為讀操作必須等待這個頁的合并。

3.   Flush dirty page

這是最好了解的一個概念了,重新整理髒頁到磁盤。Innodb是資料檔案和索引檔案緩存的(innodb中的資料檔案本質上也是索引檔案,隻是習慣這麼稱呼而已),從磁盤讀到buffer中的檔案被修改後,那麼就成了dirty page髒頁。而如果這些修改頁的操作被送出了之後這些頁就必須被flush到磁盤上。

啰嗦了這麼久基本上将mysql的insert buffer工作原理大緻說清楚了,不過需要注意的是在mysql5.5中這個insert buffer已經改名了,叫做change buffer,不見包含了insert buffer,而且包括了update buffer,delete buffer。最後提一句,随着SSD、Fusion IO這類型存儲出現,很多時候我們考慮随機IO帶來的影響或許對它們就不适用了。

因為沒有讀源碼,這些了解是通過讀其他的資料而來的,是以還留下了幾個問題:

1.   實作insert buffer的資料結構是什麼?我想應該是樹狀結構,因為這會為合并那一步提升效率。理由:第一,如果是無序連結清單的最開始的插入效率可能會比較高,但是最終判斷哪些行在相同頁或是相鄰頁的時候需要排序,這裡的代價會比較高。而有序的連結清單在性能上沒有二叉樹這種結構效率高。

2.   Insert buffer占多大空間?如果很小那豈不是隻能容納幾行?那麼在系統壓力的時候,有空間來應付插入壓力麼?而如果比較大的,那麼怎麼保證在合并時候的效率?

參考文檔:

[1] http://blogs.innodb.com/wp/2010/09/mysql-5-5-innodb-change-buffering/

[2] http://www.mysqlperformanceblog.com/2009/01/13/some-little-known-facts-about-innodb-insert-buffer/

繼續閱讀