對於單條語句,explain看下key,加個索引mysql
多個條件,加複合索引算法
where a = ? order by b 加(a,b)的複合索引sql
上面都是比較基本的,這篇咱們分析一些複雜的狀況——join的算法數據庫
以下兩張表作join併發
10w 100w tb R tb S r1 s1 r2 s2 r3 s3 ... ... rN sN
For each row r in R do For each row s in S do If r and s satisfy the join condition Then output the tuple <r,s>
掃描成本 O(Rn*Sn),也就是笛卡兒積oracle
For each row r in R do lookup in S index if found s == row Then output the tuple<r,s>
掃描成本 O(Rn)oop
優化器傾向於使用小表作驅動表,內表上建立索引便可性能
select * from a,b where a.x=b.y優化
a中的每條記錄,去b上面的index(y)中去找這個x的記錄,a表和b表,哪一個是R,哪一個是S線程
R:驅動表(外表) S:內表
對於inner join,a b 和 b a join出來結果同樣,用的索引來查詢,100w和1000w掃描成本都同樣,都是外表的行數,因此只要驅動表越小,優化器就傾向於用它作驅動表
對於left join,左表要全表掃描全部記錄,因此必定是驅動表
好比,一列10w行,另外一列100w行,優化器會喜歡把10w的這一列作外表,而後在100w的列上建索引,外表只要掃描10w次,假設100w記錄索引B+ tree高度是3,那一共就是10w * 3次io操做,反過來,就是100w * 3次,因此,mysql只要兩張表關聯,很是建議兩張表上必定要有索引,索引應該建立在S表上,也就是大表,若是索引加反了,這時候100w的表就成了驅動表
可是線上確定不會直接兩張表關聯,where後面還有不少過濾條件,優化器會把這些條件考慮進去,過濾掉這些條件,看哪張表示小表就是驅動表,可是索引有傾斜,優化器在選擇上可能會出錯
用來優化simle nested_loop join,減小內部表的掃描次數
For each tuple r in R do store used columns as p from R in join buffer For each tuple s in S do if p and s satisfy the join condition Then output the tuple <p,s>
加一個內存,join_buffer_size變量,空間換時間,這個變量決定了Join Buffer的大小
Join Buffer可被用於聯接是ALL,index,range的類型
Join Buffer只存儲須要進行查詢操做的相關列數據(要關聯的列),而不是整行的記錄(千萬要記住),因此總的來講仍是蠻高效的,
掃描成本呢?和simple同樣
Table R join buffer Table S
將多條R中的記錄一會兒放到join buffer中,join buffer 一次性和Table S中每條記錄進行比較,這樣來減小內表的掃描次數,假設join buffer足夠大,大到能把驅動表中全部記錄cache起來,那這樣就只要掃一次內表
舉例:
A B 1 1 2 2 3 3 4 外 內 SNLP BNLP 外表掃描次數 1 1 內表掃描次數 3 1 比較次數 12 12
綜上:比較次數是節省不下來的
若是是百萬級別,mysql勉強能夠跑出來結果,調優的話,不談索引,咱們就要調大join_buffer_size這個參數,默認256k,若是這個列是8個字節,256k顯然存不下太多記錄,這個參數能夠調很大,可是不要過度了,否則機器內存不夠,數據庫掛了就很差了,通常來講1g最大了
join_buffer_size是私有的,每一個線程能夠用的內存大小
網上有個特別錯誤的觀點,mysql加速join查詢,加大join_buffer_size。這個是兩張表關聯沒有索引用這個方法纔有用,你有了索引,再怎麼加大這個參數也沒用
自己也不太建議用這個算法,除非某些特殊的查詢用不到索引,或者當時沒建索引,這是個臨時處理方法,比較次數是笛卡兒積啊 個人天那,如何把這個比較次數降下來呢?
tips:
一、不知道哪一個表是驅動表,那就簡單點,兩個關聯的表的相關列都加索引,讓優化器本身選擇
二、oracle中百萬級別的表進行關聯,用index nested_loop join,千萬級以上用hash join這個說法怎麼看?心法口訣是什麼
三、join若是有索引,爲何千萬級別的join不行?這句話是錯的,是能夠的,只是速度很慢罷了,主要就是由於要回表
select max(l_extendedprice) from orders,lineitem where o_orderdate betweent '1995-01-01' and '1995-01-31' and l_orderkey=o_orderkey;
join的列不是主鍵,這種類型的查詢就是基於索引的,join不太適用的場景
看一個問題
(root@localhost) [dbt3]> explain select * from orders where o_orderdate > '1997-01-01'; +----+-------------+--------+------------+------+---------------+------+---------+------+---------+----------+-------------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+-------------+--------+------------+------+---------------+------+---------+------+---------+----------+-------------+ | 1 | SIMPLE | orders | NULL | ALL | i_o_orderdate | NULL | NULL | NULL | 1483643 | 50.00 | Using where | +----+-------------+--------+------------+------+---------------+------+---------+------+---------+----------+-------------+ 1 row in set, 1 warning (0.00 sec) (root@localhost) [dbt3]> explain select * from orders where o_orderdate > '1999-01-01'; +----+-------------+--------+------------+-------+---------------+---------------+---------+------+------+----------+-----------------------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+-------------+--------+------------+-------+---------------+---------------+---------+------+------+----------+-----------------------+ | 1 | SIMPLE | orders | NULL | range | i_o_orderdate | i_o_orderdate | 4 | NULL | 1 | 100.00 | Using index condition | +----+-------------+--------+------------+-------+---------------+---------------+---------+------+------+----------+-----------------------+ 1 row in set, 1 warning (0.00 sec)
爲何第一條sql沒走索引第二條走了呢?
這不是優化器不穩定,這個索引是一個二級索引,如今select * 因此要回表,回表對於咱們這個sql來講,假設經過索引定位到回表的記錄一共是50w條(表一共是150w條記錄,每條記錄大小是100字節),那就要回表50w次,大約是50w次io(看B+ tree 高度,這裏假設高度就是1)
若是不走索引,直接掃150w行主鍵一共須要150w/(16k/100)次io(每行大小100字節,一個頁就是16k/100條記錄,150w除以這個值,就是一共的記錄數),到這裏看基本上就是1w次io,這就是優化器沒走索引的緣由,並且,回表用的io是隨機的,掃主鍵用的是順序io,後者比前者最起碼快十倍
另外,這個選擇確定是基於cost的,但不要追究cost,官方沒有說明cost是怎麼計算的,經過源碼能夠看出一點,可是5.7版本又不同了
那針對這種狀況咱們應該如何優化呢?——MRR(5.7以後纔有,和oracle同樣)
(root@localhost) [dbt3]> explain select /* +MRR(orders) */ * from orders where o_orderdate > '1998-01-01'; +----+-------------+--------+------------+-------+---------------+---------------+---------+------+--------+----------+----------------------------------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+-------------+--------+------------+-------+---------------+---------------+---------+------+--------+----------+----------------------------------+ | 1 | SIMPLE | orders | NULL | range | i_o_orderdate | i_o_orderdate | 4 | NULL | 317654 | 100.00 | Using index condition; Using MRR | +----+-------------+--------+------------+-------+---------------+---------------+---------+------+--------+----------+----------------------------------+ 1 row in set, 1 warning (0.22 sec)
MRR:Multi-Range-Read:多範圍的read,空間換時間,解決回表的性能問題
隨機io回表,不能保證key和pk都是順序的,對於二級索引來講它的key是排序了,可是不能保證裏面保存的pk也排序了
因此如今弄了個內存,把這個二級索引裏面的pk都放進去,等放不下了,去sort一把,而後再去回表,這時候io就比較順序了,這樣經過隨機轉順序作了個優化
把外表中的數據放到一個內存中,放進去以後不是直接去和內表比較,對內存中的列建立了一個hash表,而後再掃描內表,內表中的每條記錄去探測hash表,一般查一個記錄只要探測一次
A B 1 1 2 2 3 3 4
外表掃1次,內表掃1次,比較次數是4(S表去hash表中探測),走索引的話也是1 1 4,看起來哈希和索引看起來成本同樣,hash不用建立索引,不用回表
hash join 就不存在回表的問題,還能夠用來作併發,若是join要回表,用基於索引的方式作join算法效率比較差,對於oracle的話,hash join就會好很是多
hash join有個缺點是隻支持等值的查詢,A.x=B.y >= 這種就歇菜了,不過一般join也是等值查詢
據說8.0會支持
Ⅲ、batched key access join(5.6開始支持)
用來解決若是關聯的列是二級索引,對要回表的列先cache起來,排序,再回表,性能會比較好一點
bka join調的是mrr接口,可是如今這個算法有bug,默認是永遠不啓動的,要啓用得寫hint,這是mysql如今比較大的問題
上面講了一堆mysql的不是,join算法的痤,上面這些多少w行join,線上業務會用到這些嗎?咱們線上都是簡單查詢,這些都是很複雜的查詢了,233333
錯誤作法:有人說業務層作join,這樣不會比在數據庫層作快