一:基礎數據准備
DROP TABLE IF EXISTS `tbl_user`; CREATE TABLE `tbl_user` ( `id` int(11) NOT NULL AUTO_INCREMENT, `username` varchar(255) DEFAULT NULL, `email` varchar(20) DEFAULT NULL, `age` tinyint(4) DEFAULT NULL, `type` int(11) DEFAULT NULL, `create_time` datetime DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=10 DEFAULT CHARSET=utf8; INSERT INTO `tbl_user` VALUES ('1', 'admin', 'admin@126.com', '18', '1', '2018-07-09 11:08:57'), ('2', 'mengday', 'mengday@163.com', '31', '2', '2018-07-09 11:09:00'), ('3', 'mengdee', 'mengdee@163.com', '20', '2', '2018-07-09 11:09:04'), ('4', 'root', 'root@163.com', '31', '1', '2018-07-09 14:36:19'), ('5', 'zhangsan', 'zhangsan@126.com', '20', '1', '2018-07-09 14:37:28'), ('6', 'lisi', 'lisi@gmail.com', '20', '1', '2018-07-09 14:37:31'), ('7', 'wangwu', 'wangwu@163.com', '18', '1', '2018-07-09 14:37:34'), ('8', 'zhaoliu', 'zhaoliu@163.com', '22', '1', '2018-07-11 18:29:24'), ('9', 'fengqi', 'fengqi@163.com', '19', '1', '2018-07-11 18:29:32'); DROP TABLE IF EXISTS `tbl_userinfo`; CREATE TABLE `tbl_userinfo` ( `id` int(11) NOT NULL AUTO_INCREMENT, `address` varchar(255) DEFAULT NULL, `user_id` int(11) DEFAULT NULL, PRIMARY KEY (`id`), UNIQUE KEY `idx_userId` (`user_id`) ) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8; INSERT INTO `tbl_userinfo` VALUES ('1', '上海市', '1'), ('2', '北京市', '2'), ('3', '杭州', '3'), ('4', '深圳', '4'), ('5', '廣州', '5'), ('6', '海南', '6');
二:五百萬數據插入
上面插入幾條測試數據,在使用索引時還需要插入更多的數據作為測試數據,下面就通過存儲過程插入500W條數據作為測試數據
-- 修改mysql默認的結束符號,默認是分號;但是在函數和存儲過程中會使用到分號導致解析不正確 DELIMITER $$ -- 隨機生成一個指定長度的字符串 CREATE FUNCTION rand_string(n INT) RETURNS VARCHAR(255) BEGIN # 定義三個變量 DECLARE chars_str VARCHAR(100) DEFAULT 'abcdefghijklmnopqrstuvwxyzABCDEFJHIJKLMNOPQRSTUVWXYZ'; DECLARE return_str VARCHAR(255) DEFAULT ''; DECLARE i INT DEFAULT 0; WHILE i < n DO SET return_str = CONCAT(return_str, SUBSTRING(chars_str, FLOOR(1+RAND()*52), 1)); SET i = i + 1; END WHILE; RETURN return_str; END $$ -- 創建插入的存儲過程 CREATE PROCEDURE insert_user(IN START INT(10), IN max_num INT(10)) BEGIN DECLARE i INT DEFAULT 0; SET autocommit = 0; REPEAT SET i = i + 1; INSERT INTO tbl_user VALUES ((START+i) ,rand_string(8), CONCAT(rand_string(6), '@random.com'), 1+FLOOR(RAND()*100), 3, NOW()); UNTIL i = max_num END REPEAT; COMMIT; END $$ -- 將命令結束符修改回來 DELIMITER ; -- 調用存儲過程,插入500萬數據,需要等待一會時間,等待執行完成 CALL insert_user(100001,5000000); -- Query OK, 0 rows affected (7 min 49.89 sec) SELECT COUNT(*) FROM tbl_user;
三:使用索引和不使用索引的比較
使用索引之前的查詢
然后給username創建索引再次查詢(數據庫卡死了,我用sqlyog做)
創建索引用了40秒,屬實有點慢
然后再查詢試試,基本是秒查了,效率提升很明顯
之前再黑窗口加的索引也上去了
然后刪除一個索引,byusername
四:explain命令
explain參數詳解
查看索引的使用情況:show status like 'Handler_read%'
Handler_read_key: 越高越好
Handler_read_rnd_next:越低越好
查詢優化器:
- 重新定義表的關聯順序(優化器會根據統計信息來決定表的關聯順序)
- 將外連接轉化成內連接(當外連接等於內連接)
- 使用等價變換規則(如去掉1=1)
- 優化count()、min()、max()
- 子查詢優化
- 提前終止查詢
- in條件優化
mysql可以通過 EXPLAIN EXTENDED 和 SHOW WARNINGS 來查看mysql優化器改寫后的sql語句
下圖提示我們別用*查詢,應該寫具體那一列
五:走索引的情況和不走索引的情況
1. in走索引
in操作能避免則避免,若實在避免不了,需要仔細評估in后邊的集合元素數量,控制在1000個之內。
2. 范圍查詢走索引
但是條件必須是一個具體的值,如果條件為 now() 當前時間,則會導致全表掃描
3. 模糊查詢只有左前綴使用索引
4. 反向條件不走索引 != 、 <> 、 NOT IN、IS NOT NULL
一個優化的實例:
# 常見的對not in的優化,使用左連接加上is null的條件過濾 SELECT id, username, age FROM tbl_user WHERE id NOT IN (SELECT user_id FROM tbl_order); SELECT u.id, u.username, u.age FROM tbl_user u LEFT JOIN tbl_order o ON u.id = o.user_id WHERE o.user_id IS NULL;
5. 對條件計算(使用函數或者算數表達式)不走索引
使用函數計算不走索引,無論是對字段使用了函數還是值使用了函數都不走索引,解決辦法通過應用程序計算好,將計算的結果傳遞給sql,而不是讓數據庫去計算
6. 查詢時必須使用正確的數據類型
如果索引字段是字符串類型,那么查詢條件的值必須使用引號,否則不走索引
7. or 使用索引和不使用索引的情況
or 只有兩邊都有索引才走索引,如果都沒有或者只有一個是不走索引的
8. 用union少用or
盡量避免使用or,因為大部分or連接的兩個條件同時都進行索引的情況幾率比較小,應使用uninon代替,這樣能走索引的走索引,不能走索引的就全表掃描。
9. 能用union all就不用union
union all 不去重復,union去重復,union使用了臨時表,應盡量避免使用臨時表
10. 復合索引
對於復合索引,如果單獨使用右邊的索引字段作為條件時不走索引的。即復合索引如果不滿足最左原則leftmost不會走復合索引
暫未完成,更新還會繼續