注意使用 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
著作權聲明:本文為博主原創文章,未經博主允許不得轉載。