因為工作的項目涉及千萬級數據的單表內容的統計查詢。所以對於此sql的效率做了一個測試比較,並在測試過程中發現了索引相關的問題:
情況說明:
原始表單(記A表)為業務接口調用記錄表,每個用戶的每次調用接口都會產生一條記錄。一天產生一張表,單表規模可能達到千萬。
記錄的信息包括用戶(uuid),調用的接口(method),接口耗時時間(time_cost)等…,
要求:
統計出一天內(A表一天生成一張)A表中每個用戶(uuid)的每個接口(method)對應的調用的次數及最大和最小消耗時間(max_cost,min_cost)。
另A表建有一個method列的簡單索引
在未訪問A表之前,只知道總共有哪些接口(接口總共10個左右),不知道存在哪些用戶
思路:
1、 將uuid和function分組排列,一步到位直接出結果
2、 單獨對每個接口只按uuid排列,總共多少接口統計多少次
單表數據:單表600w數據,1w個uuid隨機,method 6個隨機,time_cost 0~1000內隨機
1、將uuid和function分組排列,一步到位直接出結果
select uuid, method, count(1) call_times,min(time_cost) min_cost,max(time_cost) max_cost from A_yyyymmdd group by uuid, method into outfile '/var/lib/mysql-files/a1.txt';
sql語句執行花費:11.51s
語句后面的 into outfile ‘/var/lib/mysql-files/a.txt’表示將語句執行結果導出到文件,具體導出的目錄權限可通過mysql安全控制變量secure_file_priv查看
通過show variables like '%secure%'可查看:
2、單獨對每個接口只按uuid排列,總共多少接口統計多少次
select uuid, count(1) call_times,min(time_cost) min_cost,max(time_cost) max_cost from A_yyyymmdd where method= ‘useradd’ group by uuid into outfile '/var/lib/mysql-files/a1.txt';
統計單個接口的花費:48.61s
對於此結果有點詫異,按照道理,group by 單列應該比group by 組合列效率更高
3、考慮是否where產生的影響,那么把where去掉看下,只觀察語句執行效率。
select uuid, count(1) call_times,min(time_cost) min_cost,max(time_cost) max_cost from A_yyyymmdd group by uuid into outfile '/var/lib/mysql-files/a1.txt';
花費:5.20s
group by只消耗了5.20s,但總的卻是21.09左右,也就是說在where子句的時候花費更多的事件。
4、留where子句,去掉group by觀察語句執行效率:
select uuid from A_yyyymmdd where method= ‘useradd‘ into outfile '/var/lib/mysql-files/a1.txt';
耗時:45.5s
果然where子句消耗的時間太高。
5、查看where子句執行計划,看索引是否生效
explain select uuid from A_yyyymmdd where method= 'useradd' into outfile '/var/lib/mysql-files/a7.txt';
發現已經有用上索引,用上索引花費45s,有點疑惑,查看group by uuid的語句(花費5.2s)是否用上索引
explain select uuid, count(1) call_times,min(time_cost) min_cost,max(time_cost) max_cost from A_yyyymmdd group by uuid into outfile '/var/lib/mysql-files/a1.txt';
發現group by uuid的語句是全表掃描,沒有使用索引,反而只需要花費5.2s。
用到索引的卻要花費45.5s。
6、對where語句忽略索引,執行看看執行花費:
explain select uuid from A_yyyymmdd ignore index(aaa_method_call_times_record_method_IDX) where method= ‘useradd’ into outfile '/var/lib/mysql-files/a1.txt';
where語句不使用索引,全表掃描,執行語句:
select uuid from A_yyyymmdd ignore index(aaa_method_call_times_record_method_IDX) where method= 'useradd' into outfile '/var/lib/mysql-files/a1.txt';
耗時:3.69s
同樣一個where語句 使用索引的45.5s,忽略索引的3.69s。
7、通過網上了解到這樣結果是因為索引建立的不合適,涉及到數據庫引擎的索引原理:
A表索引是建立在method上的,在此場景中,表中method數據存在大量重復的。
我默認數據庫引擎是InnoDB
數據庫中聚集索引只有一個,默認主鍵。其他用戶創建的索引都是非聚集索引(二級索引)。非聚集索引存儲了對主鍵的引用,即通過索引確定葉子節點之后,還需要再次根據主鍵去查詢數據。(所以會查詢兩次)。如果非聚集索引重復率高(即一個同樣的值有多個主鍵),那么條件會確定近乎一半或1/3,或1/5的主鍵值,然后在一個一個去聚集索引查詢。這樣開銷會大,
如本次場景中數據:method 為6個隨機,總共600w數據。
當method為索引時,每個method值對應約100w主鍵,
索引查詢過程首先查詢非聚集索引,從中查到約100w的主鍵引用,然后根據主鍵一個一個去聚集索引中查詢數據,即查詢聚集索引要查詢約100w次。而全表掃描,只需一次掃描全部即可得出結果。此場景中索引的開銷已經遠遠大於全表掃描的開銷了。
如果索引后開銷低於全表掃描,那么使用索引。如果索引導致了比全表掃描更糟糕的結果,那么還不如全表掃描。