天天看點

注意使用 BTREE 複合索引各字段的 ASC/DESC 以優化 order by 查詢效率參考資料

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

# [email protected]: 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 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, t.trans_datetime, t.acq_ins_code, t.trace_num
			limit 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 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 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

繼續閱讀