記一次sql優(yōu)化

最近為公司寫(xiě)后臺(tái)程序,就是數(shù)據(jù)庫(kù)檢索,其實(shí)就是拼sql,在測(cè)試環(huán)境運(yùn)行沒(méi)問(wèn)題,線上卻沒(méi)有數(shù)據(jù)顯示,最后發(fā)現(xiàn)是sql運(yùn)行超時(shí),整整6s,最后給優(yōu)化到0.5s

表結(jié)構(gòu):

| newreport | CREATE TABLE `newreport` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  `user_id` varchar(32) NOT NULL DEFAULT '' COMMENT '...',
  `report_id` varchar(32) NOT NULL DEFAULT '' COMMENT '...',
  `report_type` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT '...',
  `reason_ids` varchar(256) NOT NULL DEFAULT '' COMMENT '...',
  `description` varchar(256) NOT NULL DEFAULT '' COMMENT '...',
  `pictures` varchar(256) NOT NULL DEFAULT '' COMMENT '...',
  `createTs` int(10) unsigned NOT NULL DEFAULT '0',
  `comment_id` bigint(20) NOT NULL DEFAULT '0' COMMENT '...',
  `status` tinyint(2) NOT NULL DEFAULT '0' COMMENT '...',
  `extra` text,
  `chat_type` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT '...',
  `version` varchar(32) NOT NULL DEFAULT '' COMMENT '...',
  `region` varchar(32) NOT NULL DEFAULT '' COMMENT '...',
  `operator` varchar(32) NOT NULL DEFAULT '' COMMENT '...'
  `update_time` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '...',
  PRIMARY KEY (`id`),
  KEY `report_id` (`report_id`),
  KEY `idx_user_id` (`user_id`),
) ENGINE=InnoDB AUTO_INCREMENT=1701203 DEFAULT CHARSET=utf8 |

原sql

select sql_no_cache report_id, count(*) as report_total, max(createTs) as recent_time from newreport where report_type=4  and createTs between 1566849758 and 1567454558  and 0='\'\''  and 2=2  and status=0  group by report_id  order by report_total desc, createTs  limit 0,20\G

就是這樣的sql運(yùn)行時(shí)間6s

為createTs加了索引:

  KEY `idx_createTs` (`createTs`)

時(shí)間減少到0.5s ...
當(dāng)然沒(méi)有完,要不然這個(gè)就太水了,加個(gè)索引就解決了還有啥說(shuō)的。

createTs小范圍createTs between 1566849758 and 1567454558對(duì)應(yīng)的數(shù)據(jù)量占總數(shù)的0.87%。
大范圍 createTs between 1556668800 and 1567454558對(duì)應(yīng)的行數(shù)占總數(shù)97% 時(shí)間跨度20190501-20190902。
每天新增數(shù)據(jù)量接近2k,數(shù)據(jù)庫(kù)總數(shù) 1701186。
當(dāng)范圍查詢超過(guò)總數(shù)一定比例,mysql會(huì)全局掃描。

問(wèn)題來(lái)了,上面的查詢時(shí)間范圍為大概一周,當(dāng)查詢時(shí)間增加到4個(gè)月時(shí),查詢時(shí)間降到3s,繼續(xù)優(yōu)化:

select sql_no_cache report_id, count(*) as report_total, max(createTs) as recent_time from newreport s right join (select id from newreport where createTs > 1556668800 and createTs < 1567454558) t using(id) where report_type=4 and 0='\'\''  and 2=2  and status=0  group by report_id  order by report_total desc, recent_time limit 0,20;

explain結(jié)果:

explain select sql_no_cache report_id, count(*) as report_total, max(createTs) as recent_time from newreport where id in (select id from newreport where createTs > 1556668800 and createTs < 1567454558) and report_type=4 and 0='\'\''  and 2=2  and status=0  group by report_id  order by report_total desc, createTs limit 0,20\G


*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: newreport
   partitions: NULL
         type: ALL
possible_keys: PRIMARY,report_id
          key: NULL
      key_len: NULL
          ref: NULL
         rows: 1655044
     filtered: 1.00
        Extra: Using where; Using temporary; Using filesort
*************************** 2. row ***************************
           id: 1
  select_type: SIMPLE
        table: newreport
   partitions: NULL
         type: eq_ref
possible_keys: PRIMARY,idx_createTs
          key: PRIMARY
      key_len: 8
          ref: starmaker.newreport.id
         rows: 1
     filtered: 36.68
        Extra: Using where

時(shí)間區(qū)間4個(gè)月,查詢時(shí)間0.8s,勉強(qiáng)夠用,可是看到right join的sql并沒(méi)有使用createTs索引,繼續(xù)優(yōu)化,強(qiáng)制讓mysql使用這個(gè)索引

explain select sql_no_cache report_id, count(*) as report_total, max(createTs) as recent_time from newreport s right join (select id from newreport use index(idx_createTs) where createTs > 1556668800 and createTs < 1567454558) t using(id) where report_type=4 and 0='\'\''  and 2=2  and status=0  group by report_id  order by report_total desc, createTs limit 0,20\G

explain結(jié)果:


           id: 1
  select_type: SIMPLE
        table: newreport
   partitions: NULL
         type: range
possible_keys: idx_createTs
          key: idx_createTs
      key_len: 4
          ref: NULL
         rows: 607124
     filtered: 100.00
        Extra: Using where; Using index; Using temporary; Using filesort
*************************** 2. row ***************************
           id: 1
  select_type: SIMPLE
        table: s
   partitions: NULL
         type: eq_ref
possible_keys: PRIMARY,report_id
          key: PRIMARY
      key_len: 8
          ref: starmaker.newreport.id
         rows: 1
     filtered: 5.00
        Extra: Using where
2 rows in set, 2 warnings (0.00 sec)

掃描行數(shù)由1655044減少到607124
時(shí)間區(qū)間4個(gè)月,查詢時(shí)間0.5s

當(dāng)然如果可以,還能繼續(xù)優(yōu)化,那就是修改索引createTs為聯(lián)合索引,where子句中離散型字段有點(diǎn)多,也就是distinct field后,沒(méi)有幾個(gè)值的field,可以把這幾個(gè)field放在createTs前面構(gòu)建聯(lián)合索引,比如(field, createTs),放在createTs的前面是因?yàn)樽钭笄熬Y,當(dāng)只需要聯(lián)合索引的后面字段時(shí),可以把聯(lián)合索引靠前的字段通過(guò) field in (...) and createTs = ...利用起來(lái)。

但是索引不是越多越好,因?yàn)榫S護(hù)索引也是需要成本的嘛,索引多了,插入更新就會(huì)困難,要做的是將已有的資源發(fā)揮到極限。

至于為什么要right join一下,因?yàn)閎tree原理,createTs是非主鍵索引,底層維護(hù)存儲(chǔ)的是createTs和對(duì)應(yīng)記錄行的主鍵,通過(guò)這個(gè)非聚簇索引找到滿足條件的id,這個(gè)id就是主鍵索引,即聚簇索引,聚簇索引底層除了保存了主鍵,還有主鍵對(duì)應(yīng)的記錄行,也就是聚簇索引保存了數(shù)據(jù)表,直接利用聚簇索引的話,不就省了回表操作嘛。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容