一:場景
我代碼裏需要在用戶登錄時將所有用戶相關的用戶,角色,部門,崗位,權限(其中權限放在菜單中,每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語句編譯成可執行的機器語言,只用用的時候纔去查詢數據。