爲什麼強烈建議你不要做聯表查詢?

前言

一直想要聊一聊關於開發中更建議使用單表查詢+代碼層組裝 or 聯表查詢 的問題,在開發中每個同學的開發中有各自的習慣,筆者在公司也和一些同事關於這方面有一些探討。

關於本文,更像是一些個人的看法,想到什麼說什麼,一定有不同的意見,歡迎大家留言,一起討論。

 

對比

在實際開發中,我們不可避免的要關聯幾張數據表來合成最終的展示數據,如:

select * from tag
join tag_post on tag_post.tag_id=tag.id
join post on tag_post.post_id=post.id
where tag.tag='mysql';

同樣的,我們可以用以下查詢來代替:

select * from tag where tag='mysql';

select * from tag_post where tag_id=1234;

select * from post where id in(123,456,567,9989,8909);

看似後者查詢步驟更多了,原本一個方法查詢就能出結果,現在直接變成三個。但是這樣做的好處是:

 

1、單表查詢更利於後續的維護。

在實際開發場景中,在代碼初步開發階段(如果攤上一個不太靠譜的產品),業務發生變動,某張表的結構發生變動,很可能整個join查詢都變得不可用,複雜的關聯查詢,在修改時,基本等於推倒重來。

但是如果我們使用了單表查詢,拆成上訴例子中的三個步驟,我們可能只需要修改其中的一個步驟即可,比較利於維護。

 

2、代碼可複用性高

這個不用多說,join聯表的SQL,基本不太可能被複用,但是拆分後的單表查詢,比如上面例子中,我查詢出tab數據,任何地方組裝需要tab數據,我都不需要再次做相關查詢,直接使用。

 

3、效率問題

join聯表查詢,小表驅動大表,通過索引字段進行關聯。如果表記錄比較少的話,效率還是OK的,有時效率超過單表查詢。但是如果數據量上去,多表查詢是笛卡爾乘積方式,需要檢索的數據是幾何倍上升的。另外多表查詢索引設計上也考驗開發者的功底,索引設計不合理,大數據量下的多表查詢,很可能把數據庫拖垮。

相比而言,拆分成單表查詢+代碼上組裝,業務邏輯更清晰,優化更方便,單個表的索引設計上也更簡單。用多幾行代碼,多幾次數據庫查詢換取這些優點,還是很值得的。

 

4、減少冗餘字段的查詢

在很多業務中,我們可能對某條記錄只需要查詢一次,此時如何使用關聯查詢,則不可避免的需要重複地訪問一部分數據,從而可能會加劇網絡和內存的消耗。

 

5、緩存利用率更高

比如上面查詢中的tag是不常變動的數據,緩存下來,每次查詢就可以跳過第一條查詢語句。而關聯查詢,任何一張表的數據變動都會引起緩存結果的失效,緩存利用率不會很高。

 

6、其他

數據庫資源比較寶貴,很多系統的瓶頸就在數據庫上,很多複雜的邏輯我們在Service做,不在數據庫處理會更好。

在後續數據量上去,需要分庫分表時,Join查詢更不利於分庫分表,目前MySQL的分佈式中間件,跨庫join表現不良。

單表查詢+代碼上組裝相當於解耦,現在開發中,我們常常使用各種ORM框架,不知道你的聯查orm給你搞成了什麼樣,你是很難直接優化。

以上理由,強烈推薦在今後的開發中,儘可能的使用單表查詢+代碼上組裝的方式。使用Stream lambda + mybatis plus + lombok, 酸爽!

 

單表 VS 聯表

基本就這些了,你的看法呢?

 

大家提了一些比較有意義的問題,留言回覆說不太清楚,繼續更新博客做一些說明。

首先,在實際開發中,還是應該多使用單表查詢,這樣無論在性能上或者後續維護上,都會比複雜的關聯查詢要好,很多高性能的應用都會對關聯查詢進行分解。但是針對一些不同的場景,也應該比較靈活的採取join查詢的方法,原則上join不要超過三張表。

 

數據庫連接次數過多,效率更低

單表查詢勢必要考慮2次連接帶來的開銷,但是,MySQL對連接/斷開是有處理的,而且網絡越來越快,相比單表查詢能夠讓緩存效率更高的這個優勢下,多兩次的連接所帶來的開銷基本可以忽略不計。(指一般業務下,具體問題還要具體分析)

 

多條件過濾+分頁難實現

這裏貼一個代碼片段:

無論是哪個表需要傳參查詢,最終都有一個符合條件的結果集,最終我們組合查詢後的結果集,多條件過濾無法實現的問題就不存在了。至於分頁,使用 com.baomidou.mybatisplus.core.metadata包下的IPage,很強大,誰用誰知道!

附代碼:

public PageResult<ApplyVO> searchApplyPage(Page<Apply> page, Integer approvalStatus, String proposerCard, String message){
        List<Long> ids = Optional.ofNullable(this.applyMapper.findMedicineApplyIds()).orElse(new ArrayList<>()).stream().map(Apply::getId).collect(Collectors.toList());

        List<ApplyVO> applyVOS = Lists.newArrayList();
        if(CollectionUtils.isEmpty(ids)){
            return PageResult.of(applyVOS,0);
        }
        Page<Apply> apps = this.applyMapper.selectPage(page, new QueryWrapper<Apply>().lambda()
                .in(Apply::getId,ids)
                .eq(!Objects.isNull(approvalStatus), Apply::getApprovalStatus, approvalStatus)
                .eq(!StringUtils.isEmpty(proposerCard), Apply::getProposerCard, proposerCard)
                .and(!StringUtils.isEmpty(message), i-> i.like(Apply::getTeamName, message).or()
                        .like( Apply::getProposer, message).or()
                        .like(Apply::getAthleteName, message)).orderByDesc(Apply::getApprovalNumber)
        );
        apps.getRecords().forEach(s->{
            List<MedicineDetail> medicineDetails = this.detailMapper.selectList(new QueryWrapper<MedicineDetail>().eq("apply_id", s.getId()));
            MedicineApplyVO medicineApplyVO = MedicineApplyVO.builder().accompany(s.getAccompany()).medicineDetails(medicineDetails).build();
            applyVOS.add(medicineApplyVO);

        });
        return PageResult.of(applyVOS,apps.getPages(),apps.getCurrent(),apps.getCurrent(), apps.getTotal());
}

 

排序分頁不同表的問題

這種,不考慮,直接join查詢,數據組裝或許能實現,但是沒必要搞這麼複雜,推薦大家使用分表查詢,但不是所有的都必須分表,一看就是聯表更方便的,我們一般都直接聯表寫SQL。

基本都解答完了,對於一頭磕在鍵盤上的同學的留言,說的完全有道理,虛心接受,上一篇文章其實主要是推薦大家在開發中儘量使用單表查詢,單表查詢優點>缺點,既然是鼓動大家使用單表查詢,我必然是介紹它的優點,哈哈。

但是在實際開發中,一些場景明顯使用聯表查詢更方便,我也會去使用聯表的方式,比如多喝熱水同學的留言問題,不用考慮,直接聯表查。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章