做者本人從事it 行業多年,沒進過什麼大企業。摸爬滾打多年,多少有些經驗。抗的了服務器,寫的了前端。前端
熟悉的語言:PHP,JavaScript,Python 等。mysql
熟悉的框架:Laravel,Django,Scrapy,React,Vue 等。sql
數據庫:Mysql,Redis,Es等。數據庫
早些年其實就想寫些東西,但又無處下手。直到這幾年,帶領團隊,給組員培訓,分享。纔想到爲何不把要培訓的內容,分享的知識寫成博文呢,而不是簡單幾頁PPT。bash
除了這篇 "Mysql 百問系列" 後續還會有更多系列跟你們見面吧。文中知識點都是查閱資料,親自實踐得出來的結論,若是理解有偏誤,請不吝賜教。服務器
每同樣 技術 或者 功能 都是爲了解決某一個問題而誕生的。 搞清楚 爲何 要這麼作 比 搞清楚 具體怎麼作 要重要。 因此我都會以問題開頭,但願你們能先想一想本身是否知道這些問題的答案,而後對比我給出的答案,若是不一致思考下到底誰的答案有問題,動手證實。 這種經過攻克一個個問題從而提高本身的能力,就是我但願經過一篇篇文章給你們帶來的價值。app
每篇文章 我會聚焦於一兩個點,儘可能不擴散太多。固然技術類文章沒法避免一個知識點牽連另外一個知識點。這種碰到遇到不懂知識點的時候我建議你們知道有這麼個東西,把精力集中於當前問題,而不要太擴散。固然後續我也會根據反饋,進行填坑。框架
CREATE TABLE `school` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE `student` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`school_id` int(11) DEFAULT NULL,
`name` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `idx_school` (`school_id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
delimiter;;
CREATE PROCEDURE idata ( ) BEGIN
DECLARE
i INT;
SET i = 1;
WHILE
( i <= 1000 ) DO
INSERT INTO school
VALUES
( i, 'school' );
SET i = i + 1;
END WHILE;
END;;
delimiter;
CALL idata ( );
drop procedure idata;
delimiter;;
CREATE PROCEDURE idata ( ) BEGIN
DECLARE
i INT;
SET i = 1;
WHILE
( i <= 1000 ) DO
INSERT INTO student
VALUES
( i, i%2+1, 'stu');
SET i = i + 1;
END WHILE;
END;;
delimiter;
CALL idata ( );複製代碼
如今有兩張表,一張 shool表,一張 student表。 各插入1000條數據oop
EXPLAIN select * from student STRAIGHT_JOIN school on school.id = student.school_id複製代碼
(使用STRAIGHT_JOIN) 是防止Mysql 優化器自動優化,具體優化邏輯此處不展開,後續有機會詳聊。優化
執行結果:
能夠看到student 爲 驅動表, school 爲 被驅動表.
執行流程:
找到匹配的數據
,放入結果集中。 因爲school 的id 爲主鍵,因此使用主鍵索引可以迅速定位到那一行。上面使用的是join 也就是內鏈接
,若是換成Left join 或者 Right join 外鏈接有什麼區別呢?區別就在於上面第二個步驟中,內鏈接須要找到匹配的數據放到結果集中, 而外鏈接是無論有沒有找到數據,都須要把驅動表
的那行數據加入到結果集中。
那麼無論內鏈接仍是外鏈接, 這種拿到驅動表
數據 再去循環遍歷查詢被驅動表,而且被驅動表查詢過程當中使用到了索引
的方式稱爲 Index Nested-Loop Join
。(從英文其實也能夠看出意思了, Index 索引,Nested-loop 嵌套循環)
那麼若是 被驅動表上的搜索的字段沒有索引呢?
EXPLAIN select * from student STRAIGHT_JOIN school on school.name = student.name複製代碼
(雖然現實場景中不會存在 用學校名稱 字段關聯學生名稱這種狀況,這裏只是說明若是 學校的name 這個沒有索引的字段被拿來作關聯條件時的狀況)
能夠看到 school 由於沒法用到索引,因此必需要進行全表掃表。
那麼咱們猜想的
執行流程是:
這個過程 咱們能夠看到 須要掃描的行數爲 1000 * 1000 。 若是school 或者student 數量再大必定,1萬的學校,幾百萬的學生,那麼這個查詢的效率可想而知會很是低。
因此針對這種被驅動表
沒法使用索引
的join,Mysql 作出了優化,並非咱們上面所猜想的流程。
首先,mysql 引入了join buffer 的概念。這個join buffer的大小是能夠經過啓動參數或者系統變量join_buffer_size進行配置,默認大小是256K,最小能夠設置爲128字節。
因此真實的流程爲:
驅動表
student,取出1000行 放入join buffer。因此上面的情形是 school 中的數據,一行行的取出來 去與 join_buffer 中的數據作對比,查找到 符合條件的結果。那麼 須要掃描的行數是 school 的1000 行, 每一行又要與join_buffer 中的1000行進行對比。
這種狀況 須要掃描的行數爲 student 的1000行,用來放入join_buffer , 和 school 的1000行。也就是掃描了2000行。 單內存中進行判斷的次數是 1000 * 1000;
能夠看到經過引入join buffer 將 磁盤的掃描 變成了內存的判斷,以此來提高效率
若是驅動表
數據很大,致使join buffer 放不下呢? 例如 student 有 10000萬行,join buffer 每次只能放1000行。
那麼流程就會變爲:
1.掃描 student ,直到join buffer 放滿。
2.掃描 school ,取出其中一行, 與 join_buffer 中的數據作對比, 若是知足了join 的條件,那麼加入到結果集中。
這種按 一塊塊 進行讀取放入join buffer 並比較的方式 就是Block Nested-Loop Join
了。
能夠看到若是 join 的時候使用了Block Nested-Loop Join
,那麼它的執行效率與join buffer 的大小相關,join buffer 越大,分塊的次數越小, 效率越高。
固然充分利用join buffer 空間也是一種方式, 放入join buffer 空間的數據是 驅動表的 查詢字段 和 搜索字段。因此避免使用 *
,列清楚須要查詢的字段也有利於 join buffer 空間的利用。
固然最理想的優化方式是添加索引,把 Block Nested-Loop Join
轉爲 Index Nested-Loop Join
。
索引也不是隨便添加,若是業務場景中不多拿這個字段來進行搜索,單單爲了某個join 而添加索引也得不償失,還須要根據具體業務來考量。
根據join 過程當中 被驅動表
能不能用到索引 將查詢方式分爲 Index Nested-Loop Join
和 Block Nested-Loop Join
Index Nested-Loop Join
的效率高,能夠在業務中使用。Block Nested-Loop Join
雖然mysql 引入join buffer 來進行優化,可是效率仍是偏低,應該儘可能避免。ps: 我的認爲在實際業務場景中即便多個表格進行join 過程當中都使用到了Index Nested-Loop Join
的方式,可是因爲每次join 都是進行笛卡爾積的過程,若是其中一張或多張表隨着業務發展數據量增長,會致使 整個sql 查詢效率也不是很理想。 因此仍是儘可能避免多表join 的場景。最多容許2 到 3個表格進行join 。
喜歡,支持請關注個人我的公衆號