在应用开发中ORM(我用的是NHibernate)框架使用是否得当将直接影响到我们的程序的效率,其中的两个概念:
- lazy懒加载
- select N+1问题
在性能调优中起到了至关重要的作用。
在以前我个人混乱的概念里,启用懒加载时,由于关联对象要到需要的时候才查询,所以sql会被拆分成两次查询。所以为了解决select N+1问题,需要将lazy设置为false。在实际开发使用中,这混乱的概念让我吃了不少苦头。
今日仔细测试发现,事实并非如此,
lazy的设置只是决定了关联信息的加载时间(一开始就加载还是需要时加载)。
而是否使用关联查询是由fetch属性决定。
下面就让我们看一下lazy与fetch两个属性组合使用的效果。使用两个实体WallParam(参数)与WallParamGroup(参数组),其中关系为多对一。
CASE 1:将lazy设为proxy
<!--
多对一关系
-->
<
many-to-one
name
=
"
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-one
name
=
"
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-one
name
=
"
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)