疑問
表:sl_sales_bill_head 訂單抬頭表 數據行:8474
表:sl_sales_bill 訂單明細 數據行:8839
字段:SALES_BILL_NO 訂單號
情況1
沒有任何索引 sql語句
EXPLAIN select * from sl_sales_bill_copy1 lb join sl_sales_bill_head_copy1 lh on lh.SALES_BILL_NO = lb.SALES_BILL_NO
lh為主表 lb為子表
改一下sql語句
EXPLAIN select * from sl_sales_bill_head_copy1 lh join sl_sales_bill_copy1 lb on lh.SALES_BILL_NO = lb.SALES_BILL_NO
疑問:為什么sql語句無論主表是哪個 lh都先執行
情況2
sl_sales_bill_head_copy1 的SALES_BILL_NO為主鍵索引
ALTER TABLE `sl_sales_bill_head_copy1` ADD PRIMARY KEY (`SALES_BILL_NO`) ;
sql語句1:
EXPLAIN select * from sl_sales_bill_head_copy1 lh join sl_sales_bill_copy1 lb on lh.SALES_BILL_NO = lb.SALES_BILL_NO
sql語句2:
EXPLAIN select * from sl_sales_bill_copy1 lb join sl_sales_bill_head_copy1 lh on lh.SALES_BILL_NO = lb.SALES_BILL_NO
疑問:為什么無論怎么通過sql語句改變主表 始終是lb先執行
情況3
lh.SALES_BILL_NO創建索引
ALTER TABLE `sl_sales_bill_head_copy1` ADD PRIMARY KEY (`SALES_BILL_NO`) ;
EXPLAIN select * from sl_sales_bill_copy1 lb join sl_sales_bill_head_copy1 lh on lh.SALES_BILL_NO = lb.SALES_BILL_NO where lb.SALES_BILL_NO='HP20190410000099'
EXPLAIN select * from sl_sales_bill_copy1 lb join sl_sales_bill_head_copy1 lh on lh.SALES_BILL_NO = lb.SALES_BILL_NO where lh.SALES_BILL_NO='HP20190410000099'
都會正常走索引 同時也是lh先執行
如果改為lb的其他字段
EXPLAIN select * from sl_sales_bill_copy1 lb join sl_sales_bill_head_copy1 lh on lh.SALES_BILL_NO = lb.SALES_BILL_NO where lb.id='0001c3fd44454a65a4122b259283f979'
無索引情況
ID有索引情況
變成了lb先執行
情況4
sl_sales_bill_head_copy1 的SALES_BILL_NO為主鍵索引
ALTER TABLE `sl_sales_bill_head_copy1` ADD PRIMARY KEY (`SALES_BILL_NO`) ;
SQL語句
EXPLAIN select * from sl_sales_bill_copy1 lb join sl_sales_bill_head_copy1 lh on lh.SALES_BILL_NO = lb.SALES_BILL_NO
sql語句
EXPLAIN select * from sl_sales_bill_copy1 lb left join sl_sales_bill_head_copy1 lh on lh.SALES_BILL_NO = lb.SALES_BILL_NO
疑問:為什么left join沒有走索引了
Join匹配原理
說明
mysql只支持一種算法Nested-Loop Join(嵌套循環鏈接),不像其他商業數據庫可以支持哈希鏈接和合並連接,不過MySQL的Nested-Loop Join(嵌套循環鏈接)
Simple Nested-Loop
圖片來源:InsideMySQ
R表為驅動表每掃描一行去S表找匹配的數據 這種算法是最耗時的 總掃描次數為驅動表行數*非驅動表行數
比如R表有200表數據 S表有100條 總掃描次數為200*100 可以看出這種算法效率最低
Index Nested-Loop Join
R表為驅動表每掃描一行 根據匹配條件通過索引去S表找 這種算法需要非驅動表有索引 一般我們on r.sid=s.id 索引時給非驅動表用的
比較高效
Block Nested-Loop Join
mysql 5.5對Simple Nested-Loop的優化 先掃描驅動表一定量(根據join_buffer_size來定) 放到join_buffer 然后遍歷非驅動表 非驅動表每次匹配join_buffer里面的數據 減少掃描次數
比如我們的join_buffer最多只能存放r表3條數據 遍歷R表 每遍歷3條將數據放到join_buffer然后 然后再去遍歷一次s表 每s表遍歷一行跟join_buffer里面的數據進行匹配 遍歷完成釋放join_buffer 重復上面操作
在MySQL當中,我們可以通過參數join_buffer_size來設置join buffer的值,然后再進行操作。默認情況下join_buffer_size=256K
解決疑惑
情況1
lh數據條數8274 lb數據條數8721
疑問:為什么驅動表都是lh表
解答:mysqlsql優化器 默認會將小表作為驅動表
好處:
Block Nested-Loop Join算法
比如lh有4條數據 lb數據條數6 join_buffer是只能存放2條數據
計算規則為(驅動表遍歷次數*驅動表行數)+(非驅動表遍歷次數*非驅動表行數)=總遍歷次數
我們將lb作為驅動表 掃描行數為(1*6)+(3*4)=18 總掃描行數
我們將lh作為驅動表 (1*4)+(2*6)=16 總掃描行數
可以發現小表作為驅動表掃描的行數更低
情況2:
lh數據條數8274 lb數據條數8721
疑問:為什么lh.SALES_BILL_NO為主鍵索引 驅動表始終是lb
解答:mysql優化器還是以小表為原則 如果大表關聯關系有索引而小表沒有則以有索引的表為驅動表
好處:
這里使用的Index Nested-Loop Join算法
如果使用lh驅動表 首先會遍歷8274次 每次去lb去找 因為關聯關系lb.SALES_BILL_NO沒有做索引 所以非驅動表lb也會全表掃描 總掃描次數就變成8274*8721
如果使用lb為驅動表會遍歷lb表每次通過SALES_BILL_NO去非驅動表lh找 因為lh做了索引 所以通過索引掃描一次就可以找到數據 總掃描次數:8274*1
情況3
lh數據條數8274 lb數據條數8721
疑問:為什么就lh.SALES_BILL_NO有主鍵索引 無論搜索條件是lb.SALES_BILL_NO還是lh.SALES_BILL_NO 都是lh先執行
解答:
因為on lb.SALES_BILL_NO=lh.SALES_BILL_NO where lb.SALES_BILL_NO='HP20190410000099'
這個時候雖然lb.SALES_BILL_NO沒有索引 但是關聯查找為lb.SALES_BILL_NO=lh.SALES_BILL_NO and lb.SALES_BILL_NO='HP20190410000099'
正常查找是lb全表掃描得到HP20190410000099然后去lh通過索引得到SALES_BILL_NO=lh.SALES_BILL_NO的數據
如果設置成lb.SALES_BILL_NO=lh.SALES_BILL_NO and lh.SALES_BILL_NO='HP20190410000099' 得到結果相同 以小表為驅動表原則sql優化器會優化為類似這樣的語句查找
EXPLAIN select * from sl_sales_bill_copy1 lb join sl_sales_bill_head_copy1 lh on lh.SALES_BILL_NO = lb.SALES_BILL_NO where lb.id='0001c3fd44454a65a4122b259283f979'
lh.SALES_BILL_NO 有索引 然后lb.id無論有無索引都是 lb為驅動表 因為lb.id已經縮小了數據范圍 小表原則 所以始終是lb為驅動表
情況4
因為left join相當於強制要求了lb為主表 雖然lh.SALES_BILL_NO有索引 但是join索引主要是給非驅動表用的 所以出現以上情況
join優化原則
盡量減少驅動表條數 非驅動表關聯條件建立索引
雖然大部分會經過mysql優化器自動優化,復雜sql最好通過執行計划查看一下 是否有性能瓶頸
注意不要通過left join 影響sql優化器 將大表作為驅動表
記住join 索引只有在非驅動表上面才能體現作用