數據表算法
CREATE TABLE `student` ( `std_id` int(11) NOT NULL, `std_name` varchar(20) NOT NULL DEFAULT '', `std_spec` varchar(20) NOT NULL DEFAULT '', `std_***` tinyint(4) NOT NULL DEFAULT '0', `std_age` tinyint(3) unsigned NOT NULL DEFAULT '0', PRIMARY KEY (`std_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; CREATE TABLE `course` ( `cur_id` int(11) NOT NULL, `cur_name` varchar(20) NOT NULL DEFAULT '', `cur_credit` tinyint(4) NOT NULL DEFAULT '0', `cur_hours` smallint(6) NOT NULL DEFAULT '0', PRIMARY KEY (`cur_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; CREATE TABLE `std_cur` ( `std_id` int(11) NOT NULL, `cur_id` int(11) NOT NULL, `score` tinyint(4) NOT NULL DEFAULT '0', PRIMARY KEY (`std_id`,`cur_id`), KEY `std_cur_ibfk_2` (`cur_id`), CONSTRAINT `std_cur_ibfk_1` FOREIGN KEY (`std_id`) REFERENCES `student` (`std_id`) ON DELETE NO ACTION ON UPDATE CASCADE, CONSTRAINT `std_cur_ibfk_2` FOREIGN KEY (`cur_id`) REFERENCES `course` (`cur_id`) ON DELETE NO ACTION ON UPDATE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
1、普通查詢sql
1.1 「*「處理 socket
邏輯處理過程以下所示:函數
JOIN:prepare階段性能
setup_tables():對查詢涉及的表(student表),查看是否存在,設置變量相應的值,爲查詢準備。(sql\sql_base.cc:7963)測試
setup_wild():將查詢中全部「*「展開爲對應數據表的字段。主要調用insert_fields()實現。(sql\sql_base.cc:7739)優化
insert_fields():將「*」替換,展開爲數據表的字段。(sql\sql_base.cc:8151)ui
set_fields():檢查全部字段是否在數據表中。(我的觀點:對於替換了「*」的字段,能夠省去檢查字段的操做,或者對於「*」處理過程能夠在set_fields()過程當中處理)。(sql\sql_base.cc:7825)lua
JOIN:optimize階段spa
make_join_statistics():計算每一個表查詢匹配記錄數和讀取時間。均估計值,並不是實際的值。(sql\sql_select.cc:2651)
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\sql_select.cc:5425)
JOIN:exec階段
do_select():執行查詢,並將查詢結果經過socket進行寫操做。(sql\sql_select.cc:11431)
sub_select():調用evaluate_join_record()逐條取出查詢的每條記錄。(sql\sql_select.cc:11705)
evaluate_join_record():取出每條記錄。(sql\sql_select.cc:11758)
"*"查詢student中的全部字段,執行SQL:
SELECT * FROM student;
對應的查詢計劃以下所示:
由以上處理過程能夠知道,對於沒有過濾條件以及分組排序條件的狀況下,查詢優化器處理僅對「*」替換爲對應表的字段,而且還對字段進行了再次驗證是否在查詢的表中。所以,在寫sql查詢中,不管任何狀況下,避免使用「*」,而是直接添加各個字段。而且,僅將須要的字段名進行查詢並輸出。
對於全表查詢來講,查詢優化器沒有任何的優化策略。
1.2 查詢字段
查詢表中的字段,進行全表查詢時,處理邏輯與1.1相似,惟一區別在於,省去了setup_wild()和insert_fields()處理過程。1.2測試與1.1進行對比,從而深刻理解,查詢優化器是如何工做。從而更加驗證一點,爲何不建議使用「*」查詢。
查詢student中的指定字段,執行SQL:
SELECT std_id, std_name, std_spec, std_***, std_age FROM student;
對應的查詢計劃以下所示:
1.3 Distinct條件
Distinct的邏輯處理過程以下所示:
JOIN:prepare階段
setup_tables():同1.1測試。
set_fields():同1.1測試。
JOIN:optimize階段
make_join_statistics():計算每一個表查詢匹配記錄數和讀取時間。在該測試中,該過程有不一樣的操做,主要針對distinct條件。其中,調用add_group_and_distinct_keys()函數查找distinct能夠使用的索引。(sql\sql_select.cc:2651)
add_group_and_distinct_keys():查找distinct能夠使用的索引。此外,經過調用Item_field::collect_item_field_processor()(sql\sql_item.cc:683)獲取全部查詢字段,存儲到查詢列表中。而且合併全部字段的(sql\sql_select.cc:4265)
choose_plan():同1.1測試。
greedy_search():同1.1測試。
best_extension_by_limited_search():同1.1測試。
list_contains_unique_index():查看查詢的字段中,是否有惟一索引。若是有查詢字段上有惟一索引,那麼處理過程將被轉化爲指定字段的全表查詢。(sql\sql_select.cc:13576)
create_distinct_group():根據查詢的字段建立一個group,order順序爲給定字段的排列順序。爲將distinct轉化爲group 條件。(sql\sql_select.cc:15168)
calc_group_buffer():根據查詢的字段,計算group by的所需的buffer。(sql\sql_select.cc)
create_tmp_table():根據查詢字段,建立臨時表。(sql\sql_select.cc:10229)
JOIN:exec階段
do_select():相似1.1測試,可是當前查詢是將查詢的結果寫到臨時表中。
sub_select():相似1.1測試,將查詢字段的每條記錄取出放到臨時表中。
evaluate_join_record():同1.1測試。
change_to_use_tmp_fields():將操做切換到臨時表操做。(sql\sql_select.cc:15828)
JOIN::make_simple_join():初始化join。(sql\sql_select.cc:6075)
calc_group_buffer():同上。
do_select():相似1.1測試,不一樣之處在於,該查詢在臨時表中進行。
sub_select():。相似1.1測試,不一樣之處在於,記錄查詢從臨時表中獲取,並從中過濾掉不符合查詢條件的記錄。
evaluate_join_record():同1.1測試。
查詢student中沒有重複的字段,執行SQL:
SELECT DISTINCT std_spec, std_***, std_age FROM student;
對應的查詢計劃以下所示:
由以上處理邏輯能夠看出,distinct的優化方式是將distinct轉化爲group by條件來執行,而且會用到臨時表操做。所以,在須要distinct操做時,能夠轉化爲group by操做,來避免轉化過程。
將distinct轉化GROUP BY後的SQL執行處理邏輯以下所示:
JOIN:prepare階段
setup_tables():通1.1測試。
set_fields():同1.1測試。
setup_group():初始化group by列表,並調用find_order_in_list()函數,檢查group by 的字段是否在select的字段中(調用find_item_in_list(),(sql\sql_base.cc:6835)),並查找group by列表中的字段是否在數據表中 (調用find_item_in_tables(),(sql\sql_base.cc:6602))。(sql\sql_select.cc:15037)
JOIN:optimize階段
make_join_statistics():同1.3測試。
add_group_and_distinct_keys():相似1.3測試,區別是,這裏處理的group條件,而不是distinct條件。
choose_plan():同1.3測試。
greedy_search():同1.3測試。
best_extension_by_limited_search():同1.3測試。
list_contains_unique_index():省去。
create_distinct_group(): 省去。
calc_group_buffer():同1.3測試。
create_tmp_table():同1.3測試。
JOIN:exec階段
全部操做同1.3測試
查詢student中沒有重複的字段,執行SQL:
select std_spec, std_***, std_age FROM student GROUP BY std_spec, std_***, std_age;
對應的查詢計劃以下所示:
經過轉化後的測試能夠驗證,將distinct轉化爲group by處理過程,能夠避免經過程序根據查詢字段,轉換group by的過程。
1.4 Primary key條件
主鍵查詢的邏輯處理過程以下所示:
JOIN:prepare階段
setup_tables():通1.1測試。
setup_fields():同1.1測試。
setup_conds():檢查查詢的where條件中字段是否存在。(sql_base.cc:8379)
JOIN:optimize階段
optimize_cond():優化查詢的where條件,對等值條件調用build_equal_items()(sql\sql_select.cc:8273)函數進行優化,例如(a=b AND c=d AND (b=c OR d=5)轉化爲等值(a,b,c,d) OR (a=b AND b=c AND d=5));調用propagate_cond_constants()(sql\sql_select.cc:8763)函數將字段等值轉換爲常量,例如(a=b AND c=d AND d=5轉化爲a=b AND c=5 AND d=5);調用remove_eq_conds()函數去除常量等值,例如(1=1)(sql\sql_select.cc:9405)。
make_join_statistics():與1.1測試不一樣,因爲where條件查詢的字段是主鍵,能夠使用索引。經過調用update_ref_and_keys()(sql\sql_select.cc:3967)函數,查找是否有使用索引的字段。調用create_ref_for_key()(sql\sql_select.cc:5848)函數爲索引建立索引空間。調用join_read_const_table()(sql\sql_select.cc:12109)函數查找索引得到查找的記錄。這裏由於使用了主鍵索引,因此只有一行匹配內容,記錄數和讀取時間均設置爲1。因爲使用主鍵索引對單個數據表進行查詢,所以不須要對現有的查詢進行繼續優化,。(sql\sql_select.cc:2651)。
join_read_const_table():經過查找索引獲取查詢的記錄。其中調用join_read_const()(sql\sql_select.cc:12222)函數用於取出數據。
JOIN:exec階段
do_select():與以前操做不一樣,該過程將取出的數據直接發送。
主鍵的查詢性能較高,僅有一條查詢記錄,查詢計劃爲const類型。與unique key索引的查詢優化器執行過程相似。
查詢student中主鍵字段,執行SQL:
SELECT std_id, std_name, std_spec, std_***, std_age FROM student WHERE std_id = 2012072306;
對應的查詢計劃以下所示:
1.5普通條件查詢
普通where查詢條件的邏輯處理過程以下所示:
JOIN:prepare階段
setup_tables():通1.1測試。
setup_fields():同1.1測試。
setup_conds():同1.4測試。
JOIN:optimize階段
optimize_cond():同1.4測試。
make_join_statistics():與1.4測試相似,不一樣之處在於,因爲where條件字段沒有索引,所以在查找索引時沒有匹配的索引能夠使用。因此該函數會調用update_ref_and_keys()函數,查找是否有使用索引的字段,可是不調用create_ref_for_key()和join_read_const_table()。也由於不能經過索引查找,接下來的操做跟1.1測試的全表查詢相似。
choose_plan():如下操做同1.1測試。
JOIN:exec階段
如下操做同1.1測試。
對普通字段進行where條件查詢時,若是查詢字段沒有索引,將進行全表查詢,查詢計劃類型爲all。
查詢student中普通字段,執行SQL:
SELECT std_id, std_name, std_spec, std_***, std_age FROM student WHERE std_id = 2012072306;
對應的查詢計劃以下所示:
爲查詢字段添加索引後,進行一樣的操做,查看添加索引後,查詢優化器的處理邏輯和查詢計劃的改變狀況。添加索引操做:
ALTER TABLE student ADD INDEX idx_student_name (std_name);
添加索引後,查詢優化器的處理邏輯以下所示:
JOIN:prepare階段
setup_tables():通1.1測試。
setup_fields():同1.1測試。
setup_conds():同1.4測試。
JOIN:optimize階段
optimize_cond():同1.4測試。
make_join_statistics():與1.4測試不一樣,因爲where條件查詢的字段是非主鍵,可是能夠使用索引。首先經過調用update_ref_and_keys()(sql\sql_select.cc:3967)函數,查找是否有使用索引的字段。在查到能夠使用索引後,並不會調用create_ref_for_key()和join_read_const_table()讀取記錄。而是調用get_quick_record_count()函數估計須要讀取的記錄數和讀取時間,主要處理邏輯調用SQL_SELECT::test_quick_select()函數實現,具體處理邏輯以下所示。
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)估計該範圍內索引查找的記錄數。(sql\opt_range.cc:4923)
choose_plan():同1.3測試。
greedy_search():同1.3測試。
best_extension_by_limited_search():與以前不一樣,由於查詢能夠使用索引,所以該函數調用best_access_path()(sql\sql_select.cc:4365)函數根據查詢計劃查找最佳路徑。
JOIN:exec階段
如下操做同1.3測試。
當查詢字段上有索引的狀況下,在調用make_join_statistics()函數對查詢代價進行估計時,使用索引樹進行查找,從而避免了全表查詢,並能夠在執行階段提升執行效率。從查詢計劃的類型來看,使用ref索引類型。
查詢student中普通字段,執行SQL:
SELECT std_id, std_name, std_spec, std_***, std_age FROM student WHERE std_id = 2012072306;
對應的查詢計劃以下所示:
若是當前where條件查詢的結果是離散分佈的,而且查詢結果佔總記錄數的很小部分的查詢狀況,根據業務需求,建議使用索引,能夠有效提升查詢的效率。