建議先看看開發人員MySQL調優-實戰篇0sql
準備數據腳本函數
DROP TABLE IF EXISTS `tb_tmp_vote`; CREATE TEMPORARY TABLE `tb_tmp_vote` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `user_id` varchar(20) NOT NULL DEFAULT '' COMMENT '用戶Id', `vote_num` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '投票數', `group_id` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '用戶組id 0-未激活用戶 1-普通用戶 2-vip用戶 3-管理員用戶', `status` tinyint(2) unsigned NOT NULL DEFAULT '1' COMMENT '狀態 1-正常 2-已刪除', `create_time` date DEFAULT '0000-00-00 00:00:00' COMMENT '建立時間', PRIMARY KEY (`id`) )COMMENT='投票記錄表'; DROP TABLE IF EXISTS `tb_vote`; CREATE TABLE `tb_vote` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `user_id` varchar(20) NOT NULL DEFAULT '' COMMENT '用戶Id', `vote_num` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '投票數', `group_id` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '用戶組id 0-未激活用戶 1-普通用戶 2-vip用戶 3-管理員用戶', `status` tinyint(2) unsigned NOT NULL DEFAULT '1' COMMENT '狀態 1-正常 2-已刪除', `create_time` date DEFAULT '0000-00-00 00:00:00' COMMENT '建立時間', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='投票記錄表'; -- 建立生成長度爲n的隨機字符串的函數 DELIMITER // -- 修改MySQL delimiter:'//' DROP FUNCTION IF EXISTS `rand_string` // SET NAMES utf8 // CREATE FUNCTION `rand_string` (n INT) RETURNS VARCHAR(255) CHARSET 'utf8' BEGIN DECLARE char_str varchar(100) DEFAULT 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; DECLARE return_str varchar(255) DEFAULT ''; DECLARE i INT DEFAULT 0; WHILE i < n DO SET return_str = concat(return_str, substring(char_str, FLOOR(1 + RAND()*62), 1)); SET i = i+1; END WHILE; RETURN return_str; END // -- 建立插入數據的存儲過程 DROP PROCEDURE IF EXISTS `add_tb_vote_memory` // CREATE PROCEDURE `add_tb_vote_memory`(IN n INT) BEGIN DECLARE i INT DEFAULT 1; DECLARE vote_num INT DEFAULT 0; DECLARE group_id INT DEFAULT 0; DECLARE status TINYINT DEFAULT 1; WHILE i < n DO SET vote_num = FLOOR(1 + RAND() * 10000); SET group_id = FLOOR(0 + RAND()*3); SET status = FLOOR(1 + RAND()*2); INSERT INTO `tb_tmp_vote` VALUES (NULL, rand_string(20), vote_num, group_id, status, NOW()); SET i = i + 1; END WHILE; END // DELIMITER ; -- 改回默認的 MySQL delimiter:';' call add_tb_vote_memory(500000); INSERT INTO tb_vote SELECT * FROM `tb_tmp_vote`;
需求:你的項目經理說要實現一個查詢某一天投票數超過某一個值的用戶,並按用戶ID升序排列學習
#在這裏傳遞過來的值寫成了固定值,代碼裏面用綁定變量(佔位符) select user_id,vote_num from tb_vote where create_time = '2018-03-03' and vote_num > 9000 order by user_id;
查看執行計劃優化
一眼看過去,type=ALL,extra中出現了Using filesort,這兩個是須要去掉的spa
看看sql語句中使用到的字段,先在create_time,vote_num,user_id上建立索引.net
create index idx_tb_vote_c_v_u on tb_vote(create_time,vote_num,user_id);
type=ALL變成了type=range,爲何喃?由於雖然走了索引掃描,可是條件有一個vote_num > 9000blog
雖然type=ALL優化掉了,可是Using filesort還在,爲何喃?索引雖然是有序的,可是若是隻按照user_id排序的話,MySQL對整個結果集的user_id,vote_num的順序與索引中默認的順序不一致,致使MySQL必須從新排序。我想能夠有兩個方法來解決整個問題排序
1.在order by 中添加vote_num字段索引
select user_id,vote_num from tb_vote where create_time = '2018-03-03' and vote_num > 9000 order by vote_num,user_id;
2.再建立一個索引ip
create index idx_tb_vote_c_u on tb_vote(create_time,user_id);
比較這兩種方案:
按照前面說的type=ref優於type=range,但讀取的rows的值ref卻比range多不少,由於MySQL會將全部create_time='2018-03-03'的數據行找出來比較是否vote_num>9000,因此最終ref還會比range慢。可是第一種方案和需求有一點變化,這種時候就須要和項目經理溝通,項目經理再和客戶溝通,若是對業務沒什麼影響,就能夠採起第一種方案
到這裏該SQL的問題算是解決了,實際生產中絕對沒有這麼簡單,由於一個表的字段可能會很是多,好比電信的詳單,有幾十個字段,可能涉及到的列有十多個,如何建立索引將會是一個很是複雜的問題,最好和DBA一塊兒商量着來(通常大系統都會有DBA按天監控一些重要表的DDL操做,按期收集表的統計信息,固化執行計劃等)
注意在寫SQL的時候,若是是字符類型的列,必定要加上單引號,不然生成執行計劃的時候會出問題,你認爲它會用到索引,它就不會讓你如願以償
根據上面的表造一份user數據
create table tb_v_user select user_id,left(user_id,5) as user_name,floor(40+rand()*(20)) as age,floor(1+rand()*2) as gendor from tb_vote;
這樣取值的話能夠user_name就會有重複,執行下面的語句拿到一些重複的user_name
select user_name,count(*) from tb_v_user group by user_name having count(*)>5;
user_name count(*) 1F7sJ 6 2fXTq 6
刪除以前建立的索引,方便從頭開始分析
#使用了刪除索引的兩種寫法 alter table tb_vote drop index idx_tb_vote_c_v_u; drop index idx_tb_vote_c_u on tb_vote;
需求1:查詢出user_name爲‘1F7sJ’的投票結果
select a.user_name,a.user_id,b.vote_num from tb_v_user a join tb_vote b on a.user_id = b.user_id where a.user_name = '1F7sJ';
如今兩張表在關聯的字段上面都沒有索引,MySQL首先加載tv_vote表,也就是關聯查詢右邊的表,意思就是先拿到tb_vote的全部數據和tb_v_user關聯,再判斷是否是1F7sJ用戶,兩張表都使用了ALL(全表掃描),而後在內存中對join。接下來嘗試在b表上建立索引。
create index idx_tb_vote_user_id on tb_vote(user_id);
能夠看到b表type=ref了,這結果很是好。MySQL估算出來b錶針對每一個a表的關聯只會撈一條數據出來。
咱們如今是在b表上建立的索引,若是剛纔是在a表上建立索引會是什麼效果喃?
drop index idx_tb_vote_user_id on tb_vote; create index idx_tb_v_user_user_id on tb_v_user(user_id);
如今a\b兩張表的加載順序變了,a表的type=ref。能夠推斷出MySQL針對一樣的SQL,本身會作優化,並不會死板的認爲必定先加載關聯查詢右邊的表。
能夠設想另一種狀況,若是兩張表關聯,一張表很小,另一張表相對要大不少,按照上面的處理會是什麼樣的效果喃?有興趣的同窗能夠試試。按照上面學習的理論,我推測小表上建索引是沒有用的,小表必定會先加載做爲驅動表。
再來將兩張表的user_id都加上索引試試
create index idx_tb_vote_user_id on tb_vote(user_id);
然而並無什麼變化,爲何喃?由於做爲驅動的a表,要找到符合條件的‘1F7sJ’,依然須要全表掃描
那麼再到a表的user_name上建立一個索引
create index idx_tb_v_user_user_name on tb_v_user(user_name);
這個效果就很好了,全部type=ref,並且估算出來須要撈的數據總共才7行,很是快。
此時若是需求變了,還要對結果作一些排序怎麼辦喃,能夠參考單表優化的經驗試試
總結一下表關聯的狀況:找到驅動表,減小驅動表須要關聯數據行的代價,被關聯表的關聯字段必定要有索引