在應用開發中ORM(我用的是NHibernate)框架使用是否得當將直接影響到我們的程序的效率,其中的兩個概念:
- lazy懶加載
- select N+1問題
在性能調優中起到了至關重要的作用。
在以前我個人混亂的概念裏,啓用懶加載時,由於關聯對象要到需要的時候才查詢,所以sql會被拆分成兩次查詢。所以爲了解決select N+1問題,需要將lazy設置爲false。在實際開發使用中,這混亂的概念讓我吃了不少苦頭。
今日仔細測試發現,事實並非如此,lazy的設置只是決定了關聯信息的加載時間(一開始就加載還是需要時加載)。而是否使用關聯查詢是由fetch屬性決定。
下面就讓我們看一下lazy與fetch兩個屬性組合使用的效果。使用兩個實體WallParam(參數)與WallParamGroup(參數組),其中關係爲多對一。
CASE 1:將lazy設爲proxy
<!--多對一關係-->
<many-to-onename="paramGroup"column="group_id"not-null="true"class="Model.WallParamGroup,Model"lazy="proxy"/>
當查詢WallParam信息時,lazy配置起作用了,執行的sql如下(沒有同時查出WallParamGroup, 該信息被延後了):
然而當執行查詢並同時獲取關聯的WallParamGroup信息時,nhibernate先後執行兩句sql來進行查詢:
結論:lazy的配置確實延後了關聯信息的加載,關聯信息只有被用的時候纔會去數據庫查詢。因此查詢語句也自然是拆分的sql。
CASE 2:此時已經將lazy設爲false
<!--多對一關係-->
<many-to-one name="paramGroup" column="group_id" not-null="true" class="Model.WallParamGroup,Model" lazy="false"/>
當查詢WallParam信息時,WallParamGroup的信息會同時加載出來。但是令人意外的是,執行的sql並不像我們認爲的是一句關聯查詢。而是任然執行了兩個拆分的sql,如下:
結論:lazy能延後關聯對象的數據加載,卻不能決定關聯數據的獲取方式(關聯查詢/拆分查詢)。因此僅僅使用lazy屬性並不能解決Select N+1問題。
CASE 3:保持lazy設爲false,同時將fetch設置爲join(fetch默認是“select”)
<!--多對一關係-->
<many-to-onename="paramGroup"column="group_id"not-null="true"class="Model.WallParamGroup,Model"lazy="false"fetch="join"/>
當查詢WallParam信息時,WallParamGroup的信息會同時加載出來。並且從生成的sql語句看,確實使用了關聯查詢,一句sql就取出了所有信息:
結論:設置fetch=“join”改變了查詢行爲,使得關聯對象能夠通過join查詢得到,從而解決了Select N+1問題。
CASE 4:那麼fetch設置爲join時,將lazy設爲proxy又會有什麼結果呢
<!--多對一關係-->
<many-to-onename="paramGroup"column="group_id"not-null="true"class="Model.WallParamGroup,Model"lazy="false"fetch="join"/>
當查詢WallParam信息時,WallParamGroup的信息會同時加載出來(這是因爲fetch指定了關聯查詢,所以關聯的WallParamGroup 信息已經被查詢出來,導致lazy失效)。並且從生成的sql語句看,確實使用了關聯查詢,一句sql就取出了所有信息:
結論:一旦設置fetch=“join”改變了查詢行爲(使用了關聯查詢),lazy懶加載就失去作用了(因爲關聯數據已經通過join查詢查出)。
總結:
- lazy設置僅僅指明瞭關聯對象信息的加載時機(是一開始就加載,還是需要時加載)。
- lazy加載只有在fetch=“select”(默認設置)時纔有效。
- fetch設置能夠改變關聯對象信息查詢的行爲(通過關聯查詢還是拆分查詢)。
- 解決Select N+1問題需要使用fetch=“join”。
所以個人認爲一種合理的做法就是,實體映射是保持fetch的默認設置(select),因爲只有這樣才能啓用懶加載。然後,當需要解決Select N+1問題以提高查詢效率的時候,在程序中使用如下代碼臨時改變查詢行爲:
ICriteria.SetFetchMode("WallParamGroup", FetchMode.Join)