一年的時間過得真快,去年這個時候,哥還在上海看mysql 5.6的feature, 今年就在北京看mysql 5.7的feature了。
mysql的union語句一直是被人廣為诟病的,因為它不分青紅皂白,總是會創建temporary table, 然後把全部數據寫入此臨時表,再從中讀取數據返回給用戶。
其實有些情況下,比如UNION ALL且沒有最外層排序條件( top level ORDER BY)的時候,完全可以直接從第一張表裡面讀取數據返回給用戶,再從第二張表裡讀取數據返回給用戶…….沒有必要使用臨時表。
如果能夠做到這一點,可以省去創建,寫入,讀取臨時表的過程,可以節省內存或磁盤空間,同時第一張表的數據可以立刻返回給用戶。
不過呢,臨時表也有一個隱含的優點:由於多張表的字段type可能不完全相同,當數據寫入臨時表的時候,會自動做type casting,寫入完畢再讀取的時候,得到的column type就完全一致了。
下面就粗略地講一講mysql 5.7.3 是如何優化UNION ALL的,我對源碼也不了解的說。
1) 什麼樣的UNION語句可以不寫入臨時表
sql/sql_lex.cc
/**
Decide if a temporary table is needed for the UNION.
@retval true A temporary table is needed.
@retval false A temporary table is not needed.
*/
bool st_select_lex_unit::union_needs_tmp_table()
{
return union_distinct != NULL ||
global_parameters()->order_list.elements != 0 ||
thd->lex->sql_command == SQLCOM_INSERT_SELECT ||
thd->lex->sql_command == SQLCOM_REPLACE_SELECT;
}
可以看到,如果滿足以下全部條件,那麼就不必寫入臨時表:
1. UNION ALL,非UNION,非UNION DISTINCT。
2. 沒有global ORDER BY語句。
3. UNION不是INSERT … SELECT語句的一部分。
4. UNION不是REPLACE…SELECT語句的一部分。
2) 創建臨時表
其實 ,即使做了優化,也還是存在臨時表的創建過程,
因為臨時表可以做type casting。
正常情況下,臨時表的創建create_tmp_table()是分為二個過程的:
1. 創建表結構 data structure ,細節看不懂。
2. 如果skip_create_table為false,調用instantiate_tmp_table(),把臨時表創建出來,細節看不懂。
mysql5.7.3 優化之後,跳過了創建臨時表的第2部分:
if (is_union() && !union_needs_tmp_table())
{
fake_select_lex= NULL;
instantiate_tmp_table= false;
}
else
{
instantiate_tmp_table= true;
}
3) select_union與select_union_direct
為了處理不寫入臨時表的情況, mysql創建了一個新的 class: class select_union_direct :public select_union
比較一下這二個class send_data的不同點:
bool select_union::send_data(List<Item> &values)
{
int error= 0;
if (unit->offset_limit_cnt)
{ // using limit offset,count
unit->offset_limit_cnt--;
return 0;
}
fill_record(thd, table->field, values, 1, NULL, NULL);
if (thd->is_error())
return 1;
if ((error= table->file->ha_write_row(table->record[0])))
{
/* create_myisam_from_heap will generate error if needed */
if (!table->file->is_ignorable_error(error) &&
create_myisam_from_heap(thd, table, tmp_table_param.start_recinfo,
&tmp_table_param.recinfo, error, TRUE, NULL))
return 1;
}
return 0;
}
bool select_union_direct::send_data(List<Item> &items)
{
if (!limit)
return false;
limit--;
if (offset)
{
offset--;
return false;
}
fill_record(thd, table->field, items, true, NULL, NULL);
if (thd->is_error())
return true; /* purecov: inspected */
return result->send_data(unit->item_list);
}
可以看到, select_union_direct 只是做了fill_record(),並沒有ha_write_row()寫入真實的行數據。
所以,臨時表的創建,只是為了fill_record()做type casting.
與此同時,explain select union 的輸出也做了一定的改變。