<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    Sky's blog

    我和我追逐的夢(mèng)

    常用鏈接

    統(tǒng)計(jì)

    其他鏈接

    友情鏈接

    最新評(píng)論

    編碼最佳實(shí)踐(6)--那些年,我們一起建的索引

        前幾次的編碼最佳實(shí)踐系列,我們都著眼于Java代碼,今天我們換個(gè)話題,看看另外一個(gè)領(lǐng)域,和Java代碼大相徑庭的SQL。 

        這次作為素材出場(chǎng)的,是來(lái)自項(xiàng)目中的一段SQL,用于BlackWhiteList Adapter,在每次請(qǐng)求時(shí)檢查一下,看當(dāng)前用戶是否在黑白名單中。 

        先介紹一下數(shù)據(jù)庫(kù)結(jié)構(gòu),很簡(jiǎn)單的三張表:BWLIST記錄黑白名單信息,BWLISTXADDRESS記錄每個(gè)黑白名單下的地址列表,BWLISTXAPPLICATION 記錄每個(gè)application關(guān)聯(lián)到的黑白名單。查詢時(shí)有三個(gè)輸入:PARTNER_ID和APP_ID是當(dāng)前application的唯一標(biāo)識(shí),通過(guò)這兩個(gè)參數(shù)就可以在BWLISTXAPPLICATION中找到對(duì)應(yīng)的黑白名單的id(可能有多個(gè)),然后通過(guò)黑白名單的id就可以在BWLISTXADDRESS找到對(duì)應(yīng)的所有地址,結(jié)合第三個(gè)參數(shù)ADDRESS就可以得知到當(dāng)前地址是否在黑背名單中。 

        還是直接看SQL吧,典型的3表聯(lián)合查詢: 

    String sql = "select A2.ID, A2.ADDRESS, A2.ADDRESS_TYPE, A2.DESCRIPTION, A1.type BWLIST_TYPE, A3.LIST_LEVEL, A3.SC_ID, A3.PARTNER_ID, A3.APP_ID 
    from BWLIST A1, BWLISTXADDRESS A2, BWLISTXAPPLICATION A3 
    where A1.id = A2.BWLIST_ID  and A2.BWLIST_ID = A3.BWLIST_ID  
            and A3.PARTNER_ID = ?  and A3.APP_ID = ? 
            and (INSTR(?, A2.ADDRESS ) > 0)"; 

        直接看where 后面的內(nèi)容,"A1.id = A2.BWLIST_ID  and A2.BWLIST_ID = A3.BWLIST_ID "好理解, BWLISTXADDRESS和BWLISTXAPPLICATION兩個(gè)表的BWLIST_ID字段是外鍵,對(duì)應(yīng)到表BWLIST的主鍵ID,用來(lái)做關(guān)聯(lián)。后面的"A3.PARTNER_ID = ?  and A3.APP_ID = ? "是用來(lái)唯一標(biāo)識(shí)當(dāng)前application,為了加速查詢,建有一個(gè)PARTNER_ID+APP_ID的索引:UNIQUE INDEX SYS_C0098362 (APP_ID(150), SP_PK, BWLIST_ID)。 

        關(guān)鍵在最后一個(gè)where條件:(INSTR(?, A2.ADDRESS ) > 0)"。這里使用INSTR()而不是簡(jiǎn)單的=,是考慮到地址可能有多種格式,比如"13900000000","tel:13900000000", "tel:+8613900000000",實(shí)際都是一個(gè)號(hào)碼。因此考慮在數(shù)據(jù)庫(kù)將A2.ADDRESS保存為"13900000000",這樣無(wú)論當(dāng)前輸入的地址格式是"13900000000","tel:13900000000", 還是"tel:+8613900000000",都可以被正確處理。 

        同樣為了加速查詢,開(kāi)發(fā)的同事為A2.ADDRESS這個(gè)字段增加了索引:UNIQUE INDEX SYS_C0098354 (ADDRESS(250), BWLIST_ID)。 

        這樣在上述幾個(gè)索引的支持下,前面的sql語(yǔ)句的where條件,按說(shuō)基本都被索引優(yōu)化到了 ————— 但是,相信一些有經(jīng)驗(yàn)的同事可能第一時(shí)間就已經(jīng)反映過(guò)來(lái),最后的這個(gè)基于ADDRESS字段的索引,有問(wèn)題! 

        問(wèn)題出在(INSTR(?, A2.ADDRESS ) > 0),INSTR()是一個(gè)SQL函數(shù),作為一個(gè)基本常識(shí),大家都知道的:如果索引列是SQL函數(shù)的參數(shù),那么索引在查詢時(shí)是用不上的。 

        很遺憾,當(dāng)時(shí)編寫這個(gè)SQL的同學(xué)可能不知道或者一時(shí)沒(méi)有反應(yīng)過(guò)來(lái),結(jié)果上述的SQL被寫入到產(chǎn)品。隨后更糟糕的是,在壓力測(cè)試中,居然沒(méi)有被發(fā)現(xiàn),原因是測(cè)試時(shí)使用的數(shù)據(jù)規(guī)模太小,只為被測(cè)試的黑白名單準(zhǔn)備了幾十個(gè)地址,所以雖然索引無(wú)法被利用,但是對(duì)于區(qū)區(qū)幾十條記錄,不走索引反而能更快一些...... 就這樣逃過(guò)測(cè)試,發(fā)布并部署運(yùn)行于客戶線上。 

        然后,后面的事情就可以想象了,客戶實(shí)際跑的時(shí)候,BWLISTXADDRESS中記錄的條數(shù)遠(yuǎn)不是幾十,而是幾十萬(wàn),幾百萬(wàn)......而黑白名單過(guò)濾功能一旦開(kāi)啟,是每個(gè)請(qǐng)求都要檢查一次,上面的SQL每次都要執(zhí)行一次。于是數(shù)據(jù)庫(kù)理所當(dāng)然的頂不住,整個(gè)系統(tǒng)的速度都被拖累,客戶就抱怨說(shuō)黑白名單開(kāi)啟之后性能出現(xiàn)大幅下降。而痛苦的是我們自己測(cè)試時(shí)不能重現(xiàn)問(wèn)題,嗯,用那個(gè)只有幾十個(gè)地址的名單當(dāng)然重現(xiàn)不出來(lái)...... 

        總之這個(gè)小bug引來(lái)了后面一堆的事情,我們不繼續(xù)吐糟,回頭來(lái)看看,問(wèn)題是如何一步一步的產(chǎn)生: 

    1. 開(kāi)發(fā)人員犯錯(cuò),常識(shí)性的小錯(cuò)誤:索引對(duì)SQL函數(shù)無(wú)效 
    2. 對(duì)于性能敏感的SQL,沒(méi)有做慎重的處理:如果當(dāng)時(shí)有用查詢分析器看一下執(zhí)行計(jì)劃,就可以避免出現(xiàn)類似的索引失效而不自知的情況;如果有其他有經(jīng)驗(yàn)的同事review這個(gè)SQL,也可以在早期發(fā)現(xiàn)問(wèn)題 
    3. 性能測(cè)試時(shí)數(shù)據(jù)建模失誤,沒(méi)有模擬到真實(shí)線上數(shù)據(jù)的規(guī)模,以至于最后一道關(guān)卡被突破,讓性能問(wèn)題逃過(guò)了性能測(cè)試 

        第3條是另外一個(gè)話題,我們這里重點(diǎn)來(lái)看1和2: 

        1的問(wèn)題本質(zhì)上是一個(gè)老生常談的問(wèn)題:如何避免在同一個(gè)坑中跌倒多次?這里所說(shuō)的同一個(gè)坑,針對(duì)不同的對(duì)象有不同的含義:對(duì)于個(gè)人,上次犯下的錯(cuò)誤下次會(huì)不會(huì)還繼續(xù)?對(duì)于一個(gè)團(tuán)隊(duì),A同學(xué)出錯(cuò)的地方B同學(xué)是否能避免?對(duì)于整個(gè)公司,A產(chǎn)品線遇到的問(wèn)題B產(chǎn)品線能不能有所借鑒? 

        可以說(shuō),我們現(xiàn)在的這個(gè)編碼最佳實(shí)踐的系列,就是為了解決類似的問(wèn)題:將我們不同的產(chǎn)品線犯下的一些典型問(wèn)題總結(jié)下來(lái),分享給其他人,避免同一個(gè)坑不停的有人踩的尷尬和無(wú)奈。 

        而2的問(wèn)題在于我們的WOW(Way Of Working)還不夠完善,對(duì)于性能敏感的關(guān)鍵代碼,應(yīng)該保持足夠的謹(jǐn)慎和細(xì)致,類似的每次查詢都要執(zhí)行一次的SQL,怎么都要看看執(zhí)行計(jì)劃才能放心寫入產(chǎn)品。我們也應(yīng)該有完善的code review機(jī)制來(lái)保證當(dāng)有疏漏的時(shí)候應(yīng)該能及時(shí)補(bǔ)救。 

        最后我們?cè)倩氐皆紗?wèn)題,關(guān)于這個(gè)SQL,我們現(xiàn)在知道INSTR()函數(shù)用不上索引,通常的解決方案是使用函數(shù)索引,但是對(duì)于"INSTR(?, A2.ADDRESS ) > 0",函數(shù)索引也無(wú)能為力。因此只好修改業(yè)務(wù)處理方式,不再在SQL查詢這個(gè)層次處理地址格式的問(wèn)題,將格式問(wèn)題拋給Java代碼:在數(shù)據(jù)庫(kù)中保存標(biāo)準(zhǔn)格式如"tel:13900000000",業(yè)務(wù)處理流程中對(duì)輸入的地址格式做標(biāo)準(zhǔn)化,將地址匹配簡(jiǎn)化為簡(jiǎn)單的"="操作,這樣可以極大的節(jié)約數(shù)據(jù)庫(kù)查詢開(kāi)銷。 

        最終修訂版本的SQL如下: 

    String sql = "select A2.ID, A2.ADDRESS, A2.ADDRESS_TYPE, A2.DESCRIPTION, A1.type BWLIST_TYPE, A3.LIST_LEVEL, A3.SC_ID, A3.PARTNER_ID, A3.APP_ID 
    from BWLIST A1, BWLISTXADDRESS A2, BWLISTXAPPLICATION A3 
    where A1.id = A2.BWLIST_ID  and A2.BWLIST_ID = A3.BWLIST_ID 
            and A3.PARTNER_ID = ?  and A3.APP_ID = ? 
            and A2.ADDRESS=?"; 

        這也是性能優(yōu)化的常見(jiàn)方式:將復(fù)雜的業(yè)務(wù)邏輯盡量留給Java代碼,盡可能的保持?jǐn)?shù)據(jù)庫(kù)操作的簡(jiǎn)單和高效。 

    posted on 2013-01-04 12:08 sky ao 閱讀(2192) 評(píng)論(1)  編輯  收藏 所屬分類: java

    評(píng)論

    # re: 編碼最佳實(shí)踐(6)--那些年,我們一起建的索引 2013-01-04 17:38 牌具

    文章不錯(cuò),文字不要弄背景就更好了  回復(fù)  更多評(píng)論   

    主站蜘蛛池模板: 国产一级a毛一级a看免费视频 | 日韩精品无码一区二区三区免费 | 成人片黄网站A毛片免费| 亚洲sm另类一区二区三区| 久久精品亚洲男人的天堂| 久久精品免费一区二区| 亚洲AV色欲色欲WWW| 亚洲免费观看视频| 成人免费毛片内射美女-百度| 黄网站在线播放视频免费观看 | 一区二区3区免费视频| 666精品国产精品亚洲| 日日夜夜精品免费视频| 亚欧免费无码aⅴ在线观看| 亚洲欧美aⅴ在线资源| 亚洲AV永久无码精品水牛影视| 永久免费毛片在线播放| 中文在线观看永久免费| 亚洲中文字幕久久无码| 亚洲成a人片在线观看无码| 麻豆国产人免费人成免费视频| 免费人成在线观看网站| 免费看内射乌克兰女| 亚洲一区电影在线观看| 日韩亚洲欧洲在线com91tv| 国产精品成人免费综合| 中文字幕乱码免费视频| 中文在线观看永久免费| 日韩色日韩视频亚洲网站| 亚洲国产视频网站| 亚洲国产精品福利片在线观看| 国产免费观看黄AV片| 国语成本人片免费av无码| 久久久久久久99精品免费| 国产99久久久久久免费看| 亚洲av无码成人精品区一本二本| 亚洲色大成网站www永久| 亚洲乱码一区二区三区在线观看 | 亚洲精品无码专区久久久 | 国产麻豆剧传媒精品国产免费| 国产成人精品免费视频网页大全|