三行Java代碼提升接口性能600倍
來源:juejin.cn/post/7322156759443144713
推薦:https ://t.zsxq.com/16HjsdjFc
背景
業(yè)務(wù)在群里反饋編輯結(jié)算單時(shí)有些賬單明細(xì)查不出來,但是新建結(jié)算單可以,我第一反應(yīng)是去測(cè)試環(huán)境試試有沒有該問題,結(jié)果發(fā)現(xiàn)沒任何問題?。?!
然后我登錄生產(chǎn)環(huán)境編輯業(yè)務(wù)反饋有問題的結(jié)算單,發(fā)現(xiàn)查詢接口直接504網(wǎng)關(guān)超時(shí)了,此時(shí)心里已經(jīng)猜到是代碼性能問題導(dǎo)致的,接來下就把重點(diǎn)放到排查接口超時(shí)的問題上了。
歡迎加入 https://t.zsxq.com/16OHFdQff。

問題排查
遇到生產(chǎn)問題先查日志是基本操作,登錄阿里云的日志平臺(tái),可以查到接口耗時(shí)竟然高達(dá)469245毫秒!
這個(gè)結(jié)算單關(guān)聯(lián)的賬單數(shù)量也就800多條,所以可以肯定這個(gè)接口存在性能問題。
但是日志除了接口耗時(shí),并沒有其他報(bào)錯(cuò)信息或異常信息,看不出哪里導(dǎo)致了接口慢。
接口慢一般是由如下幾個(gè)原因?qū)е拢?/strong>
- 依賴的外部系統(tǒng)慢,比如同步調(diào)用外部系統(tǒng)的接口耗時(shí)比較久
- 處理的數(shù)據(jù)過多導(dǎo)致
- sql性能有問題,存在慢sql
- 有大循環(huán)存在循環(huán)處理的邏輯,如循環(huán)讀取exel并處理
- 網(wǎng)絡(luò)問題或者依賴的中間件比較慢
- 如果使用了鎖,也可能由于長(zhǎng)時(shí)間獲取不到鎖導(dǎo)致接口超時(shí)
當(dāng)然也可以使用 arthas 的 tracehttps://arthas.aliyun.com/doc/trace.html命令分析哪一塊比較耗時(shí)。
由于安裝arthas有點(diǎn)麻煩,就先猜測(cè)可能慢sql導(dǎo)致的,然后就登錄阿里云RDS查看了慢sql監(jiān)控日志。
好家伙一看嚇一跳,sql耗時(shí)竟然高達(dá)66秒,而且執(zhí)行次數(shù)還挺多!
我趕緊把sql語(yǔ)句放到數(shù)據(jù)庫(kù)用explain命令看下執(zhí)行計(jì)劃,分析這條sql為啥這么慢。
EXPLAIN SELECT DISTINCT(bill_code) FROM `t_bill_detail_2023_4` WHERE
(settlement_order_code IS NULL OR settlement_order_code = 'JS23122600000001');
分析結(jié)果如下:
如果不知道explain結(jié)果每個(gè)字段的含義,可以看看這篇文章《EXPLAIN 和 SHOW TABLE STATUS LIKE 里返回的 rows 為什么不準(zhǔn)確?》。
可以看到掃描行數(shù)達(dá)到了250多萬行,ref已經(jīng)是最高效的const,但是看最后的Extra列 Using temporary 表明這個(gè)sql用到了臨時(shí)表,頓時(shí)心里清楚什么原因了。
因?yàn)閟ql有個(gè)去重關(guān)鍵字DISTINCT,所以mysql在需要建臨時(shí)表來完成查詢結(jié)果集的去重操作,如果結(jié)果集數(shù)據(jù)量比較小沒有超過buffer,就可以直接在內(nèi)存中去重,這種效率也是比較高的。
但是如果結(jié)果集數(shù)據(jù)量很大,buffer存不下,那就需要借助磁盤完成去重了,我們都知道操作磁盤相比內(nèi)存是非常慢的,時(shí)間差幾個(gè)數(shù)量級(jí)。
雖然這個(gè)表里的settlement_order_code字段是有索引的,但是線上也有很多settlement_order_code為null的數(shù)據(jù),這就導(dǎo)致查出來的結(jié)果集非常大,然后又用到臨時(shí)表,所以sql耗時(shí)才這么久!
同時(shí),這里也解釋了為什么測(cè)試環(huán)境沒有發(fā)現(xiàn)這個(gè)問題,因?yàn)闇y(cè)試環(huán)境的數(shù)據(jù)不多,直接在內(nèi)存就完成去重了。
問題解決
知道了問題原因就很好解決了,首先根據(jù)SQL和接口地址很快就找到出現(xiàn)問題的代碼是下圖紅框圈出來的地方
可以看到代碼前面有個(gè)判斷,只有當(dāng)isThreeOrderQuery=true時(shí)才會(huì)執(zhí)行這個(gè)查詢,判斷方法代碼如下
然后因?yàn)檫@是個(gè)編輯場(chǎng)景,前端會(huì)把當(dāng)前結(jié)算單號(hào)(usedSettlementOrderCode字段)傳給后端,所以這個(gè)方法就返回了true。
同理,拼接出來的sql就帶了條件(settlement_order_code IS NULL OR settlement_order_code = 'JS23122600000001')。
解決起來也很簡(jiǎn)單,把isThreeOrderQuery()方法圈出來的代碼去掉就行了,這樣就不會(huì)執(zhí)行那個(gè)查詢,同時(shí)也不會(huì)影響原有的代碼邏輯,因?yàn)楹竺鏁?huì)根據(jù)篩選條件再查一次t_bill_detail表。
改代碼發(fā)布后,再編輯結(jié)算單,優(yōu)化后的效果如下圖:
只改了三行代碼,接口耗時(shí)就立馬從469245ms縮短到700ms,性能提升了600多倍!
總結(jié)
感覺壓測(cè)環(huán)境還是有必要的,有些問題數(shù)據(jù)量小了或者請(qǐng)求并發(fā)不夠都沒法暴露出來,同時(shí)以后寫代碼可以提前把sql在數(shù)據(jù)庫(kù)explain下看看性能如何,畢竟能跑就行不是我們的追求??。
