二、複合查詢算法
在進行復合查詢時,爲了體現外鏈接(left join、right join)和通常聯合查詢的區別,對student表增長了幾條記錄,而這幾條記錄在std_cur和course中都沒有對應的記錄。sql
2.1 多表聯合查詢socket
多表聯合查詢的邏輯處理過程以下所示:函數
JOIN:prepare階段測試
setup_tables():對查詢涉及的表,逐個查看是否存在,設置變量相應的值,爲查詢準備。優化
setup_fields():對查詢的字段進行檢查,不一樣於以前1.1的檢查,該過程當中若是不指定具體數據表的字段的話,將會對全部查詢的數據表進行檢查。ui
setup_conds():檢查查詢的where條件中字段是否存在,一樣若是不指定具體數據表的字段,將會對全部查詢的數據表進行檢查。(sql_base.cc:8379)lua
JOIN:optimize階段spa
simplify_joins():若是能夠將外鏈接簡化爲內鏈接處理,那麼簡化爲內鏈接處理。此外,若是查詢爲內鏈接或者外鏈接查詢使用的表拒絕 NULL值,那麼將ON條件添加到where條件中,將表的鏈接操做轉化爲聯合查詢處理。在該測試中,因爲沒有顯示的JOIN ON操做,所以不作以上處理。設計
optimize_cond():優化查詢的where條件,對等值條件調用build_equal_items()(sql\sql_select.cc:8273)函數進行優化,查詢條件轉化爲(multiple equal(`test`.`student`.`std_id`, `test`.`std_cur`.`std_id`) 和 multiple equal(101, `test`.`course`.`cur_id`, `test`.`std_cur`.`cur_id`)兩個等值條件);調用propagate_cond_constants()(sql\sql_select.cc:8763)函數將字段等值轉換爲常量;調用remove_eq_conds()函數去除常量等值,例如(1=1)(sql\sql_select.cc:9405)。
make_join_statistics():因爲where條件查詢的字段是主鍵,可使用索引。經過調用update_ref_and_keys()(sql\sql_select.cc:3967)函數,查找是否有使用索引的字段。因爲查詢條件中的cur_id爲主鍵索引,且爲const類型。所以,調用create_ref_for_key()(sql\sql_select.cc:5848)函數爲索引建立索引空間,調用join_read_const_table()(sql\sql_select.cc:12109)函數查找索引得到查找的記錄。對使用索引(非主鍵)查詢的表,調用get_quick_record_count()函數估計須要讀取的記錄數和讀取時間,主要處理邏輯調用SQL_SELECT::test_quick_select()函數實現,具體處理邏輯以下所示。
join_read_const_table():經過查找索引獲取查詢的記錄。其中調用join_read_const()(sql\sql_select.cc:12222)函數用於取出數據。
SQL_SELECT::test_quick_select():該函數調用get_mm_tree()(sql\opt_range.cc:5518)函數獲取查詢樹,調用get_key_scans_params()函數獲取最優的查找範圍,並估計查詢的記錄數,具體處理邏輯以下。(sql\sql_select.cc:2651)
get_key_scans_params():該函數調用check_quick_select()(sql\opt_range.cc:7519)函數,核心處理邏輯調用check_quick_keys()(sql\opt_range.cc:7628)函數,遞歸估計經過索引查找的記錄的行數,經過調用ha_innodb.cc::records_in_range()(storage\innobase\handler\ha_innodb.cc:7505)估計該範圍內索引查找的記錄數。
choose_plan():查找最優的查詢計劃。實際處理過程,調用greedy_search()貪婪查找算法實現。(sql\sql_select.cc:4913)
greedy_search():貪婪查找最優執行計劃的算法。具體處理過程調用遞歸函數best_extension_by_limited_search()實現。(sql\sql_select.cc:5219)
best_extension_by_limited_search():因爲當前查詢對多表進行聯合查詢,因此在該階段會對聯合查詢的全部表進行組合,查找最優化的聯合查詢方式。(sql\sql_select.cc:5425)
get_best_combination():根據查找的最優的查詢組合方式,生成查詢計劃。(sql\sql_select.cc:5794)
JOIN:exec階段
do_select():執行查詢,並將查詢結果經過socket進行寫操做,具體過程調用sub_select()函數執行。(sql\sql_select.cc:11431)
sub_select():因爲是聯合查詢,該過程會調用evaluate_join_record()逐條取出查詢計劃中的第一個表的每條記錄,根據where條件過濾符合條件的記錄,而後經過聯合查詢條件查找聯合查詢表中的記錄。若是聯合查詢表中沒有索引,則對聯合表進行全表掃描;若是有索引,則經過索引進行查找;若是第一個表和聯合查詢表的聯合查詢條件都沒有索引,則會掃描第一個表的逐行記錄,根據where條件過濾符合條件的記錄,而後根據聯合條件,全表掃描聯合表,過濾查詢記錄。(sql\sql_select.cc:11705)
evaluate_join_record():取出每條記錄。(sql\sql_select.cc:11758)
多表聯合查詢,執行SQL:
SELECT student.std_id, std_name, std_spec, std_***, std_age, cur_name, cur_credit, cur_hours, score FROM student, course, std_cur WHERE student.std_id = std_cur.std_id AND course.cur_id = std_cur.cur_id AND course.cur_id = 101;
對應的查詢計劃以下所示:
從查詢處理邏輯和查詢計劃能夠看出,該過程的處理是:首先分析可知,(course.cur_id = std_cur.cur_id AND course.cur_id = 101)能夠轉化爲const類型。則根據course.cur_id主鍵索引,能夠定位course表的惟一記錄,該過程如測試1.3主鍵查詢;而且根據course.cur_id能夠經過聯合主鍵索引,定位std_cur表中的記錄。其次,根據查詢優化器的貪婪算法,對查詢組合方式進行估計,選擇最優的組合方式,即:std_cur經過索引查找到的記錄(記錄數:26)聯合student表(記錄數:31,因爲增長了五條記錄)進行查詢。最後,對std_cur表經過索引查找的記錄進行遍歷,並經過查詢條件student.std_id = std_cur.std_id從student表中主鍵查找對應的student表中對應的數據。
2.2 Join查詢
從測試計劃來看,從2.2至2.8的測試主要對join的不一樣組合方式進行測試,查看查詢優化器對不一樣join方式的處理方式和產生查詢計劃的差別。經過測試能夠有利於根據查詢表的數據量和索引狀況,來優化join的查詢sql語句。
測試2.2用於測試student表join聯合course表和 std_cur表的結果集,聯合條件和2.1測試中的where條件一致。
具體的查詢處理邏輯以下所示:
JOIN:prepare階段
setup_tables():同2.1測試。
setup_fields():同2.1測試。
setup_conds():相似2.1測試,不一樣之處在於,該過程查詢ON條件中涉及的字段是否存在。此外,該函數的處理過程是首先查詢where條件中的字段,而後查詢on條件中的字段,而且還對CHECK OPTION條件進行檢查和處理。
JOIN:optimize階段
simplify_joins():相似2.1測試。因爲該過程當中有JOIN ON鏈接,因此該過程會將ON條件添加到where條件中,而且將顯示的JOIN查詢簡化爲多表聯合查詢。
optimize_cond():同2.1測試。
make_join_statistics():同2.1測試。
join_read_const_table():同2.1測試。
SQL_SELECT::test_quick_select():同2.1測試。
get_key_scans_params():同2.1測試。
choose_plan():同2.1測試。
greedy_search():同2.1測試。
best_extension_by_limited_search():同2.1測試。
get_best_combination():同2.1測試。
JOIN:exec階段
如下同2.1測試。
Join查詢,執行SQL:
SELECT student.std_id, std_name, std_spec, std_***, std_age, cur_name, cur_credit, cur_hours, score FROM student JOIN(course, std_cur) ON (student.std_id=std_cur.std_id AND std_cur.cur_id=course.cur_id AND course.cur_id = 101);
對應的查詢計劃以下所示:
經過以上測試能夠看出,從處理邏輯和執行計劃來看,多表聯合查詢和join查詢的處理邏輯和執行過程是一致的。也就是說聯合查詢的表用「,」和join查詢時同樣的。所不一樣的是,在setup_conds()中須要對ON條件進行處理,在simplify_joins()中將ON條件添加到where條件進行處理,最終join查詢處理過程轉化多表聯合查詢。
此外,經過查看MySQL官方文檔發現,「,」進行聯合查詢,實際是進行cross join查詢,而join、cross join、inner join的操做是同樣的,能夠互相替換的[1]。
若是從理論上不理解緣由的話,能夠以簡單的處理邏輯來理解。具體來講,對於多表聯合查詢操做、join、cross join以及inner join等鏈接操做來講,MySQL的處理邏輯都是同樣的。
首先,MySQL對待where條件和on條件的處理邏輯是相同,只有處理的前後(先處理where條件,後處理on條件)。
而後,將ON條件添加到where條件中,根據查詢的條件查看數據表中的索引是否可使用,若是是主鍵查詢且查詢結果只有一條的記錄,直接獲取查詢結果;若是是索引查詢(ref類型),調用get_quick_record_count()獲取查詢記錄的行數並估計使用的時間,並生成查詢樹;若是沒有索引,則爲全表查詢。特別的,該過程當中對於聯合主鍵索引來講,若是查詢條件中只對聯合主鍵的一個進行查詢時,查詢處理過程與索引查詢(ref類型)一致。
接下來,經過調用best_extension_by_limited_search()函數,進行不一樣的組合方式,查找最優的查詢組合方式,生成查詢計劃。所以,在該過程當中,表的順序是沒有關係的,即:a join b和b join a的效果是同樣的,對查詢計劃沒有影響。
最後,執行查詢時,逐條查詢驅動表(該表是指查詢計劃中的第一個表)的每條記錄,根據where條件過濾查詢結果,而後在聯合查詢表中,查找符合聯合條件的記錄。逐條查詢驅動表的每條記錄並不是必定是全表掃描該表,若是是索引查詢,則經過生成的查詢樹,逐條查詢每條記錄,不然進行全表掃描。對應的聯合查詢表,根據聯合查詢條件進行過濾查詢記錄。一樣若是聯合查詢表的對應字段可使用索引,那麼使用索引定位記錄;若是沒有可使用的索引,則進行全表掃描。
2.3 JOIN嵌套查詢
測試2.3主要用於比較兩個JOIN的聯合查詢對查詢計劃的影響,以及查詢優化器處理邏輯的差別。
具體的查詢處理邏輯以下所示:
JOIN:prepare階段
setup_tables():同2.1測試。
setup_fields():同2.1測試。
setup_conds():檢查where條件和ON條件中的字段是否存在,一樣若是不指定具體數據表的字段,將會對全部查詢的數據表進行檢查。(sql_base.cc:8379)
JOIN:optimize階段
simplify_joins():同2.2測試。此外,因爲有JOIN ON的嵌套操做,所以增長了一次處理過程。
optimize_cond():同2.1測試。
make_join_statistics():同2.1測試。
join_read_const_table():同2.1測試。
SQL_SELECT::test_quick_select():同2.1測試。
get_key_scans_params():同2.1測試。
choose_plan():同2.1測試。
greedy_search():同2.1測試。
best_extension_by_limited_search():同2.1測試。
get_best_combination():同2.1測試。
JOIN:exec階段
如下同2.1測試
Join嵌套查詢,執行SQL:
SELECT student.std_id, std_name, std_spec, std_***, std_age, cur_name, cur_credit, cur_hours, score FROM student JOIN std_cur ON student.std_id = std_cur.std_id JOIN course ON std_cur.cur_id=course.cur_id WHERE course.cur_id = 101;
對應的查詢計劃以下所示:
經過以上測試能夠看出,與測試2.2的處理邏輯基本一致。不一樣之處是不但有ON條件的處理,還增長了where條件的處理,最終join查詢處理過程轉化多表聯合查詢。所以,內鏈接的嵌套JOIN查詢,與多表聯合查詢的處理過程是一致的,只是在處理過程當中,須要首先對內嵌的JOIN進行處理。
2.4 JOIN嵌套查詢(普通字段)
測試2.3中的JOIN嵌套查詢中,where字段爲course表的主鍵索引,而且是常量條件和一條記錄。爲了更深刻的研究嵌套JOIN查詢的處理邏輯,設計2.4測試。
具體的查詢處理邏輯以下所示:
JOIN:prepare階段
setup_tables():同2.1測試。
setup_fields():同2.1測試。
setup_conds():同2.3測試。
JOIN:optimize階段
simplify_joins():同2.3測試。
optimize_cond():同2.1測試。
make_join_statistics():調用update_ref_and_keys()(sql\sql_select.cc:3967)函數,因爲where條件的字段沒有索引,所以找不到可用的索引。而且where條件字段不是主鍵索引,所以不會調用函數create_ref_for_key()(sql\sql_select.cc:5848)和函數join_read_const_table()(sql\sql_select.cc:12109)來查詢const 記錄。儘管表連接操做使用索引,可是因爲where條件中的字段不能使用索引,所以連接操做也沒法使用索引進行快速獲取讀取的記錄數和讀取時間。所以,也不能調用get_quick_record_count()函數和SQL_SELECT::test_quick_select()函數。
choose_plan():同2.1測試。
greedy_search():同2.1測試。
best_extension_by_limited_search():相似2.1測試。不一樣之處在於因爲where條件不是常量const查詢,因此在進行best_access_path()查找最優的查詢計劃時,對三個表進行不一樣的組合,獲取最優的查詢記錄數和最優的查詢時間,從而最終生成查詢計劃。
get_best_combination():相似2.1測試。不一樣之處在於,course表進行全表掃描,其餘表調用create_ref_for_key()函數進行索引查詢,並生成最終的查詢計劃。
JOIN:exec階段
如下與2.1測試相似,不一樣之處在於這裏是三個表的join查詢。
最後的查詢執行過程詳細以下:首先對course進行全表掃描,當course中找到符合查詢條件的記錄,經過主鍵查找std_cur表中的記錄,再根據std_cur表中的符合主鍵std_id查找student表中的記錄。查找記錄數爲:(20+1*26*1),其中20表示全表掃描course表的每條記錄;1表示只有一條記錄符合查詢條件;26表示符合條件的cur_id在std_cur中有26條記錄符合查詢條件,而且經過符合主鍵查找;1表示經過std_cur中的std_id主鍵查找student表。
Join嵌套查詢(普通字段),執行SQL:
SELECT student.std_id, std_name, std_spec, std_***, std_age, cur_name, cur_credit, cur_hours, score FROM student JOIN (std_cur JOIN course ON std_cur.cur_id=course.cur_id) ON student.std_id = std_cur.std_id WHERE course.cur_name = 'PHP';
對應的查詢計劃以下所示:
經過以上分析能夠看出,不一樣於以前的join查詢之處在於,當使用主鍵查詢時,因爲是常量查詢,會直接取出查詢記錄,而且轉化表連接條件(std_cur.cur_id = course.cur_id)爲確切的查詢常量條件(std_cur.cur_id = 101),能夠直接經過索引建立查詢樹。而且在執行查詢時,爲兩個表的聯合查詢。而當普通查詢條件時,則查詢爲全表掃描,而且也不能經過連接條件進行任何的簡化操做。只能在查詢執行時,經過索引提升聯合查詢表的查詢效率。
此外,從查詢計劃能夠看出,std_cur表查詢的記錄行數實際上爲26,而查詢計劃中的記錄行數爲13。這是因爲在best_extension_by_limited_search()中查找最優計劃時,進行估計查詢記錄行數和查詢時間時,得出的一個估計值,並不是實際值。