程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 數據庫知識 >> MYSQL數據庫 >> MySQL綜合教程 >> Bug #19528825 "UNABLE TO PURGE A RECORD",

Bug #19528825 "UNABLE TO PURGE A RECORD",

編輯:MySQL綜合教程

Bug #19528825 "UNABLE TO PURGE A RECORD",


概述:

      在生產環境中,當開啟insert buffer時(參數innodb_change_buffering=all),部分實例偶爾會出現“UNABLE TO PURGE A RECORD”錯誤。這個其實是一個老bug,從官方bug系統來看,也有其它用戶遇到過類似的問題,並且官方在今年2月份已經對該bug進行了修復。

insert buffer:

      在描述問題的產生原因和解決方案之前,首先來簡單說說insert buffer。簡單來說,insert buffer是二級索引操作的一個緩存。在進行二級索引操作時,若請求的page不在buffer pool中,則將二級索引操作進行緩存,待下次讀取該page時,將page與buffer中的操作合並,通過這種方式,可以減少在更新(INSERT,DELETE,UPDATE等)操作時磁盤的隨機讀,提高更新的響應時間。

      最早insert buffer僅支持insert 操作的buffer,所以這種buffer叫insert buffer,到mysql5.6時,不僅支持insert操作的buffer操作,還可以支持update,delete,purge等操作的buffer功能,而insert buffer也改稱changing buffer,後面我簡稱ibuf。通過參數innodb_change_buffering設置,用戶可以靈活控制二級索引操作的buffer開啟與否。ibuf僅支持普通二級索引,對於唯一索引和主鍵索引不起作用。我們知道,innodb表是索引組織表,每個表由一個聚簇索引和若干個二級索引組成。若使用ibuf,則對於每個二級索引的更新操作,都會對應產生一個ibuf操作符,下表列出了ibuf操作符與更新語句的對應關系:

操作

Insert buffer操作符

說明

Insert

IBUF_OP_INSERT

 

Delete

IBUF_OP_DELETE_MARK

Delete語句實際是對刪除記錄打標

Purge

IBUF_OP_DELETE

Purge真正物理上清理打刪除標的記錄

Update

IBUF_OP_DELETE_MARK

IBUF_OP_INSERT

二級索引的update由Delete+Insert組成。

purge:

      上節提到的purge其實並非用戶發起的更新語句,而是innodb存儲引擎實現MVCC(多版本並發控制)時,需要的一個後台清理操作。Innodb的多版本實現機制(利用回滾段保存歷史版本信息,並通過記錄中的ROLLPTR與回滾段記錄進行關聯)在刪除或更新記錄時,並非真正物理刪除,而僅僅是在記錄上打上刪除標記,通過後台線程進行清理,這個過程就是purge過程。當然,purge還有另外一個作用是清理回滾段,回收空間,以便空間可以重復利用。有關purge和mvcc的實現,有機會在單獨整理一篇文章。

問題產生的原因:

    回到問題本身,我們通過一個簡單的場景來復現問題。假設存在表t(id int, c1 varchar(100),primary key(id),key(c1)),表中包含一條記錄(1,a);並且假設表t的page都不在buffer pool中,以便可以使用ibuf。通過下表的操作序列,可以復現問題。

操作序列

更新語句

Ibuf操作

1【DELETE】

delete from t where id=1;

 

IBUF_OP_DELETE_MARK

2【INSERT】

insert into t values(1,’a’);

 

IBUF_OP_INSERT

3【PURGE】

 

IBUF_OP_DELETE

在讀取page的過程中,會進行合並操作,合並操作的順序是以對應page在ibuf中的操作順序來進行的,在執行到第3步:IBUF_OP_DELETE時,發現待刪除的記錄並沒有打刪除標記(第2步插入了相同主鍵+二級索引的記錄,將刪除標記清理),認為異常,導致拋錯。由於purge操作是由單獨的線程在後台執行,因此執行更新語句的操作與purge操作並沒有嚴格的先後順序,如果上述操作的順序變為1->3->2則不會復現問題。

重要流程:

     從上節分析來看,產生問題的主要原因是purge操作和更新操作沒有嚴格的同步,導致purge可能清理到未打刪除標記的記錄。

purge流程:

函數調用關系:

srv_do_purge->trx_purge->que_thr_step->row_purge_step
->row_purge->row_purge_record_func->row_purge_del_mark

->row_purge_remove_sec_if_poss-> row_purge_remove_sec_if_poss_leaf                                     

purge二級索引流程:

判斷是否使用ibuf調用關系:

row_purge_remove_sec_if_poss_leaf->row_search_index_entry->btr_pcur_open_func->btr_cur_search_to_nth_level->ibuf_should_try

更新操作與purge操作在ibuf中的協同:

      purge線程獲取page時,若page不在buffer中,將page設置watch標記,然後執行ibuf_insert將purge操作緩存。更新操作(insert,delete,update等)也調用ibuf_insert操作進行buffer,首先會判斷page是否有watch標記,若存在,則認為可能 與purge動作沖突,不能使用ibuf。此時,會去從磁盤讀取page,在讀取page過程中會將purge操作進行合並,後續進行更新操作則不會存在問題。通過watch標記來達到更新操作和purge操作協同使用ibuf的目的,避免上述提到的問題。

      到這裡,大家可能會有一個疑問,假設不使用ibuf,正常的更新和purge操作同樣是在不同的線程,也有可能出現(DELETE,INSERT,PURGE)序列,為啥就沒有問題呢?因為在purge二級索引時,還會調用row_purge_poss_sec函數,確認記錄是否可以purge(二級索引記錄對應的聚集索引沒有delete mark或者trx_id比purge view還新時,不能purge),從而避免上述問題。      

解決方法:

       同樣在purge二級索引過程中,btr_cur_search_to_nth_level,首先調用buf_page_get_gen函數進行watch設置,然後調用row_purge_poss_sec判斷記錄是否可以purge,若用戶已經re-insert,則此時purge動作忽略;否則,表示還沒有insert記錄進來,繼續執行調用ibuf_insert接口進行緩存操作。另一方面,更新操作,insert在使用ibuf時,會判斷是否有watch標記,但程序邏輯在返回時,將標記丟了,導致出現問題。因此只要保證更新操作真正使用ibuf前,檢查沒有purge同時使用ibuf,則可以避免問題發生。

詳細解法可以參考:

https://github.com/mysql/mysql-server/commit/ec369cb4f363161dfbbbd662b20763b54808b7d1

 

參考文檔:

http://mysqllover.com/?p=1264

https://bugs.mysql.com/bug.php?id=73767

http://hedengcheng.com/?p=94

http://mysql.taobao.org/monthly/2015/04/01/

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved