從零開始java數據庫SQL優化(二):多個LEFT JOIN的SQL優化

一:場景

  我代碼裏需要在用戶登錄時將所有用戶相關的用戶,角色,部門,崗位,權限(其中權限放在菜單中,每2張表有一張關聯表),不多說直接上SQL

SELECT 
			a.fk_user_id AS "fk_user_id",
			a.user_realname AS "user_realname",
			a.user_name AS "user_name",
			a.user_type AS "user_type",
			a.sex AS "sex",
			a.phone AS "phone",
			a.password AS "password",
			a.user_addr AS "user_addr",
			a.avater AS "avater",
			a.status AS "status",
			
			
			
			fr.role_id as role_id,
			fr.role_code as role_code,
			fr.role_name as role_name,
			fr.role_range as role_range,
			
			
			fd.dept_id as dept_id,
			fd.dept_name as dept_name,
			fd.parent_id     as dept_parent,
			fd.parent_ids     as dept_parents,
			fd.company_name as company_name, 
			 
			fp.post_id as post_id,
			fp.post_name as post_name,
			fp.post_split as post_split,
			
			m.perms as perms
			
			FROM fk_user a
		 	LEFT JOIN fk_user_role fkur ON fkur.user_id = a.fk_user_id
			LEFT JOIN fk_role fr ON fr.role_id = fkur.role_id
			LEFT JOIN fk_role_menu rm ON rm.role_id = fr.role_id
			LEFT JOIN fk_menu m ON m.menu_id = rm.menu_id
	
			LEFT JOIN fk_dept fd ON fd.dept_id = a.dept_id
			LEFT JOIN fk_user_post fup ON fup.user_id = a.fk_user_id
			LEFT JOIN fk_post fp ON fp.post_id = fup.post_id 
WHERE a.user_name ='admin'

耗時:

二:優化索引

 1. 用戶表優化

  (1)用戶表添加唯一索引

                    由於用戶名在程序中控制爲唯一,因此用戶名創建唯一索引。(TIPS: 由於我程序使用的框架原因,登錄先更具用戶

                     名查出用戶信息再比對密碼。如果是用戶名和密碼一起查詢,則可以在密碼字段添加一個普通索引。)

                  

  (2)查詢語句添加limit 1 

                 至於原因,理由放在這篇從零開始java數據庫SQL優化(番外):SQL執行性能分析

                     

 (3)優化結果:

                 用戶表 Type級別已經達到效率最高了。

              查詢一下計劃:   

 

2. 優化用戶角色表

  上圖可以看出來fkur(即用戶角色表)的類型還是ALL,這裏我們對這張表做一個優化。

(1)用戶角色添加組合索引

 

 (2)優化結果

        ,數據較少效果不明顯,但是由查詢計劃看到Type

3.優化角色菜單表

(1)添加組合索引

      

(2)優化結果 

              效果不明顯,但是看一下查詢計劃的Type

 

 

4.優化用戶部門表

 (1)添加索引

           

 (2)優化結果

              看一下查詢計劃,它的類型還是ALL(這裏有個坑,如果數據過少,查詢會自動判斷走索引還是全表,因此表中數據要 > 2*查詢出的數據索引纔能有效果)

 5.總結

  到這裏索引的添加就完成了,主要就是添加唯一索引和普通索引的問題。

 三:多LEFT JOIN的SQL優化

  目前,在阿里Java開發的規範手冊上明確提到Left join表,最多不得超過3個。很尬尷的是在我們本例中後臺採用的是Security的安全權限框架。至少需要將,用戶、角色以及權限查詢出來放入緩存以做權限檢驗。其次,由於數據權限,還需要將用戶的部門,崗位查詢出來,基本上表是沒法少的。因此,纔出現上面的優先添加索引的這個方法。但是這裏我們提供一些優化多個Left Join的思路吧。

(1)Left Join查詢的方式

  顧名思義,Left join當然是以左邊表作爲查詢條件,有以下幾個特點:

 

  第一點:左表爲基準,右表做爲關聯,查不到返回NULL。比如說 User Left  Join   Dept。User有10條數據,Dept只有5條數據

                 那麼查詢出來的一定10條數據。那麼這裏查詢的開銷是多少?最接近User * Dept次查詢(當然這個前提是沒有索引)

                 接近2張表的笛卡爾開銷。Left Join越多,笛卡爾開銷越大。

 

  第二點: Mysql中的Join的查詢原理是一種叫做nested loop join的算法。這種算法是以驅動表作爲循環依據,一條一條傳入下一

                  個表作爲查詢。

 

第三點: ALL式查詢,我們也是以這個SQL作爲優化示例。在最初的 查詢計劃中所有關聯都是採用ALL的方式,也就是沒有添加

                索引情況下。當然再添加完索引查詢計劃就被優化了。

 

(2) 優化建議

   針對上述的3點我們提出一些優化建議:

  第一點:這個沒辦法,只能說盡量減少多個Left Join。

  第二點:在業務場景允許的情況下,將小數據的表作爲左表關聯。

  第三點:首先,添加必要的索引。其次,如果出現Join表的查詢條件把查詢條件放入Join中。這點的原理實際上基於第二點的

                 查詢算法,但是我把放入這裏是因爲它可以較低查詢級別。舉個簡單的例子,比如  ...Left Join  LEFT JOIN fk_menu m

                ON m.menu_id = rm.menu_id,這個地方如果有隻要查詢出菜單類型爲按鈕的。不要在這個SQL最後添加

               m.menu_type='BUTTON',而是在Left Join(select  *  from  fk_menu where  mene_type = 'BUTTON' ) m  on  ....。

 

四:預編譯SQL-視圖

  好麼,最好放出最有效的方法,預編譯。使用臨時表或者視圖或者存儲過程來存儲SQL,代碼直接調用即可。

1.創建視圖

CREATE VIEW  v_login_user
 AS

 SELECT 
			a.fk_user_id AS "fk_user_id",
			a.user_realname AS "user_realname",
			a.user_name AS "user_name",
			a.user_type AS "user_type",
			a.sex AS "sex",
			a.phone AS "phone",
			a.password AS "password",
			a.user_addr AS "user_addr",
			a.avater AS "avater",
			a.status AS "status",
			
			
			
			fr.role_id as role_id,
			fr.role_code as role_code,
			fr.role_name as role_name,
			fr.role_range as role_range,
			
			
			fd.dept_id as dept_id,
			fd.dept_name as dept_name,
			fd.parent_id     as dept_parent,
			fd.parent_ids     as dept_parents,
			fd.company_name as company_name, 
			 
			fp.post_id as post_id,
			fp.post_name as post_name,
			fp.post_split as post_split,
			
			m.perms as perms
			
			FROM fk_user a
		 	LEFT JOIN fk_user_role fkur ON fkur.user_id = a.fk_user_id
			LEFT JOIN fk_role fr ON fr.role_id = fkur.role_id
			LEFT JOIN fk_role_menu rm ON rm.role_id = fr.role_id
			LEFT JOIN fk_menu m ON m.menu_id = rm.menu_id
	
			LEFT JOIN fk_dept fd ON fd.dept_id = a.dept_id
			LEFT JOIN fk_user_post fup ON fup.user_id = a.fk_user_id
			LEFT JOIN fk_post fp ON fp.post_id = fup.post_id 

  2.調用視圖

  視圖的本質就是一張臨時表,

SELECT * FROM  v_login_user  WHERE user_name = 'admin'

3.視圖的更新 

  比較感人的是,表數據的更新並不會導致視圖的更新。比如說,我將User的username改爲Admin123.視圖查詢UserName=Adm

in123並沒有 。需要我們手動更新:更新語句如同更新一個表的行一樣。

4.視圖新增和刪除

  從上面可以看出,新增和刪除需要我們手動來嘍,當然同新增/刪除一張表一致。

5.說明視圖的使用場景

  視圖的創建就是將當前數據緩存到一張臨時表中,然後查詢,一般不適用可以修改的數據,因爲修改要改表,也要改視圖很麻煩。那麼視圖有什麼作用呢?作用就是對前一天或者幾天數據做統計。很明顯本例不適用。

五:創建存儲過程

  存儲過程的SQL比較複雜,這裏就不上傳了。其實在本例中也不適用。Mysql的視圖是一張臨時表,它是將查詢結果放入其中,當被查詢的表出現改動,視圖臨時表數據不會改動。而Mysql的存儲過程則是預先將SQL語句編譯成可執行的機器語言,只用用的時候纔去查詢數據。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章