注意使用 BTREE 複合索引各欄位的 ASC/DESC 以最佳化 order by 查詢效率,btreeasc

來源:互聯網
上載者:User

注意使用 BTREE 複合索引各欄位的 ASC/DESC 以最佳化 order by 查詢效率,btreeasc
tbl_direct_pos_201506 表有 190 萬資料,DDL:

CREATE TABLE `tbl_direct_pos_201506` (  `acq_ins_code` char(13) NOT NULL DEFAULT '' COMMENT '機構代碼',  `trace_num` char(6) NOT NULL DEFAULT '' COMMENT '跟蹤號',  `trans_datetime` char(10) NOT NULL DEFAULT '' COMMENT '交易時間',  `process_flag` char(1) DEFAULT NULL COMMENT '處理標識',  `rev_flag` char(1) DEFAULT NULL COMMENT '接收標識',  `before_trans_code` char(3) DEFAULT NULL COMMENT '交易類型',  `trans_amt` decimal(15,3) DEFAULT NULL COMMENT '交易金額',  `acct_num` char(21) DEFAULT NULL COMMENT '卡號',  `mer_type` char(4) DEFAULT NULL COMMENT '商戶類型',  `recv_ins_code` char(13) DEFAULT NULL COMMENT '發卡行代碼',  `retrivl_ref_num` char(12) DEFAULT NULL COMMENT '檢索參考號',  `resp_auth_code` char(6) DEFAULT NULL COMMENT '授權碼',  `resp_code` char(2) DEFAULT NULL COMMENT '應答碼',  `term_id` char(8) DEFAULT NULL COMMENT '終端代碼',  `mer_code` char(15) DEFAULT NULL COMMENT '商戶代碼',  `mer_addr_name` char(40) DEFAULT NULL COMMENT '商戶名稱和地址,前 25 位元組是名稱,後面是地址',  `self_define` varchar(300) DEFAULT NULL COMMENT '第 259 位元組是卡片類型',  `sys_date` char(8) NOT NULL DEFAULT '' COMMENT '交易日期',  `sa_sav2` varchar(300) DEFAULT NULL COMMENT '第 243 位元組是 DCC 標識',  `rec_create_time` datetime DEFAULT NULL COMMENT '聯機入庫時間',  `rec_update_time` datetime DEFAULT NULL COMMENT '最後修改時間',  PRIMARY KEY (`sys_date`,`trans_datetime`,`acq_ins_code`,`trace_num`),  KEY `idx_direct_pos_create_time` (`rec_create_time`)) ENGINE=MyISAM DEFAULT CHARSET=utf8 COMMENT='交易月表模板';

關於該表的一個慢查詢日誌如下:
# Time: 150701 15:45:28
# User@Host: test[test] @ localhost [127.0.0.1]  Id:     1
# Query_time: 2.478195  Lock_time: 0.010007 Rows_sent: 20  Rows_examined: 450612
SET timestamp=1435736728;
select substr(t.acq_ins_code, 3) merAcqInsCode, t.mer_code, t.term_id, substr(t.mer_addr_name, 1, 12) merName,
tt.trans_desc, t.rev_flag, t.trans_amt, concat(substr(t.sys_date, 1, 4), t.trans_datetime) transTime,t.before_trans_code,
t.acct_num, t.retrivl_ref_num,  t.resp_code, t.resp_auth_code,  r.recv_ins_name,t.acq_ins_code,t.trace_num,t.trans_datetime,
case substr(t.sa_sav2,259,1)  when 1 then '借記卡' when 2 then '貸記卡'
when 3 then '准貸記卡' when 4 then '私人預付卡' else '' end  cardType,
case 
when locate('VIS',t.sa_sav2) > 0 then 'VISA' 
when locate('JCB',t.sa_sav2) > 0 then 'JCB' 
when locate('DNC',t.sa_sav2) > 0 then '大萊卡' 
when locate('CUP',t.sa_sav2) > 0 then '銀聯境內卡' 
when locate('UPI',t.sa_sav2) > 0 then '銀聯境外卡'
else '' end cardBrand 
from tbl_direct_pos_201506 t
left join trans_recv_ins r on r.recv_ins_code = t.recv_ins_code
left join tbl_trans_type tt on tt.trans_code = t.before_trans_code
where t.sys_date between '20150622' and '20150628' 
order by
t.sys_date desc, t.trans_datetime desc, t.acq_ins_code, t.trace_num 
limit 0, 20;
日誌中可以看出該 sql 的執行時間是 2.478 s。
我們來查看一下該 sql 的執行計畫:
id select_type table type possible_keys key key_len ref rows Extra
1 SIMPLE t range PRIMARY PRIMARY 24   299392 Using index condition; Using filesort
1 SIMPLE r eq_ref PRIMARY PRIMARY 41 msp.t.recv_ins_code 1 Using where
1 SIMPLE tt eq_ref PRIMARY PRIMARY 14 msp.t.before_trans_code 1 Using where

執行計畫分析:
Using filesort。是的,看到它,說明我們的查詢需要最佳化了:檔案排序是通過相應的排序演算法,將取得的資料在記憶體中進行排序。
MyISAM 儲存引擎的主鍵索引和非主鍵索引差別很小,只不過是主鍵索引的索引鍵是一個唯一且非空的鍵而已。MyISAM 的索引預設為 B-TREE。也就是說,主鍵在這裡相當於一個普通的 B-TREE。
該 sql 一個 where 欄位,四個 order by 欄位,都在主鍵裡邊呀,而且 order by 的順序完全符合最左首碼原則,為什麼還要 filesort?
MySql 索引建立手冊裡如是說:
索引列的定義可以跟隨 ASC 或者 DESC。這些關鍵字允許為未來擴充用於指定升序或降序索引值儲存。這個文法會被解析但卻被忽略。索引列預設以升序排列。——也就是說你寫了不會報錯,但寫了白寫。
這樣看來,我們的主鍵沒起排序作用,原因就在於我們的主鍵是各主鍵欄位 asc 儲存, order by 裡 desc 和 asc(預設是 asc) 混用。為了驗證這個說法,我們把該 order by 換為和主鍵一致的 asc:
select substr(t.acq_ins_code, 3) merAcqInsCode, t.mer_code, t.term_id, substr(t.mer_addr_name, 1, 12) merName,tt.trans_desc, t.rev_flag, t.trans_amt, concat(substr(t.sys_date, 1, 4), t.trans_datetime) transTime,t.before_trans_code,t.acct_num, t.retrivl_ref_num,  t.resp_code, t.resp_auth_code,  r.recv_ins_name,t.acq_ins_code,t.trace_num,t.trans_datetime,case substr(t.sa_sav2,259,1)  when 1 then '借記卡' when 2 then '貸記卡'when 3 then '准貸記卡' when 4 then '私人預付卡' else '' end  cardType,case when locate('VIS',t.sa_sav2) > 0 then 'VISA' when locate('JCB',t.sa_sav2) > 0 then 'JCB' when locate('DNC',t.sa_sav2) > 0 then '大萊卡' when locate('CUP',t.sa_sav2) > 0 then '銀聯境內卡' when locate('UPI',t.sa_sav2) > 0 then '銀聯境外卡'else '' end cardBrand from tbl_direct_pos_201506 tleft join trans_recv_ins r on r.recv_ins_code = t.recv_ins_codeleft join tbl_trans_type tt on tt.trans_code = t.before_trans_codewhere t.sys_date between '20150622' and '20150628' order by t.sys_date, t.trans_datetime, t.acq_ins_code, t.trace_numlimit 0, 20;

執行時間:0.023 s。
結果差強人意。查看其執行計畫:
id select_type table type possible_keys key key_len ref rows Extra
1 SIMPLE t range PRIMARY PRIMARY 24   299392 Using index condition
1 SIMPLE r eq_ref PRIMARY PRIMARY 41 msp.t.recv_ins_code 1 Using where
1 SIMPLE tt eq_ref PRIMARY PRIMARY 14 msp.t.before_trans_code 1 Using where

果然,我們利用到了主鍵索引,Using filesort 沒有了。
既然找的了問題的症兆所在,接下來的事情似乎只是走流程了。
問了下業務,分頁結果裡 sys_date 和 trans_datetime 兩個欄位必須降序排列,其餘兩個欄位倒不是很在意。
既然我們無法更改索引每一列的降序、升序(預設為升序),那麼我們可以在寫 order by 的時候讓索引各欄位降序/升序一致。最終的 sql 改寫為:
select substr(t.acq_ins_code, 3) merAcqInsCode, t.mer_code, t.term_id, substr(t.mer_addr_name, 1, 12) merName,tt.trans_desc, t.rev_flag, t.trans_amt, concat(substr(t.sys_date, 1, 4), t.trans_datetime) transTime,t.before_trans_code,t.acct_num, t.retrivl_ref_num,  t.resp_code, t.resp_auth_code,  r.recv_ins_name,t.acq_ins_code,t.trace_num,t.trans_datetime,case substr(t.sa_sav2,259,1)  when 1 then '借記卡' when 2 then '貸記卡'when 3 then '准貸記卡' when 4 then '私人預付卡' else '' end  cardType,case when locate('VIS',t.sa_sav2) > 0 then 'VISA' when locate('JCB',t.sa_sav2) > 0 then 'JCB' when locate('DNC',t.sa_sav2) > 0 then '大萊卡' when locate('CUP',t.sa_sav2) > 0 then '銀聯境內卡' when locate('UPI',t.sa_sav2) > 0 then '銀聯境外卡'else '' end cardBrand from tbl_direct_pos_201506 tleft join trans_recv_ins r on r.recv_ins_code = t.recv_ins_codeleft join tbl_trans_type tt on tt.trans_code = t.before_trans_codewhere t.sys_date between '20150622' and '20150628' order by t.sys_date desc, t.trans_datetime desc, t.acq_ins_code desc, t.trace_num desc limit 0, 20;

執行之,0.029 s,搞定。


參考資料
  • http://dev.mysql.com/doc/refman/5.5/en/create-index.html
  • http://stackoverflow.com/questions/10109108/how-do-i-create-a-desc-index-in-mysql
  • http://stackoverflow.com/questions/2341576/updating-mysql-primary-key

著作權聲明:本文為博主原創文章,未經博主允許不得轉載。

相關文章

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.