日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > 数据库 >内容正文

数据库

mysql优化的魅力,从20s优化到500ms,仅需三招(荣耀典藏版)

發布時間:2024/3/13 数据库 45 豆豆
生活随笔 收集整理的這篇文章主要介紹了 mysql优化的魅力,从20s优化到500ms,仅需三招(荣耀典藏版) 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

?

目錄

前言

1. 案發現場

2. 現狀

3. 第一次優化

4. 第二次優化

5. 第三次優化

5.1 前端做分頁

5.2 分批調用接口


?

?

接口性能問題,對于從事后端開發的同學來說,是一個繞不開的話題。想要優化一個接口的性能,需要從多個方面著手。

本文將會接著接口性能優化這個話題,從實戰的角度出發,聊聊我是如何優化一個慢查詢接口的。

前言

上周我優化了一下線上的批量評分查詢接口,將接口性能從最初的20s,優化到目前的500ms以內。

總體來說,用三招就搞定了。

到底經歷了什么?

1. 案發現場

在大數據量的請求下,導致我們每天早上上班的時候,總是會收到一些線上慢查詢接口匯總郵件,郵件中會展示接口地址、調用次數、最大耗時、平均耗時和traceId等信息。

我看到其中有一個批量評分查詢接口,最大耗時達到了20s,平均耗時也有2s。

用skywalking查看該接口的調用信息,發現絕大數情況下,該接口響應還是比較快的,大部分情況都是500ms左右就能返回,但也有少部分超過了20s的請求。

這個現象就非常奇怪了。

莫非跟數據有關?

比如:要查某一個組織的數據,是非常快的。但如果要查平臺,即組織的根節點,這種情況下,需要查詢的數據量非常大,接口響應就可能會非常慢。

但事實證明不是這個原因。

很快有個同事給出了答案。

他們在結算單列表頁面中,批量請求了這個接口,但他傳參的數據量非常大。

怎么回事呢?

當初說的需求是這個接口給分頁的列表頁面調用,每頁大小有:10、20、30、50、100,用戶可以選擇。

換句話說,調用批量評價查詢接口,一次性最多可以查詢100條記錄。

但實際情況是:結算單列表頁面還包含了很多訂單。基本上每一個結算單,都有多個訂單。調用批量評價查詢接口時,需要把結算單和訂單的數據合并到一起。

這樣導致的結果是:調用批量評價查詢接口時,一次性傳入的參數非常多,入參list中包含幾百、甚至幾千條數據都有可能。

2. 現狀

如果一次性傳入幾百或者幾千個id,批量查詢數據還好,可以走主鍵索引,查詢效率也不至于太差。

但那個批量評分查詢接口,邏輯不簡單。

偽代碼如下:

public?List<ScoreEntity>?query(List<SearchEntity>?list)?{//結果List<ScoreEntity>?result?=?Lists.newArrayList();//獲取組織idList<Long>?orgIds?=?list.stream().map(SearchEntity::getOrgId).collect(Collectors.toList());//通過regin調用遠程接口獲取組織信息List<OrgEntity>?orgList?=?feginClient.getOrgByIds(orgIds);for(SearchEntity?entity?:?list)?{//通過組織id找組織codeString?orgCode?=?findOrgCode(orgList,?entity.getOrgId());//通過組合條件查詢評價ScoreSearchEntity?scoreSearchEntity?=?new?ScoreSearchEntity();scoreSearchEntity.setOrgCode(orgCode);scoreSearchEntity.setCategoryId(entity.getCategoryId());scoreSearchEntity.setBusinessId(entity.getBusinessId());scoreSearchEntity.setBusinessType(entity.getBusinessType());List<ScoreEntity>?resultList?=?scoreMapper.queryScore(scoreSearchEntity);if(CollectionUtils.isNotEmpty(resultList))?{ScoreEntity?scoreEntity?=?resultList.get(0);result.add(scoreEntity);}}return?result; }

其實在真實場景中,代碼比這個復雜很多,這里為了給大家演示,簡化了一下。

最關鍵的地方有兩點:

  • 在接口中遠程調用了另外一個接口

  • 需要在for循環中查詢數據

  • 其中的第1點,即:在接口中遠程調用了另外一個接口,這個代碼是必須的。

    因為如果在評價表中冗余一個組織code字段,萬一哪天組織表中的組織code有修改,不得不通過某種機制,通知我們同步修改評價表的組織code,不然就會出現數據不一致的問題。

    很顯然,如果要這樣調整的話,業務流程上要改了,代碼改動有點大。

    所以,還是先保持在接口中遠程調用吧。

    這樣看來,可以優化的地方只能在:for循環中查詢數據。

    3. 第一次優化

    由于需要在for循環中,每條記錄都要根據不同的條件,查詢出想要的數據。

    由于業務系統調用這個接口時,沒有傳id,不好在where條件中用id in (...),這方式批量查詢數據。

    其實,有一種辦法不用循環查詢,一條sql就能搞定需求:使用or關鍵字拼接,例如:

    (org_code='001' and category_id=123 and business_id=111 and business_type=1)?or?(org_code='002' and category_id=123 and business_id=112 and business_type=2)?or?(org_code='003' and category_id=124 and business_id=117 and business_type=1)...

    這種方式會導致sql語句會非常長,性能也會很差。

    其實還有一種寫法:

    where?(a,b)?in?((1,2),(1,3)...)

    不過這種sql,如果一次性查詢的數據量太多的話,性能也不太好。

    居然沒法改成批量查詢,就只能優化單條查詢sql的執行效率了。

    首先從索引入手,因為改造成本最低。

    第一次優化是優化索引。

    評價表之前建立一個business_id字段的普通索引,但是從目前來看效率不太理想。

    由于我果斷加了聯合索引:

    alter?table?user_score?add?index??`un_org_category_business`? (`org_code`,`category_id`,`business_id`,`business_type`)?USING?BTREE;

    該聯合索引由:org_code、category_id、business_id和business_type四個字段組成。

    經過這次優化,效果立竿見影。

    批量評價查詢接口最大耗時,從最初的20s,縮短到了5s左右。

    4. 第二次優化

    由于需要在for循環中,每條記錄都要根據不同的條件,查詢出想要的數據。

    只在一個線程中查詢數據,顯然太慢。

    那么,為何不能改成多線程調用?

    第二次優化,查詢數據庫由單線程改成多線程。

    但由于該接口是要將查詢出的所有數據,都返回回去的,所以要獲取查詢結果。

    使用多線程調用,并且要獲取返回值,這種場景使用java8中的CompleteFuture非常合適。

    代碼調整為:

    CompletableFuture[]?futureArray?=?dataList.stream().map(data?->?CompletableFuture.supplyAsync(()?->?query(data),?asyncExecutor).whenComplete((result,?th)?->?{})).toArray(CompletableFuture[]::new); CompletableFuture.allOf(futureArray).join();

    CompleteFuture的本質是創建線程執行,為了避免產生太多的線程,所以使用線程池是非常有必要的。

    優先推薦使用ThreadPoolExecutor類,我們自定義線程池。

    具體代碼如下:

    ExecutorService?threadPool?=?new?ThreadPoolExecutor(8,?//corePoolSize線程池中核心線程數10,?//maximumPoolSize?線程池中最大線程數60,?//線程池中線程的最大空閑時間,超過這個時間空閑線程將被回收TimeUnit.SECONDS,//時間單位new?ArrayBlockingQueue(500),?//隊列new?ThreadPoolExecutor.CallerRunsPolicy());?//拒絕策略

    也可以使用ThreadPoolTaskExecutor類創建線程池:

    @Configuration public?class?ThreadPoolConfig?{/***?核心線程數量,默認1*/private?int?corePoolSize?=?8;/***?最大線程數量,默認Integer.MAX_VALUE;*/private?int?maxPoolSize?=?10;/***?空閑線程存活時間*/private?int?keepAliveSeconds?=?60;/***?線程阻塞隊列容量,默認Integer.MAX_VALUE*/private?int?queueCapacity?=?1;/***?是否允許核心線程超時*/private?boolean?allowCoreThreadTimeOut?=?false;@Bean("asyncExecutor")public?Executor?asyncExecutor()?{ThreadPoolTaskExecutor?executor?=?new?ThreadPoolTaskExecutor();executor.setCorePoolSize(corePoolSize);executor.setMaxPoolSize(maxPoolSize);executor.setQueueCapacity(queueCapacity);executor.setKeepAliveSeconds(keepAliveSeconds);executor.setAllowCoreThreadTimeOut(allowCoreThreadTimeOut);//?設置拒絕策略,直接在execute方法的調用線程中運行被拒絕的任務executor.setRejectedExecutionHandler(new?ThreadPoolExecutor.CallerRunsPolicy());//?執行初始化executor.initialize();return?executor;} }

    經過這次優化,接口性能也提升了5倍。

    從5s左右,縮短到1s左右。

    但整體效果還不太理想。

    5. 第三次優化

    經過前面的兩次優化,批量查詢評價接口性能有一些提升,但耗時還是大于1s。

    出現這個問題的根本原因是:一次性查詢的數據太多。

    那么,我們為什么不限制一下,每次查詢的記錄條數呢?

    第三次優化,限制一次性查詢的記錄條數。其實之前也做了限制,不過最大是2000條記錄,從目前看效果不好。

    限制該接口一次只能查200條記錄,如果超過200條則會報錯提示。

    如果直接對該接口做限制,則可能會導致業務系統出現異常。

    為了避免這種情況的發生,必須跟業務系統團隊一起討論一下優化方案。

    主要有下面兩個方案:

    5.1 前端做分頁

    在結算單列表頁中,每個結算單默認只展示1個訂單,多余的分頁查詢。

    這樣的話,如果按照每頁最大100條記錄計算的話,結算單和訂單最多一次只能查詢200條記錄。

    這就需要業務系統的前端做分頁功能,同時后端接口要調整支持分頁查詢。

    但目前現狀是前端沒有多余開發資源。

    由于人手不足的原因,這套方案目前只能暫時擱置。

    5.2 分批調用接口

    業務系統后端之前是一次性調用評價查詢接口,現在改成分批調用。

    比如:之前查詢500條記錄,業務系統只調用一次查詢接口。

    現在改成業務系統每次只查100條記錄,分5批調用,總共也是查詢500條記錄。

    這樣不是變慢了嗎?

    答:如果那5批調用評價查詢接口的操作,是在for循環中單線程順序的,整體耗時當然可能會變慢。

    但業務系統也可以改成多線程調用,只需最終匯總結果即可。

    此時,有人可能會問題:在評價查詢接口的服務器多線程調用,跟在其他業務系統中多線程調用不是一回事?

    還不如把批量評價查詢接口的服務器中,線程池的最大線程數調大一點?

    顯然你忽略了一件事:線上應用一般不會被部署成單點。絕大多數情況下,為了避免因為服務器掛了,造成單點故障,基本會部署至少2個節點。這樣即使一個節點掛了,整個應用也能正常訪問。

    當然也可能會出現這種情況:假如掛了一個節點,另外一個節點可能因為訪問的流量太大了,扛不住壓力,也可能因此掛掉。

    換句話說,通過業務系統中的多線程調用接口,可以將訪問接口的流量負載均衡到不同的節點上。

    他們也用8個線程,將數據分批,每批100條記錄,最后將結果匯總。

    經過這次優化,接口性能再次提升了1倍。

    從1s左右,縮短到小于500ms。

    溫馨提醒一下,無論是在批量查詢評價接口查詢數據庫,還是在業務系統中調用批量查詢評價接口,使用多線程調用,都只是一個臨時方案,并不完美。

    這樣做的原因主要是為了先快速解決問題,因為這種方案改動是最小的。

    要從根本上解決問題,需要重新設計這一套功能,需要修改表結構,甚至可能需要修改業務流程。但由于牽涉到多條業務線,多個業務系統,只能排期慢慢做了。

    ?

    總結

    以上是生活随笔為你收集整理的mysql优化的魅力,从20s优化到500ms,仅需三招(荣耀典藏版)的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。

    主站蜘蛛池模板: 精品国产18久久久久久 | 一区二区三区在线视频免费观看 | 日本理论中文字幕 | 在线观看免费日韩av | 麻豆传媒在线播放 | 国产小视频在线观看免费 | 成人在线网站 | 国产福利午夜 | 午夜激情小视频 | 又紧又大又爽精品一区二区 | 国产日产亚洲系列最新 | 麻豆午夜视频 | 操极品美女| 国产免费观看久久黄av片 | 不卡网av | 久久精品99北条麻妃 | 一卡二卡三卡四卡在线 | 在线视频资源 | 中文字幕日韩电影 | 伊人欧美在线 | 天天舔天天操 | 一级日韩一级欧美 | 久久艹伊人 | 欧美成人xxx | 91久久亚洲 | 日本久色 | 免费看一级视频 | 40一50一60老女人毛片 | 99爱免费 | 欧美色图亚洲视频 | 荒野求生21天去码版网站 | 亚洲色图27p | 久久久久久18 | 人人爽人人爽人人爽 | 91麻豆国产福利精品 | 美女黄视频在线观看 | 制服丝袜第一页在线观看 | 欧美大片免费观看 | 色97色 | 欧美日韩三级在线 | 看中国毛片 | 亚洲永久无码精品一区二区 | 91丨porny丨露出| 18禁免费观看网站 | 素人一区| 国产精品日本一区二区在线播放 | 在线观看亚洲国产 | 国产黄色网址在线观看 | 国产成人av无码精品 | 天天躁日日躁狠狠躁伊人 | 欧美日韩精品久久 | 大战熟女丰满人妻av | 熟女视频一区二区三区 | 久久久国产成人 | 欧美日韩首页 | 久久青娱乐 | 韩国三级做爰视频 | 91黄色免费版 | 亚洲一区二区图片 | 校园春色亚洲 | 欧美日韩一区在线播放 | 全黄性性激高免费视频 | 老妇女av| 亚洲精品久久一区二区三区777 | 中文字字幕一区二区三区四区五区 | 精品在线视频一区 | 浪潮av网站| 国产黄免费 | 黄色片视频在线观看 | 国产美女网站视频 | www.亚洲在线 | 日韩免费观看一区二区 | 亚洲天堂性 | 国产乱码精品一区二区三区五月婷 | 精品亚洲aⅴ无码一区二区三区 | 国产内射合集颜射 | 日本韩国在线播放 | aise爱色av| 国产精品理伦片 | 久久久一二三四 | 午夜福利理论片在线观看 | 朝桐光在线观看 | 国产午夜麻豆影院在线观看 | 97人人爱 | 久久久精品人妻无码专区 | 日本无遮挡边做边爱边摸 | 在线99 | 日产电影一区二区三区 | 成人h视频在线观看 | 色播欧美| 久久精品无码中文字幕 | 女同一区二区 | 女人特黄大aaaaaa大片 | 激情五月婷婷小说 | 中文字幕在线播放视频 | 欧美日韩成人在线播放 | 日本视频在线免费观看 | 99久久人妻无码精品系列 | 三级视频在线播放 |