天天看點

MySQL · 捉蟲動态 · left-join多表導緻crash

有一天小編胡亂寫sql, left join了30張表, 結果導緻了mysql server gone away…

我們來看看crash堆棧

可以看出, 在産生執行計劃過程中crash了。

堆棧表明, <code>update_ref_and_keys</code>函數中<code>join_tab-&gt;join-&gt;join_list</code>為無效位址。 排檢視到函數入口處這個變量還是ok的, 那麼在gdb裡watch一下。

這麼整齊的位址一看就有問題。函數棧:

而<code>add_key_fields</code>修改<code>join_tab-&gt;join-&gt;join_list</code>實際是不合理的, 是以這裡說明一下路徑上幾個關鍵的函數。

還要從子查詢優化說起,當遇到semi-join子查詢情況下, <code>join::optimize()</code>會調用<code>join::flatten_subqueries</code>改寫sql, 如下形式:

會被修改為:

函數<code>join::flatten_subqueries</code>, 做了以下幾件事:

建立semi join(it1, …, itn)的節點并添加到外層查詢語句的from文法樹下

将<code>subq_where and oe=ie</code>加入到外層查詢語句的where樹下

再移除原先的子查詢語句

<code>join::flatten_subqueries</code>中, 對于每一個子查詢, 調用函數<code>join::convert_subquery_to_semijoin</code>, 那麼子查詢上維護的query資訊也要同步加到外部查詢上。是以可見, 子查詢中的資訊, 會轉交給外部查詢。

之後, <code>join::optimize()</code>調用<code>update_ref_and_keys</code>, 這個函數用來處理出最終查詢要使用的索引。crash的問題也出現在這個函數中, 是以還要看<code>update_ref_and_keys</code>内部做了什麼。

在函數<code>update_ref_and_keys</code>中, 一個重要的數組, key_fields, 用來存放所有可能用到的索引字段。先通過<code>key_fields=(key_field*) thd-&gt;alloc(sz)</code>配置設定空間, 再調用<code>add_key_fields</code>遞歸周遊where樹, 遇到等值表達式, 會填充到<code>key_fields</code>數組中。而之前已經看到, add_key_field在寫key_fields時卻修改了<code>join_tab-&gt;join-&gt;join_list</code>。

可見在new的時候拿到了<code>join_tab-&gt;join-&gt;join_list</code>, 是(*key_fields++)的時候, 加過頭了。進而可推斷, key_fields沒有配置設定到應該有的記憶體空間。那麼出問題的就是sz用來配置設定空間的數字了。

這裡涉及到兩個變量<code>select_lex-&gt;cond_count</code>和<code>select_lex-&gt;between_count</code>, 而cond_count就是number of conditions; 構造的語句中的等值表達式足有31條, 而這裡在配置設定時是2, 活該記憶體越界。

而這個變量在子查詢優化過程中, 子查詢應該将其移交給外部查詢語句。

函數<code>join::convert_subquery_to_semijoin</code>中, 改寫完sql後, 忘記把子查詢的cond_count和between_cond資訊更新到外部查詢了, 這時隻要手動添加即可。

<a href="https://github.com/mysql/mysql-server/commit/71e74f2a0118f460abc4f7a3da215c61785d35f0" target="_blank">官方修複(5.6.25)參見</a>

<a href="http://dev.mysql.com/worklog/task/?id=5275" target="_blank">相關worklog參見</a>

可以通過以下方式複現

然後執行

mysql5.6在5.6.25之前的小版本都可以複現, 請盡情調戲 .^.