前言
相信我們都碰見過sql優化的問題,而大部分人的做法就是建立索引,在InnoDB引擎中索引分爲 B+tree 和 Hash,
Hash index :
Hash的應用場景不多,我們簡單的聊幾句帶過,Hash 顧名思義是通過hash算法將索引列進行計算確定存儲指針位置,優點就是查詢非常快,缺點就是不能進行範圍或者不等於查詢,而且不合適的hash算法會造成大量的hash衝突
B+tree index :
這裏涉及了一些數據結構的知識,需要我們去了解一下,B+tree也是由 二叉樹 --> 平衡二叉樹 --> B-tree(B槓tree) --> B+tree演化優化而來,這裏有一篇不錯的介紹,感興趣的可以看看裏面的具體演化過程和對InnoDB引擎的介紹
文章地址: https://www.toutiao.com/i6709447784136704523/
方案
sql優化的方案記錄:
1. 儘量索引全值匹配
2. varchar類型的引號不可省略
3. 組合索引,索引要遵守最左匹配原則
4. 不在索引列上做任何操作[計算,函數,(自動 or 手動)類型轉換]會導致索引失效而進行全表掃描
5. 範圍條件放到最後[存儲引擎不能使用索引中範圍條件右邊的列]
6. 不等於要慎用[在使用不等於(!= 或者 <>)的時候無法使用索引會導致全表掃描]
7. OR 改 UNION 效率高
8. like百分號寫最右邊(爲了遵守最左匹配原則)
測試案例
建表DDL語句(測試數據量大概200萬):
CREATE TABLE `srcorder_item` (
`order_item_no` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '訂單行號',
`order_no` varchar(20) DEFAULT NULL COMMENT '訂單號',
`cust_no` varchar(20) DEFAULT NULL COMMENT '會員編碼',
`commdty_code` varchar(20) DEFAULT NULL COMMENT '商品編碼',
`quantiy` int(11) DEFAULT NULL COMMENT '數量',
`unit_price` decimal(28,6) DEFAULT NULL COMMENT '單價',
`create_time` datetime DEFAULT NULL COMMENT '創建時間',
`update_time` datetime DEFAULT NULL COMMENT '最後更新時間',
`order_header_no` bigint(20) DEFAULT NULL,
`status` varchar(30) DEFAULT NULL COMMENT '訂單行狀態',
`gc_order_no` varchar(20) DEFAULT NULL COMMENT '訂單號',
`gc_item_no` varchar(20) DEFAULT NULL COMMENT '訂單行號',
`invoice_status` char(1) DEFAULT NULL COMMENT '發票是否已開狀態',
PRIMARY KEY (`order_item_no`),
KEY `srcorder_item_idx_1` (`order_no`) USING BTREE,
KEY `srcorder_item_idx_2` (`cust_no`) USING BTREE,
KEY `srcorder_item_idx5` (`create_time`,`status`) USING BTREE,
KEY `srcorder_item_idx_7` (`gc_order_no`) USING BTREE,
KEY `srcorder_item_idx_8` (`gc_item_no`) USING BTREE,
KEY `srcorder_item_idx_10` (`update_time`) USING BTREE,
KEY `srcorder_item_idx_3` (`order_header_no`,`commdty_code`) USING BTREE,
KEY `srcorder_item_idx_11` (`update_time`,`cust_no`) USING BTREE,
KEY `srcorder_item_idx_12` (`cust_no`,`gc_order_no`,`create_time`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=1550000 DEFAULT CHARSET=utf8 COMMENT='訂單行信息';
Test1. 儘量索引全值匹配
explain
select * from srcorder_item where cust_no = '6022517106' and gc_order_no ='1002296352' and create_time = STR_TO_DATE('2015-01-28 16:10:35', '%Y-%m-%d %H:%i:%s');
執行計劃:
提示: 按索引全值匹配,應該走 srcorder_item_idx_12 這個索引,但實際 srcorder_item_idx_7 ,這裏有一點需要注意,搜索引擎會優化選擇合適的索引,但不一定是最合適的,我們可以強制讓sql走 srcorder_item_idx_12 這個索引,測試一下平均查詢時間
explain
select * from srcorder_item force index(srcorder_item_idx_12) where cust_no = '6022517106' and gc_order_no ='1002296352' and create_time = STR_TO_DATE('2015-01-28 16:10:35', '%Y-%m-%d %H:%i:%s');
1)非強制執行5次的執行時間分別爲 [0.022, 0.038, 0.040, 0.039, 0.025] 平均 = 0.0328
2)強制索引執行5次的執行時間分別爲 [0.033, 0.010, 0.013, 0.021, 0.028] 平均 = 0.021
由此可見,有時候走不到自己的全值匹配索引時,可以考慮一下強制索引的寫法
Test2. varchar類型的引號不可省略
select * from srcorder_item where order_no = 1002296352
select * from srcorder_item where order_no = '1002296352'
Test3. 組合索引,索引要遵守最左匹配原則
explain select * from srcorder_item where create_time = STR_TO_DATE('2015-01-28 16:10:35', '%Y-%m-%d %H:%i:%s') and status='00'
explain select * from srcorder_item force index(srcorder_item_idx_12) where gc_order_no ='1002296352' and create_time >= '2015-01-01 16:10:35' and create_time < '2015-01-31 16:10:35';
Test4. 不在索引列上做任何操作[計算,函數,(自動 or 手動)類型轉換]會導致索引失效而進行全表掃描
explain select * from srcorder_item where DATE_FORMAT(update_time, '%Y-%m-%d') = CURDATE()
Test5. 範圍條件放到最後[存儲引擎不能使用索引中範圍條件右邊的列]
explain select * from srcorder_item where create_time > STR_TO_DATE('2015-01-28 16:10:35', '%Y-%m-%d %H:%i:%s') and status='00'
Test6. 不等於要慎用[在使用不等於(!= 或者 <>)的時候無法使用索引會導致全表掃描]
explain select * from srcorder_item where create_time != STR_TO_DATE('2015-01-28 16:10:35', '%Y-%m-%d %H:%i:%s') and status='00'
Test7. OR 改 UNION 效率高
select * from srcorder_item where order_no = '1002296352' or order_no='1418580809770'
修改後
select * from srcorder_item where order_no = '1002296352'
UNION
select * from srcorder_item where order_no = '1418580809770'
前者5次平均:[0.034,0.036,0.273,0.038,0.037] 平均 = 0.0836
後者5次平均[0.035,0.024,0.010,0.021,0.021] 平均 = 0.0222
Test8. like百分號寫最右邊(爲了遵守最左匹配原則)
select * from srcorder_item where order_no like '1418580809770%'
非特殊需求,儘量不要用下面的寫法(執行計劃來看,會走全表掃描)
select * from srcorder_item where order_no like '%1418580809770%'
小結
全值匹配我最愛,最左前綴要遵守
帶頭大哥不能死,中間兄弟不能斷
索引列上少計算,範圍之後全失效
LIKE百分寫最右,覆蓋索引不寫*[意思是索引(a,b),你查詢的時候 select a,b from t where a=? and b=?,索引直接有值,避免回表]
不等空值還有OR,索引影響要注意
VAR引號不能丟,SQL優化有訣竅
重要重要重要(引用某大牛說的): 數據庫的優化,遠遠比不上應用層面上的優化,有可能是設計的問題,也值得考慮,而不是直指SQL
例外
查詢是否用到索引,有時候並不是我們建立了索引,就一定會走索引,搜索引擎還會做優化
1.例如走全表掃描比索引快時,執行計劃中就走不到我們建立的索引
2.通過索引掃描的記錄超過20%~30%,可能會變成全表掃描,常見於數據量大,根據時間範圍查詢的時候