<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          一條失去條件的動態(tài) SQL,到手的年終獎飛了|文末彩蛋

          共 5484字,需瀏覽 11分鐘

           ·

          2020-12-30 21:18

          點(diǎn)擊藍(lán)色「小黑十一點(diǎn)半關(guān)注樓下小黑哥?

          點(diǎn)擊下方卡片可搜索文章?


          Hello,大家好,我是樓下小黑哥~

          2021 年馬上就要來,小黑哥沒文化,就簡單一點(diǎn):

          「就祝大家 2021 年 bug 少點(diǎn),頭發(fā)多點(diǎn),績效高點(diǎn),年年 375~」

          ps:文末有彩蛋,可以獲取 2020 年 Github 年度報告,看看這一年你在上面干了些啥~

          前言

          好了,進(jìn)入今天的正文,今天想跟大家聊聊一次 mybatis 動態(tài) SQL 引發(fā)的生產(chǎn)事故。

          事情這樣的,我們有個訂單相關(guān)數(shù)據(jù)庫服務(wù),專門負(fù)責(zé)訂單相關(guān)的增刪改查。這個服務(wù)運(yùn)行了很久,一直都沒有問題。

          直到某天中午,正想躺下休息一下,就突然接到系統(tǒng)報警,大量訂單創(chuàng)建失敗。訂單服務(wù)可以說是核心服務(wù),這個服務(wù)不可用,整個流程都會被卡主,交易都將會失敗。

          馬上沒了睡意,立刻起來登上生產(chǎn)運(yùn)維機(jī),查看訂單服務(wù)的系統(tǒng)日志。

          Caused by: java.util.concurrent.RejectedExecutionException: Thread pool is EXHAUSTED! Thread Name: DubboServerHandler-xxip, Pool Size: 200 (active: 200, core: 200, max: 200, largest: 200), Task: 165633 (completed: 165433), Executor status:(isShutdown:false, isTerminated:false, isTerminating:false), in 1!
          at com.alibaba.dubbo.common.threadpool.support.AbortPolicyWithReport.rejectedExecution(AbortPolicyWithReport.java:53)
          at java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:768)
          at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:656)
          at com.alibaba.dubbo.remoting.transport.dispatcher.all.AllChannelHandler.caught(AllChannelHandler.java:65)

          如上所示,日志中打印大量的 Dubbo 線程池線程耗盡,直接拒絕服務(wù)調(diào)用的日志。登上另一臺機(jī)器,好家伙,除了上述日志以外,仔細(xì)翻看居然還發(fā)生 「OOM」!!!

          其實(shí)發(fā)生 「OOM」 了,問題倒是簡單了,首先 「dump」 一下,然后分析一下生成的日志,查找內(nèi)存占用最大類,然后分析定位具體代碼塊。

          結(jié)合系統(tǒng)日志以及 dump 日志,我們很快就定位到發(fā)生問題的代碼位置,樣例代碼如下:

          Order?order=new?Order();
          log.info("訂單查詢參數(shù)信息:{}",order);
          //?其他系統(tǒng)邏輯,關(guān)鍵信息數(shù)據(jù)加密等
          List?orderList?=?orderMapper.query(order);
          //?..?其他查詢邏輯

          查詢底層使用 mybatis 動態(tài) sql 功能,樣例如下:

          <select?id="query"?parameterType="order"?resultMap="orderResultMap">
          ????select?orderId,amt,orderInfo?//?還有其他信息
          ????from
          ????Order
          ????<where>
          ????????<if?test="orderId?!=?null">
          ????????????orderId?=?#{orderId}
          ????????if>
          ????????<if?test="amt?!=?null">
          ????????????AND?amt?=?#{amt}
          ????????if>
          ???????.....?其他條件
          ????where>
          select>

          上面的代碼很簡單,由于傳入 mybatis 查詢語句參數(shù)都未設(shè)置,從而導(dǎo)致生成的 sql 缺失了查詢條件了,查詢?nèi)怼?/p>

          而由于訂單表的數(shù)據(jù)非常多,全表查詢返回的數(shù)據(jù)將會源源不斷的加載到應(yīng)用內(nèi)存中,從而引發(fā) 「Full GC」,導(dǎo)致應(yīng)用陷入長時間的 「stop the world」。

          由于 Dubbo 線程也被暫停了,接收到正常的調(diào)用無法及時返回結(jié)果,從而引發(fā)服務(wù)消費(fèi)者超時。

          另一方面,由于應(yīng)用不斷接受請求,而大量 Dubbo 線程不能及時處理調(diào)用,從而導(dǎo)致 Dubbo 線程池中線程資源被耗盡,后續(xù)請求將會被直接拒絕。

          最后最后,系統(tǒng)應(yīng)用內(nèi)存實(shí)在無法再加載任何數(shù)據(jù),于是拋出上文中 「OOM」 異常。

          這張圖真的體現(xiàn)小黑哥當(dāng)時心態(tài)變化

          問題本質(zhì)原因是找到了,那為什么之前查詢都沒事,而這次突然就沒傳值了呢?

          原來是因?yàn)榍岸隧撁娓膭?,?dǎo)致傳入的查詢參數(shù)為空?。。?/p>

          前端頁面遲遲不能顯示查詢的訂單,用戶一般會選擇重試,然后又未傳入查詢參數(shù),再一次加重應(yīng)用的情況,雪上加霜。

          擴(kuò)展思考

          上面的問題,我們只要重啟應(yīng)用,暫時還是能解決問題。想象一下如果使用動態(tài) sql 發(fā)生在其他場景,會怎么樣?

          假設(shè)用戶的余額表使用動態(tài) sql 更新,這時如果條件丟失將會導(dǎo)致全部用戶的余額都會發(fā)生了變化。如果是余額變多,那可能還好。但是如果余額是變少的,那真的很可能演變成社會事故了~

          我們再假設(shè)下,如果某些配置表使用了動態(tài) sql 物理刪除數(shù)據(jù),這時如果條件丟失將會導(dǎo)致全表數(shù)據(jù)被刪。數(shù)據(jù)如果都沒了,沒什么好說了,跑路吧~

          可以看到,更新/刪除這類動態(tài) sql,如果丟失了條件,那導(dǎo)致的危害將會很大,業(yè)務(wù)可能都會被停擺。

          解決辦法

          那有沒有什么辦法解決這些問題?

          「很簡單,不要用動態(tài) sql 了,直接手寫吧~」

          emm!你們先把刀放下,我開個玩笑的~

          雖然上面的問題確實(shí)是動態(tài) sql 引起的,但是本質(zhì)原因我覺得還是使用不當(dāng)引起的。

          我們肯定不能因噎廢食,自廢武功,從此退回到「刀耕火種」時代,手寫 sql。

          好了,不說廢話了,解決動態(tài) sql 帶來潛在的問題,我覺得可以從兩方面下手:

          第一、改變意識形態(tài),科普動態(tài) sql 可能引發(fā)的問題,讓所有開發(fā)對這個問題引起重視。

          只有當(dāng)我們意識動態(tài) sql 可能引發(fā)的問題,我們才有可能在開發(fā)過程去思考,這么寫會不會被帶來問題。

          「這一點(diǎn),我覺得真的很重要?!?/strong>

          第二,針對實(shí)際的業(yè)務(wù)場景提供可控的查詢條件,并且對外接口一定要做好必要的參數(shù)校驗(yàn)。

          我們要從實(shí)際的業(yè)務(wù)場景出發(fā)分析對外需要提供那些條件,原則上主庫表必須按照主鍵或唯一鍵查詢單條,或者使用相關(guān)的外鍵查詢多條。比如說,訂單表查詢支付單號這類主鍵查詢。

          另外針對這些查詢條件,接口層一定要做好的必要的參數(shù)校驗(yàn)。如果參數(shù)未傳,直接打回,防患于未然。

          如果真的有需要查詢多條數(shù)據(jù)后臺需求,這類查詢不需要很高實(shí)時性,那么我們其實(shí)可以與上面應(yīng)用查詢剝離開來,并且查詢使用從庫。

          第三,增加一些工具類預(yù)防插件。

          比如我們可以在 mybatis 增加一個插件,檢查執(zhí)行的 sql 是否帶有 where 關(guān)鍵字,若不存在直接攔截。

          mybatis 攔截器如下:

          @Intercepts({
          ????????@Signature(type?=?Executor.class,?method?=?"query",?args?=?{MappedStatement.class,?Object.class,
          ????????????????RowBounds.class,?ResultHandler.class}),
          ????????@Signature(type?
          =?Executor.class,?method?=?"query",?args?=?{MappedStatement.class,?Object.class,
          ????????????????RowBounds.class,?ResultHandler.class,?CacheKey.class,?BoundSql.class}),
          ????????@Signature(type?
          =?Executor.class,?method?=?"update",?args?=?{MappedStatement.class,
          ????????????????Object.class})})
          @Slf4j
          public?class?CheckWhereInterceptor?implements?Interceptor?
          {

          ????private?static?final?String?WHERE?=?"WHERE";

          ????@Override
          ????public?Object?intercept(Invocation?invocation)?throws?Throwable?{
          ????????//獲取方法的第0個參數(shù),也就是MappedStatement。@Signature注解中的args中的順序
          ????????MappedStatement?mappedStatement?=?(MappedStatement)?invocation.getArgs()[0];
          ????????//獲取sql命令操作類型
          ????????SqlCommandType?sqlCommandType?=?mappedStatement.getSqlCommandType();
          ????????final?Object[]?queryArgs?=?invocation.getArgs();
          ????????final?Object?parameter?=?queryArgs[1];
          ????????BoundSql?boundSql?=?mappedStatement.getBoundSql(parameter);
          ????????String?sql?=?boundSql.getSql();
          ????????if?(Objects.equals(SqlCommandType.DELETE,?sqlCommandType)
          ????????????????||?Objects.equals(SqlCommandType.UPDATE,?sqlCommandType)
          ????????????????||?Objects.equals(SqlCommandType.SELECT,?sqlCommandType))?{
          ????????????//格式化sql
          ????????????sql?=?sql.replace("\n",?"");
          ????????????if?(!StringUtils.containsIgnoreCase(sql,?WHERE))?{
          ????????????????sql?=?sql.replace("?",?"");
          ????????????????log.warn("SQL?語句沒有where條件,禁止執(zhí)行,sql為:{}",?sql);
          ????????????????throw?new?Exception("SQL語句中沒有where條件");
          ????????????}
          ????????}
          ????????Object?result?=?invocation.proceed();
          ????????return?result;
          ????}

          ????@Override
          ????public?Object?plugin(Object?target)?{
          ????????return?Plugin.wrap(target,?this);
          ????}

          ????@Override
          ????public?void?setProperties(Properties?properties)?{

          ????}
          }

          上面的代碼其實(shí)還是比較粗糙,各位可以根據(jù)各自的業(yè)務(wù)增加相應(yīng)的預(yù)防措施。

          小結(jié)

          今天的文章,從真實(shí)的例子出發(fā),引出了動態(tài) sql 潛在的問題,主要想讓大家意識到這方面的問題。從而在今后使用動態(tài) sql 的過程中更加小心。

          好了,我是樓下小黑哥,一位資深的踩坑工程師。

          2020 Github 年度報告

          2020 年即將結(jié)束,這是特別的一年,相信每個人都有自己不平凡的經(jīng)歷

          作為開發(fā)者的我們,到了年底也迎來了盤點(diǎn)一年收獲的時刻

          GitHub 作為全球最大的開源協(xié)作平臺,相信不少開發(fā)者都貢獻(xiàn)過自己的代碼,作為開發(fā)者的你是否對以下問題好奇?

          • 我使用過多少種編程語言?
          • 我的代碼量是多少?
          • 我的年度語言是什么?
          • 我是否能在 GitHub 報告排行榜上占據(jù)一席之地?

          本篇文章將給你全部答案!2020 年度 GitHub 代碼報告即將上演!

          領(lǐng)取方式:

          「關(guān)注公眾號「小黑十一點(diǎn)半」,回復(fù)「報告」即可領(lǐng)取,還可以查看 GitHub 報告排行榜」

          嘿嘿,下面是小黑哥的報告,這一年為了寫文章,搞了幾個開源工程,還是提交挺多代碼的。





          「[吃瓜]說起來,今年最大的成就,給 Dubbo 改過一個bug,修改了兩處開源文檔的?!?/strong>





          往期精彩回顧




          老大甩給我 30G 文件,讓小黑哥幾天內(nèi)全部導(dǎo)入到數(shù)據(jù)庫|文末送書
          IDEA 2020年最后一個版本更新了,機(jī)器學(xué)習(xí)都整上了
          數(shù)據(jù)庫讀寫分離這個坑,讓剛?cè)肼毜奈乙荒樸卤疲?/a>



          瀏覽 54
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                    <th id="afajh"><progress id="afajh"></progress></th>
                    不卡a片在线观看 | 麻豆五月婷婷 | 亚洲阿v视频 | 秋霞成人无码 | 国内视频精品 |