天天看點

TokuDB · 捉蟲動态 · MRR 導緻查詢失敗

最近有使用者在使用 tokudb 時,遇到了一個查詢報錯的問題,這裡給大家分享下。

具體的報錯資訊是這樣的:

表結構如下:

從報錯資訊來看,是引擎層傳回錯誤的,難道是 tokudb 資料出問題了麼,我們首先要确認的是使用者資料是否還能通路。

從表結構來看,出錯的語句應該走了二級索引,那麼我們強制走 pk 是否能通路資料呢。

上面的測試可以說明走 pk 是沒問題呢,那麼問題可能在二級索引。

同時我們在觀察使用者的其它 sql 時發現,二級索引也是可以通路資料的。

比如下面這種:

都是走二級索引,為什麼有的會報錯呢,這 2 條語句有啥差別呢,explain 看下:

把這個優化關掉呢?

可以看到,關掉優化器的 mrr 後,語句就傳回正常了。是以基本可以判斷是 mrr 導緻的。

下面我們從源碼層面分析下看,到底是怎麼回事。

根據報錯資訊,來 gdb 跟蹤,發現導緻報錯的棧是這樣的,可以看到是在 mrr 執行初始化階段:

具體在 dsmrr_impl::dsmrr_init 中的邏輯是這樣的:

我們對應看下 tokudb 裡條件下推接口實作:

可以看到 <code>ha_tokudb::idx_cond_push</code> 是會将原條件在傳回給 server 的。是以就導緻了 <code>dsmrr_impl::dsmrr_init</code> 傳回錯誤碼 1 (got error 1 from storage engine)。

而 <code>handler:idx_cond_push()</code> 接口是允許引擎層傳回非 null 值的,引擎層認為自己沒有完全過濾結果集,那麼是可以傳回條件給 server 層,讓 server 層再做一次過濾的:

是以這個問題是 mrr 在實作上的一個 bug,沒有考慮引擎在icp時傳回非 null 的情況。

另外我們在查問題時發現,如果 mysqld 重新開機或者通過 flush table 關閉表的話,查詢是不會出錯的:

從 explain 結果看,是因為沒有用到 mrr,這又是為什麼呢?

我們看下優化器是如何選擇是否用mrr優化的,在 <code>dsmrr_impl::choose_mrr_impl()</code> 這個函數裡是有這樣的邏輯的:

可以看到,mrr 選擇條件是這樣的:

如果引擎的 cache 比表大的話,是不會用 mrr 優化的;

如果引擎沒有 cache,預設用 100m,用于自己不管理 cache 引擎,如 myisam;

如果要查詢的行數不超過50的話,也是不會用 mrr 優化的;

這個 cache 對 innodb 來說,就是 <code>innodb_buffer_pool_size</code>;對 tokudb 來說,就是 <code>tokudb_cache_size</code>。但是 tokudb handler 層沒有實作 <code>get_memory_buffer_size()</code> 這個接口,導緻一直用 100m 做為 cache 來判斷,這個是 tokudb handler 實作的上的一個bug。

而 <code>data_file_length</code> 這個是值是記憶體資訊,在表剛關閉重新打開的時候,是0,是以不會用mrr優化。

另外還有一個判斷條件時,如果要求排序的話,也是不會用 mrr 優化的,這也就是為什麼我們剛開始發現的,語句中用了 order by 後,explain 結果中就沒有 mrr了。

從上面的分析來看,滿足下面條件語句會被影響:

語句通路的是 tokudb 表,并且走的二級索引,有回表操作;

表大小超過 100m;

簡單的判斷方法是,explain 結果中有 <code>using index condition; using where; using mrr</code>,并且語句報錯 got error 1 from storage engine。

臨時的解決方法是關閉優化器的 mrr 或者 icp:

繼續閱讀