MySQL-技術專題-實戰技巧

{"type":"doc","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" join用於多表中字段之間的聯繫,在數據庫的"},{"type":"text","marks":[{"type":"strong"}],"text":"DML"},{"type":"text","text":" (數據操作語言,即各種增刪改查操作)中有着重要的作用。"}]},{"type":"paragraph","attrs":{"indent":1,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" 合理使用Join語句優化SQL有利於:"}]},{"type":"horizontalrule"},{"type":"numberedlist","attrs":{"start":1,"normalizeStart":1},"content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":1,"number":1,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"strong"}],"text":"增加數據庫的處理效率,減少響應時間;"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":1,"number":2,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"strong"}],"text":"減少數據庫服務器負載,增加服務器穩定性;"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":1,"number":3,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"strong"}],"text":"減少服務器通訊的網絡流量;"}]}]}]},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"1.Join的分類:"}]},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"strong"}],"text":"內連接 Inner Join"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"strong"}],"text":"全外連接 FULL Outer Join"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"strong"}],"text":"左外連接 Left Outer Join"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"strong"}],"text":"右外連接 Right Outer Join"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"strong"}],"text":"交叉連接 Cross Join"}]}]}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"連接的分類"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" 每種連接的區別作爲基礎內容,這裏就不再展開說明,請讀者自己參看其他文章瞭解,比如"},{"type":"link","attrs":{"href":"https://link.jianshu.com?t=https%3A%2F%2Fwww.cnblogs.com%2Fblueoverflow%2Fp%2F4714470.html","title":null},"content":[{"type":"text","text":"Mysql Join語法以及性能優化"}]}]},{"type":"paragraph","attrs":{"indent":1,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" 需要說明的是,目前MySQL不支持全連接,需要使用"},{"type":"text","marks":[{"type":"strong"}],"text":"UNION"},{"type":"text","text":"關鍵字進行聯合。"}]},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"strong"}],"text":"Union"},{"type":"text","marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}}],"text":":對兩個結果集進行並集操作,不包括重複行,同時進行默認規則的排序;"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"strong"}],"text":"Union All"},{"type":"text","marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}}],"text":":對兩個結果集進行並集操作,包括重複行,不進行排序;"}]}]},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"2.Join使用的注意事項"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"下面進行本文重點,Join的使用注意事項和技巧,首先給出要使用的表結構:"}]},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"-- auto-generated definition\nCREATE TABLE customer\n(\n id INT AUTO_INCREMENT PRIMARY KEY,\n cust_name VARCHAR(50) NOT NULL CHARSET utf8,\n over VARCHAR(100) NULL CHARSET utf8,\n CONSTRAINT customer_id_uindex\n UNIQUE (id)\n)\nENGINE = InnoDB;\n\n-- auto-generated definition\nCREATE TABLE faculty\n(\n id INT AUTO_INCREMENT PRIMARY KEY,\n user_name VARCHAR(50) NOT NULL CHARSET utf8,\n over VARCHAR(200) NULL CHARSET utf8,\n CONSTRAINT faculty_id_uindex\n UNIQUE (id)\n)\nENGINE = InnoDB;"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/ac/ac0c055e95732674948b825cfc3b2e6f.png","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/ff/fff77ce9bce961e4c82f93071b6f1b8a.png","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"2.1 顯式連接 VS 隱式連接"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"所謂顯式連接,即如上顯示使用"},{"type":"text","marks":[{"type":"strong"}],"text":"inner Join"},{"type":"text","text":"關鍵字連接兩個表,"}]},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"select * from table a inner join table b on a.id = b.id;"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"而隱式連接即不顯示使用"},{"type":"text","marks":[{"type":"strong"}],"text":"inner Join"},{"type":"text","text":"關鍵字,如:"}]},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"select a.*, b.* from table a, table b where a.id = b.id;"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" "},{"type":"text","marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}}],"text":" 二者在功能上沒有差別,實現的性能上也幾乎一樣。只不過隱式連接是"},{"type":"text","marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"strong"}],"text":"SQL92"},{"type":"text","marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}}],"text":"中的標準內容,而在"},{"type":"text","marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"strong"}],"text":"SQL99"},{"type":"text","marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}}],"text":"中顯式連接爲標準,雖然很多人還在用隱私連接,但是它已經從標準中被移除。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}}],"text":" 從使用的角度來說,還是推薦使用顯示連接,這樣可以更清楚的顯示出多個表之間的連接關係和連接依賴的屬性。"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"2.2 On VS Where"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" "},{"type":"text","marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"strong"}],"text":"ON 條件(“A LEFT JOIN B ON 條件表達式”中的ON)用來決定如何從 B 表中檢索數據行。如果 B 表中沒有任何一行數據匹配 ON 的條件,將會額外生成一行所有列爲 NULL 的數據,在匹配階段 WHERE 子句的條件都不會被使用。僅在匹配階段完成以後,WHERE 子句條件纔會被使用。ON將從匹配階段產生的數據中檢索過濾。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" "},{"type":"text","marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"strong"}],"text":"所以我們要注意:在使用Left (right) join的時候,一定要在先給出儘可能多的匹配滿足條件,減少Where的執行。儘可能滿足ON的條件,而少用Where的條件,從執行性能來看也更加高效。"}]},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"3 Join的技巧"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"3.1 過慮條件更新自身表"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" 假設現在要將是職工中的消費者的“over”屬性設置爲\"優惠\",直接如下更新會報錯:"}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/6e/6ee74aeaaf1cb579b7e481efe6c4819b.png","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" 這是由於Mysql不支持這種查詢後更新(這其實是標準SQL中一項要求,Oracle、SQL Server中都是可以的)。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" 爲了解決這種更新的過慮條件中包含要更新的表的情況,可以把帶過濾條件的查詢結果當做一個新表,在新表上,執行更新操作。"}]},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"UPDATE (faculty f INNER JOIN customer c\n on user_name=cust_name)\nset c.over = \"優惠\";"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/0b/0b57d5c0c62ad8c0417e976425306292.png","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"更新成功"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"3.2 Join優化子查詢"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" 嵌套的子查詢是比較低效地,因爲每一條記錄都要進行匹配,如果記錄長度比較大的話,那麼我們的查詢就有可能非常的耗時。我們應該儘量避免使用子查詢,而用表連接。如下面的這個子查詢就可以轉化爲等價的連接查詢"}]},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"SELECT user_name, over ,(SELECT over FROM customer c where user_name=cust_name) as over2\nfrom faculty f;\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"SELECT user_name, f.over , c.over as over2\nfrom faculty f\n LEFT JOIN customer c ON cust_name=user_name;\n"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"3.3 Join優化聚合查詢"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"爲了說明這個問題 ,我們在添加一個工作量的表,記錄每個職工每天的工作量"}]},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"-- auto-generated definition\nCREATE TABLE tasks\n(\n id SMALLINT(5) UNSIGNED AUTO_INCREMENT\n PRIMARY KEY,\n facult_id SMALLINT(5) UNSIGNED NULL,\n timestr TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL,\n workload SMALLINT(5) UNSIGNED NULL\n)\nENGINE = InnoDB CHARSET = utf8;\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/6e/6e2849f87bce5d9efaa0ccde99d9739d.png","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"tasks記錄職工的工作量"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"比如我們想查詢每個員工工作量最多是哪一天,通過子查詢可以這樣實現:"}]},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"select a.user_name ,b.timestr,b.workload from faculty a join tasks b\non a.id = b.facult_id\nwhere b.workload = (\n select max(c.workload)\n from tasks c\n where c.facult_id = b.facult_id)"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"查詢結果"}]},{"type":"paragraph","attrs":{"indent":1,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"使用表連接優化之後:"}]},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"SELECT user_name, t.timestr, t.workload\nFROM faculty f\n JOIN tasks t ON f.id = t.facult_id\n JOIN tasks t2 ON t2.facult_id = t.facult_id\nGROUP BY user_name,t.timestr,t.workload\nHAVING t.workload = max(t2.workload);"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" 這裏額外的再連接了一個task表中內容,在這個“額外表”中通過聚合計算出工作量的最大值,然後再過慮(HAVING)出工作量最大的日期。"}]},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"strong"}],"text":"因爲聚合函數通過作用於一組數據而只返回一個單個值,因此,在SELECT語句中出現的元素要麼爲一個聚合函數的輸入值,要麼爲GROUP BY語句的參數,否則會出錯。"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" 但是mysql的"},{"type":"text","marks":[{"type":"strong"}],"text":"group by"},{"type":"text","text":"做過"},{"type":"text","marks":[{"type":"strong"}],"text":"擴展"},{"type":"text","text":"了,select之後的列允許其不出現在group by之後,MySQL在執行這類查詢語句時,它會默認理解爲,沒寫到GROUP BY子句的列,其列值是唯一的,如果GROUP BY省略的列值其實並不唯一,將會默認取第一個獲得的值,這樣就會指代不明,那麼最好不要使用這項功能。"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"3.4 如何實現分組查詢"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" 要獲取每個員工完成工作量最多的兩天。這個也可以通過Join來完成。"}]},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"select d.user_name,c.timestr,workload\nFROM (\n select facult_id,timestr,workload,\n (SELECT COUNT(*)\n FROM tasks b\n WHERE b.facult_id=a.facult_id AND a.workload<=b.workload) AS cnt\n FROM tasks a\n GROUP BY facult_id,timestr,workload) c\nJOIN faculty d ON c.facult_id=d.id\nWHERE cnt <= 2;"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" 其中,內部的查詢結果"},{"type":"text","marks":[{"type":"strong"}],"text":"cnt"},{"type":"text","text":"表示對於tasks表中某個給定記錄,相同員工的工作裏記錄比其大的數量有多少。"}]},{"type":"paragraph","attrs":{"indent":1,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" 內部查詢的結果如下:"}]},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"select facult_id,timestr,workload,\n (SELECT COUNT(*)\n FROM tasks b\n WHERE b.facult_id=a.facult_id AND a.workload<=b.workload) AS cnt\nFROM tasks a\nGROUP BY facult_id,timestr,workload;"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"內部查詢的結果"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"即每個工作量記錄信息和同一員工的工作量排名。"}]},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}}],"text":"cnt <= 2"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"就代表該記錄是某位員工的工作量最大兩天之一。"}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/36/365d1fa5a941f0eadb1a1ea44e74182f.png","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"每個員工完成工作量最多的兩"}]},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"4. join的實現原理"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" join的實現是採用Nested Loop Join算法,就是通過驅動表的結果集作爲循環基礎數據,然後一條一條的通過該結果集中的數據作爲過濾條件到下一個表中查詢數據,然後合併結果。如果有多個join,則將前面的結果集作爲循環數據,再一次作爲循環條件到後一個表中查詢數據。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"比如我們以如下SQL語句爲例:"}]},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"EXPLAIN \nSELECT C.id, cust_name,T.workload\nFROM customer C\n INNER JOIN faculty F\n ON C.cust_name = F.user_name\n INNER JOIN tasks T\n ON T.facult_id = F.id ;"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/80/80da352983871cfc0419d5dbb19485b3.png","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"EXPLAIN 連接查詢"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" 從"},{"type":"text","marks":[{"type":"strong"}],"text":"explain"},{"type":"text","text":"的輸出看出,MySQL選擇"},{"type":"text","marks":[{"type":"strong"}],"text":"C"},{"type":"text","text":"作爲驅動表,"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" 首先通過"},{"type":"text","marks":[{"type":"italic"}],"text":"Using Where"},{"type":"text","text":"和"},{"type":"text","marks":[{"type":"italic"}],"text":"Using join buffer"},{"type":"text","text":"來匹配"},{"type":"text","marks":[{"type":"strong"}],"text":"F"},{"type":"text","text":"中的內容,然後在其結果的基礎上通過主鍵的索引"},{"type":"text","marks":[{"type":"italic"}],"text":"PRIMARY,faculty_id_uindex"},{"type":"text","text":"匹配到"},{"type":"text","marks":[{"type":"strong"}],"text":"T"},{"type":"text","text":"表中的內容。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"其過程類似於三次次嵌套的循環。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"strong"}],"text":"需要說明的是,C作爲驅動表,通過"},{"type":"text","marks":[{"type":"italic"},{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"strong"}],"text":"Using Where"},{"type":"text","marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"strong"}],"text":"和"},{"type":"text","marks":[{"type":"italic"},{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"strong"}],"text":"Using join buffer"},{"type":"text","marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"strong"}],"text":"來匹配F,是因爲"},{"type":"codeinline","content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"strong"}],"text":"C.cust_name ,F.user_name"}],"marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"strong"}]},{"type":"text","marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"strong"}],"text":"都沒有加索引,要獲取具體的內容只能通過對全表的數據進行where過濾才能獲取,而"},{"type":"text","marks":[{"type":"italic"},{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"strong"}],"text":"Using join buffer"},{"type":"text","marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"strong"}],"text":"是指使用到了Cache(只有當join類型爲ALL,index,rang或者是index_merge的時候纔會使用join buffer),記錄已經查詢的結果,提高效率。"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"而對於"},{"type":"text","marks":[{"type":"strong"}],"text":"T"},{"type":"text","text":"和"},{"type":"text","marks":[{"type":"strong"}],"text":"F"},{"type":"text","text":"之間通過T的主鍵T.id連接,所以join類型爲"},{"type":"codeinline","content":[{"type":"text","text":"eq_ref"}]},{"type":"text","text":",也不用使用Using join buffer。"}]},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"5. join語句的優化原則"}]},{"type":"numberedlist","attrs":{"start":null,"normalizeStart":1},"content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"strong"}],"text":"用小結果集驅動大結果集"},{"type":"text","marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}}],"text":",將篩選結果小的表首先連接,再去連接結果集比較大的表,儘量減少join語句中的Nested Loop的循環總次數;"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"strong"}],"text":"優先優化Nested Loop的內層循環"},{"type":"text","marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}}],"text":"(也就是最外層的Join連接),因爲內層循環是循環中執行次數最多的,每次循環提升很小的性能都能在整個循環中提升很大的性能;"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":3,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}}],"text":"對被驅動表的join字段上建立"},{"type":"text","marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"strong"}],"text":"索引"},{"type":"text","marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}}],"text":";"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":4,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}}],"text":"當被驅動表的join字段上無法建立索引的時候,設置"},{"type":"text","marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"strong"}],"text":"足夠的Join Buffer Size"},{"type":"text","marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}}],"text":"。"}]}]}]},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"6.join的實現原理"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" join的實現是採用Nested Loop Join算法,就是通過驅動表的結果集作爲循環基礎數據,然後一條一條的通過該結果集中的數據作爲過濾條件到下一個表中查詢數據,然後合併結果。如果有多個join,則將前面的結果集作爲循環數據,再一次作爲循環條件到後一個表中查詢數據。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" 接下來通過一個三表join查詢來說明mysql的Nested Loop Join的實現方式。"}]},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"select m.subject msg_subject, c.content msg_contentfrom user_group g,group_message m,group_message_content cwhere g.user_id = 1and m.group_id = g.group_idand c.group_msg_id = m.id"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"使用explain看看執行計劃:"}]},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"explain select m.subject msg_subject, c.content msg_content from user_group g,group_message m,group_message_content c where g.user_id = 1 and m.group_id = g.group_id and c.group_msg_id = m.id\\G;"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"結果如下:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"*************************** 1. row ***************************\nid: 1\nselect_type: SIMPLE\ntable: g\ntype: ref\npossible_keys: user_group_gid_ind,user_group_uid_ind,user_group_gid_uid_ind\nkey: user_group_uid_ind\nkey_len: 4\nref: const\nrows: 2\nExtra:\n*************************** 2. row ***************************\nid: 1\nselect_type: SIMPLE\ntable: m\ntype: ref\npossible_keys: PRIMARY,idx_group_message_gid_uid\nkey: idx_group_message_gid_uid\nkey_len: 4\nref: g.group_id\nrows: 3\nExtra:\n*************************** 3. row ***************************\nid: 1\nselect_type: SIMPLE\ntable: c\ntype: ref\npossible_keys: idx_group_message_content_msg_id\nkey: idx_group_message_content_msg_id\nkey_len: 4\nref: m.id\nrows: 2\nExtra:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"從結果可以看出,explain選擇user_group作爲驅動表,首先通過索引user_group_uid_ind來進行const條件的索引ref查找,然後用user_group表中過濾出來的結果集group_id字段作爲查詢條件,對group_message循環查詢,然後再用過濾出來的結果集中的group_message的id作爲條件與group_message_content的group_msg_id進行循環比較查詢,獲得最終的結果。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這個過程可以通過如下代碼來表示:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"for each record g_rec in table user_group that g_rec.user_id=1{"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"     for each record m_rec in group_message that m_rec.group_id=g_rec.group_id{"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"          for each record c_rec in group_message_content that c_rec.group_msg_id=m_rec.id"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"                pass the (g_rec.user_id, m_rec.subject, c_rec.content) row"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"          combination to output;"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"      }"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果去掉group_message_content表上面的group_msg_id字段的索引,執行計劃會有所不一樣。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"drop index idx_group_message_content_msg_id on group_message_content;explain select m.subject msg_subject, c.content msg_content from user_group g,group_message m,group_message_content c where g.user_id = 1 and m.group_id = g.group_id and c.group_msg_id = m.id\\G;"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"得到的執行計劃如下:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"*************************** 1. row ***************************\nid: 1\nselect_type: SIMPLE\ntable: g\ntype: ref\npossible_keys: user_group_uid_ind\nkey: user_group_uid_ind\nkey_len: 4\nref: const\nrows: 2\nExtra:\n*************************** 2. row ***************************\nid: 1\nselect_type: SIMPLE\ntable: m\ntype: ref\npossible_keys: PRIMARY,idx_group_message_gid_uid\nkey: idx_group_message_gid_uid\nkey_len: 4\nref: g.group_id\nrows: 3\nExtra:\n*************************** 3. row ***************************\nid: 1\nselect_type: SIMPLE\ntable: c\ntype: ALL\npossible_keys: NULL\nkey: NULL\nkey_len: NULL\nref: NULL\nrows: 96\nExtra:Using where;Using join buffer"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"因爲刪除了索引,所以group_message_content的訪問從ref變成了ALL,keys相關的信息也變成了NULL,Extra信息也變成了Using Where和Using join buffer,也就是說需要獲取content內容只能通過對全表的數據進行where過濾才能獲取。Using join buffer是指使用到了Cache,只有當join類型爲ALL,index,rang或者是index_merge的時候纔會使用join buffer,它的使用過程可以用下面代碼來表示:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"for each record g_rec in table user_group{"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"      for each record m_rec in group_message that m_rec.group_id=g_rec.group_id{"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"           put (g_rec, m_rec) into the buffer"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"           if (buffer is full)"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"                 flush_buffer();"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"      }"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"flush_buffer(){"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"      for each record c_rec in group_message_content that c_rec.group_msg_id = c_rec.id{"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"            for each record in the buffer"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"                 pass (g_rec.user_id, m_rec.subject, c_rec.content) row combination to output;"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"      }"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"      empty the buffer;"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在實現過程中可以看到把user_group和group_message的結果集放到join buffer中,而不用每次user_group和group_message關聯後馬上和group_message_content關聯,這也是沒有必要的;需要注意的是join buffer中只保留查詢結果中出現的列值,它的大小不依賴於表的大小,我們在僞代碼中看到當join buffer被填滿後,mysql將會flush buffer。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"join語句的優化"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"1. 用小結果集驅動大結果集,儘量減少join語句中的Nested Loop的循環總次數;"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"2. 優先優化Nested Loop的內層循環,因爲內層循環是循環中執行次數最多的,每次循環提升很小的性能都能在整個循環中提升很大的性能;"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"3. 對被驅動表的join字段上建立索引;"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"4. 當被驅動表的join字段上無法建立索引的時候,設置足夠的Join Buffer Size。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"參考文章"}]},{"type":"numberedlist","attrs":{"start":null,"normalizeStart":1},"content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https://link.jianshu.com?t=http%3A%2F%2Fblog.itpub.net%2F7607759%2Fviewspace-692946%2F","title":null},"content":[{"type":"text","text":"MySQL數據庫對GROUP BY子句的功能擴展(1)"}]}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https://link.jianshu.com?t=ttps%3A%2F%2Fwww.cnblogs.com%2F8335IT%2Fp%2F5850531.html","title":null},"content":[{"type":"text","text":"SQL中GROUP BY語句與HAVING語句的使用"}]}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":3,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https://link.jianshu.com?t=https%3A%2F%2Fwww.cnblogs.com%2Fblueoverflow%2Fp%2F4714470.html","title":null},"content":[{"type":"text","text":"Mysql Join語法以及性能優化"}]}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":4,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https://link.jianshu.com?t=http%3A%2F%2Fblog.csdn.net%2Ftonyxf121%2Farticle%2Fdetails%2F7796657","title":null},"content":[{"type":"text","text":"mysql join的實現原理及優化思路"}]}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":5,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https://link.jianshu.com?t=https%3A%2F%2Fstackoverflow.com%2Fquestions%2F44917%2Fexplicit-vs-implicit-sql-joins","title":null},"content":[{"type":"text","text":"Explicit vs implicit SQL joins"}]}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https://link.jianshu.com?t=https%3A%2F%2Fblogs.technet.microsoft.com%2Fwardpond%2F2008%2F09%2F13%2Fdeprecation-of-old-style-join-syntax-only-a-partial-thing%2F","title":null},"content":[{"type":"text","text":"Deprecation of \"Old Style\" JOIN Syntax: Only A Partial Thing"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章