天天看點

MySQL核心月報 2015.02-MySQL · 社群動态· 5.6.23 InnoDB相關Bugfix

本節摘取了mysql5.6.23的幾個和innodb相關的主要bugfix,簡單闡述下問題及解決方案。

<b>問題一</b>

當執行flush table..for export指令時,會暫停purge線程的操作。這一步通過設定一個标記purge_sys-&gt;state的值為purge_state_stop來告訴purge線程該停下來歇歇了。

然而如果purge線程目前正在函數srv_do_purge中工作,該函數會執行一個while循環,退出條件是目前server shutdown,或者上次purge的page數為0,并沒有檢查purge線程的狀态是否被設定為purge_state_stop; 很顯然,如果目前的history list非常長,那麼可能需要等待purge完成後,才能退出循環,而在使用者看來,就好像hang了很久一樣。推長history list 很容易:開啟一個打開read view的事務(例如rr級别下執行一個select)不做送出,同時有并發的dml,跑一段時間history list就上去了。

<b>解決</b>

在函數srv_do_purge函數的while退出條件中加上purge線程狀态判斷,如果被設定為purge_state_stop,就退出循環。

<a href="https://github.com/mysql/mysql-server/commit/f9a1df899b724d26d7997a49e6403bbe90024bf3">更新檔</a>

<b>問題二</b>

在執行innodb crash recovery階段,如果發現不合法的大字段,就會去調用函數ib_warn_row_too_big 去列印一條warning,函數為push_warning_printf。然而這個函數的目的是給用戶端傳回一條warning,而這時候系統還在崩潰恢複階段,并沒有合法的thd對象,是以造成系統崩潰。

tips:這個bug是在更新到新版本5.6出現的,最根本的原因是5.6新版本對大字段長度做的限制。早期版本5.6及之前的版本,我們可以定義非常大的blob字段,但如果字段太長,對這些字段的修改,可能導緻redo log的checkpoint點被覆寫,因為計算redo log 空間是否足夠,并沒有依賴即将插入的redo 記錄長度,而僅僅是保留一定的比例。是以在5.6.22版本中做了限制:如果blob的長度超過innodb_log_file_size * innodb_log_files_in_group的十分之一時,就會更新失敗,給使用者傳回db_too_big_record的錯誤碼。這個問題在5.7版本裡被徹底解決:每寫4個blob外部存儲頁,檢查一次redo log空間是否足夠,如果不夠用,就推進checkpoint點。

在函數ib_warn_row_too_big中判斷目前線程thd是否被初始化,如果為null,直接傳回,不調用push_warning_printf。

<a href="https://github.com/mysql/mysql-server/commit/901ce5314b6b0d4115b0237491e2afaafe5a274e">更新檔</a>

<b>問題三</b>

當我們通過alter語句修改一個被外鍵限制的列名時,由于沒有從資料詞典cache中将包含老列名的cache項驅逐掉,導緻重載外鍵限制時失敗。

舉個簡單的例子:

可以看到,盡管t1表的a列已經被rename成 id,但列印出來的資訊也并沒有更正。

當被外鍵限制的列名被修改時,将對應的外鍵項從資料詞典cache中驅逐,當其被随後重新加載時就會使用新的對象。

<a href="https://github.com/mysql/mysql-server/commit/a54364d2d1c147d6c325c818057de470672f8e3d">更新檔</a>

<b>問題四</b>

如上文所提到的,在新版本innodb中,對blob字段的資料操作需要保證其不超過總的redo log file大小的十分之一,但是傳回的錯誤碼db_too_big_record及列印的資訊太容易讓人誤解,大概如下:

輸出更合适、更直覺的錯誤資訊,如下:

<a href="https://github.com/mysql/mysql-server/commit/4423b9b5633d91e5793ee637ac068059001f85ba">更新檔</a>

<b>問題五</b>

flush table操作在某些情況下可能導緻執行個體crash。 例如如下執行序列:

當執行flush table時,在重載表cache時,innodb層會針對每個表設定其狀态(ha_innobase::store_lock)。如果執行flush 操作,并且加的是讀鎖時,就會調用函數row_quiesce_set_state将table-&gt;quiesce設定為quiesce_start。在上例中,表t1的兩個表名表均加讀鎖,造成重複設定狀态為quiesce_start,導緻斷言失敗。

tips:在5.6版本中,雖然有明确的flush table..for export指令來協助轉儲ibd檔案。但實際上,簡單的flush table操作預設就會産生一個tbname.cfg的配置檔案,拷貝該檔案和ibd,可以将資料轉移到其他執行個體上。table-&gt;quiesce用于辨別操作狀态,例如,如果辨別為quiesce_start,就會在函數ha_innobase::external_lock中調用row_quiesce_table_start來啟動配置檔案的生成。

移除斷言

<a href="https://github.com/mysql/mysql-server/commit/a3f3c2ab7a1b985775f4e58529a4dd563c025b8e">更新檔</a>

<b>問題六</b>

線上執行個體錯誤日志中偶爾出現 “unable to purge a record”,從官方bug系統來看,很多使用者都遇到了類似的問題。

當change buffer子產品以如下序列來緩存索引操作時可能産生上述錯誤資訊:

記錄被标記删除(ibuf_op_delete_mark)

随後插入相同記錄--ibuf_op_insert

purge線程需要實體删除二級索引記錄,操作被buffer--ibuf_op_delete

當讀入實體頁時,總是需要進行ibuf merge。如果執行到ibuf_op_delete這種類型的change buffer時,發現記錄并沒有被标記删除,就會導緻錯誤日志報錯。

顯然上述的操作序列是不合理的,正确的序列應該是ibuf_op_delete_mark,ibuf_op_delete,ibuf_op_insert。

為了搞清楚邏輯,我們簡單的理一下相關代碼。

注意ibuf_op_delete是由第一步的标記删除操作觸發,purge線程發起;在每個buffer pool的控制結構體中,有一個成員buf_pool-&gt;watch[buf_pool_watch_size],buf_pool_watch_size的值為purge線程個數,用于輔助purge操作。

假定記憶體中沒有對應的page,purge線程會做如下幾件事兒:

首先查詢buffer pool,看看page是否已經讀入記憶體;如果不在記憶體中,則将page no等資訊存儲到watch數組中,并插入page hash(buf_pool_watch_set)。(如果随後page被讀入記憶體,也會删除watch标記)

判斷該二級索引記錄是否可以被purge(row_purge_poss_sec,當該二級索引記錄對應的聚集索引記錄沒有delete mark并且其trx id比目前的purge view還舊時,不可以做purge操作)

随後在插入ibuf_op_delete類型的ibuf記錄時,還會double check下該page是否被設為sentinel (ibuf_insert_low,buf_pool_watch_occurred),如果未被設定,表明已經page已經讀入記憶體,就可以直接去做purge,而無需緩存了。

對于普通的操作類型,例如ibuf_op_insert和ibuf_op_delete_mark,同樣也會double check page 是否讀入了記憶體。在函數ibuf_insert中會調用buf_page_hash_get進行檢查,如果page被讀入記憶體,則不緩存操作,如果請求的page被設為sentinel,則從buf_page_hash_get傳回null,是以随後判定需要緩存該類型的操作。這也正是問題的所在:

标記删除記錄,寫入ibuf_op_delete_mark

purge線程設定page對應的sentinel,完成合法性檢查,準備調用ibuf_insert

插入相同記錄,寫入ibuf_op_insert

purge線程寫入ibuf_op_delete

如果記錄所在的page被設定了一個sentinel,那麼對該page的并發插入操作就不應該緩存到change buffer中,而是直接去嘗試讀取實體頁。

<a href="https://github.com/mysql/mysql-server/commit/ec369cb4f363161dfbbbd662b20763b54808b7d1">更新檔</a>

<b>問題七</b>

對于非windows系統的平台上,函數os_file_pwrite和os_file_pread在碰到io錯誤時傳回-1,并錯誤的作為寫入/讀取的位元組數寫在錯誤日志中。

單獨記錄失敗的系統調用日志,列印更可讀的日志資訊。

<a href="https://github.com/mysql/mysql-server/commit/ae0f4c17c82d1d3ee89ca5afb64655b4ab1d2552">更新檔</a>

<b>問題八</b>

在崩潰恢複後立刻執行一次slow shutdown (innodb_fast_shutdown = 0) 可能産生斷言失敗crash。原因是當完成crash recovery後,對于需要復原的事務,會起單獨的線程來執行,這時候如果shutdown執行個體,會觸發觸發purge線程内部斷言失敗:ut_a(n_pages_purged == 0 || srv_fast_shutdown != 0);

等待trx_rollback_or_clean_all_recovered完成後,再進行slow shutdown

<a href="https://github.com/mysql/mysql-server/commit/8edcc65fcd0c930a902cdf1c41ad0a1aaf21ff90">更新檔</a>

繼續閱讀