NHibernate导致查询效率低速度慢的原因
2015-03-02 11:32阅读:
转载自
http://blog.csdn.net/educast/article/details/6602353
[NHibernate]
Guid 作主键速度超慢的背后
------------------------------------------------------------
最近遇到了一个让人抓狂的性能问题。生产环境里有一张表的数据量目前达到了 70
万条。结果发现无论是匹配主键的查询还是更新,执行一条语句居然需要 3.5 秒!如果把 NH Prof 中截获的 SQL 语句拿到
PL/SQL Developer 里执行,就只需几十毫秒。一开始还以为是NH的问题,后来发现其实另有隐情。
介绍一下环境先。数据库使用 Oracle10g,所有字符类型的字段都是 varchar2
[1]。所有的主键都使用
Guid,在数据库里是 varchar2(36) 类型,相应的,实体的 Id 属性的类型是 string。ORM 使用的是
NHibernate 2.1.0 和 FluentNHibernate1.1。
经过一番排查之后发现,问题的根源是 NH 将 SQL 语句传递给 Oracle 时,所有字符型的参数都是 nvarchar2
类型,而数据库里对应的字段却是 varchar2 类型,这将导致 Oracle
无法使用索引,终于造成全表扫描,所以数据量稍大就慢得不行。
第一种解决方法是,把数据库中所有的字符型字段的类型由 varchar2 更改为
nvarchar2,出于种种原因我们不希望这么做。
第二种解决方法是,让 NH
把 varchar2 作为参数类型传递给 Oracle。
事实上,NH 默认把 .net 的 string 映射为 DbType.Stri
ng
[2],把
DbType.String 映射为 nvarchar2
[3]。把
DbType.AnsiString 映射为 varchar2
[4]。
所以对于查询比较简单,只要把 HQL 的参数类型指定为 AnsiString 就行了。
view sourceprint?
var query = Session.CreateQuery(@'select t
from Region as t |
.SetAnsiString('Id',
id); |
view sourceprint?
var query = Session.CreateQuery(@'select t
from Region as t |
.SetParameterList('Ids',
ids.ToList(), NHibernateUtil.AnsiString); |
但是如何设置 Update 和 Delete
语句的参数类型呢?这里有个小小的秘技,把映射文件里的属性类型指定为“AnsiString”即可。
view sourceprint?
public class
RegionMap : TreeNodeMap |
Id(t => t.Id,
'REGION_ID').CustomType('AnsiString'); |
注意 一定要使用 CustomType() 而不是
CustomSqlType()。
当然了,要是把每一个配置文件都改一遍实在很烦,好像项目使用了 Fluent
NHibernate,只要添加一个
IdConvention 就行了。
view sourceprint?
public class
IdConvention :
FluentNHibernate.Conventions.IIdConvention |
public
void
Apply(FluentNHibernate.Conventions.Instances.IIdentityInstance
instance) |
instance.CustomType('AnsiString'); |
想要彻底一点的话,可以再加一个 string 类型的 property 的 convention。
view sourceprint?
public class
StringPropertyConvention : IPropertyConvention,
IPropertyConventionAcceptance |
public
void Accept(IAcceptanceCriteria
criteria) |
criteria.Expect(x => x.Property.PropertyType
==
typeof(string)); |
public
void Apply(IPropertyInstance
instance) |
instance.CustomType('AnsiString'); |
把这两个 Convention 加到配置里面:
view sourceprint?
Session['SessionFactory'] =
Fluently.Configure() |
.Database(OracleClientConfiguration.Oracle10 |
.ConnectionString('User
ID=iBlast;Password=不可说;Data Source=Moki') |
.QuerySubstitutions('true 1, false
0, yes 'Y', no 'N'') |
.Mappings(m => {
m.HbmMappings.AddFromAssembly(Assembly.Load('Infrastructure.Repositories'));
|
m.FluentMappings.AddFromAssembly(Assembly.Load('Infrastructure.Repositories')) |
.ExportTo(@'F:\temp');
}) |
注意倒数第二行的 .ExportTo(@'F:\temp') 是为了测试一下生成的映射文件对不对而把映射文件输出到了
“F:\temp\”,映射文件应该像这个样子:
view sourceprint?
'urn:nhibernate-mapping-2.2'
default-access='property'
auto-import='true'
default-cascade='none'
default-lazy='true'> |
<</code>class
xmlns='urn:nhibernate-mapping-2.2'
dynamic-insert='true'
dynamic-update='true'
mutable='true'
where='IsDelete=0'name='Dawn.HIS.Infrastructure.Core.Data.Region,
Infrastructure.Core, Version=1.0.0.0, Culture=neutral,
PublicKeyToken=null'table='INFRA_REGION'> |
'Version'
type='System.Int32, mscorlib,
Version=4.0.0.0, Culture=neutral,
PublicKeyToken=b77a5c561934e089'> |
'CreateTime'
type='System.DateTime, mscorlib,
Version=4.0.0.0, Culture=neutral,
PublicKeyToken=b77a5c561934e089'> |
'Name'
type='AnsiString'> |
[1] 之所以使用 varchar2 而不是 nvarchar2,除了考虑 varchar2
可以节省空间之外,主要是为了避免
nvarchar2 排序时的性能问题。
[2] 见
NHibernate-2.1.0.GA-src\src\NHibernate\Type\TypeFactory.cs 第 197
行。
[3] 见
NHibernate-2.1.0.GA-src\src\NHibernate\Dialect\Oracle8iDialect.cs
第 92
行。
[4] 见
NHibernate-2.1.0.GA-src\src\NHibernate\Dialect\Oracle8iDialect.cs
第
88
行。