• [NHibernate] Guid 作主键速度超慢的背后


    http://blog.csdn.net/educast/article/details/6602353

    最近遇到了一个让人抓狂的性能问题。生产环境里有一张表的数据量目前达到了 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.String [2],把 DbType.String 映射为 nvarchar2 [3]。把 DbType.AnsiString 映射为 varchar2 [4]
    所以对于查询比较简单,只要把 HQL 的参数类型指定为 AnsiString 就行了。

    var query = Session.CreateQuery(@"select t from Region as t
                                       where t.Id = :Id")
                         .SetAnsiString("Id", id);
    var query = Session.CreateQuery(@"select t from Region as t
                                       where t.Id in (:Ids)")
                         .SetParameterList("Ids", ids.ToList(), NHibernateUtil.AnsiString);

    但是如何设置 Update 和 Delete 语句的参数类型呢?这里有个小小的秘技,把映射文件里的属性类型指定为“AnsiString”即可。

    public class RegionMap : TreeNodeMap<Region>
    {
        public RegionMap()
        {
            Table("INFRA_REGION");
            Id(t => t.Id, "REGION_ID").CustomType("AnsiString");
            ...
        }
    }

    注意 一定要使用 CustomType() 而不是 CustomSqlType()。
    当然了,要是把每一个配置文件都改一遍实在很烦,好像项目使用了 Fluent  NHibernate,只要添加一个 IdConvention 就行了。

    public class IdConvention : FluentNHibernate.Conventions.IIdConvention
    {
        public void Apply(FluentNHibernate.Conventions.Instances.IIdentityInstance instance)
        {
            instance.CustomType("AnsiString");
        }
    }

    想要彻底一点的话,可以再加一个 string 类型的 property 的 convention。

    public class StringPropertyConvention : IPropertyConvention, IPropertyConventionAcceptance
    {
        public void Accept(IAcceptanceCriteria<IPropertyInspector> criteria)
        {
            criteria.Expect(x => x.Property.PropertyType == typeof(string));
        }
      
        public void Apply(IPropertyInstance instance)
        {
            instance.CustomType("AnsiString");
        }
    }

    把这两个 Convention 加到配置里面:

    Session["SessionFactory"] = Fluently.Configure()
              .Database(OracleClientConfiguration.Oracle10
                  .Dialect<Oracle10gDialect>()
                  .ConnectionString("User ID=iBlast;Password=不可说;Data Source=Moki")
                  .QuerySubstitutions("true 1, false 0, yes 'Y', no 'N'")
                  .UseOuterJoin()
                  .ProxyFactoryFactory<ProxyFactoryFactory>()
                  .AdoNetBatchSize(1000)
                  .Driver<OracleClientDriver>())
              .Mappings(m => { m.HbmMappings.AddFromAssembly(Assembly.Load("Infrastructure.Repositories")); 
                               m.FluentMappings.AddFromAssembly(Assembly.Load("Infrastructure.Repositories"))
                                               .Conventions.Add<EnumConvention>()
                                               .Conventions.Add<HasManyConvention>()
                                               .Conventions.Add<HasManyToManyConvention>()
                                               .Conventions.Add<StringPropertyConvention>()
                                               .Conventions.Add<IdConvention>()
                                               .ExportTo(@"F: emp"); })
              .BuildSessionFactory();

    注意倒数第二行的 .ExportTo(@"F: emp") 是为了测试一下生成的映射文件对不对而把映射文件输出到了 “F: emp”,映射文件应该像这个样子: 

    <hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" default-access="property" auto-import="true" default-cascade="none" default-lazy="true">
      <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">
        <id name="Id" type="AnsiString">
          <column name="REGION_ID" />
          <generator class="assigned" />
        </id>
        <version name="Version" type="System.Int32, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
          <column name="Version" />
        </version>
        <property name="CreateTime" type="System.DateTime, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
          <column name="CREATETIME" />
        </property>
        <property name="Name" type="AnsiString">
          <column name="NAME" />
        </property>
        ...
      </class>
    </hibernate-mapping>


    [1] 之所以使用 varchar2 而不是 nvarchar2,除了考虑 varchar2 可以节省空间之外,主要是为了避免 nvarchar2 排序时的性能问题
    [2] 见 NHibernate-2.1.0.GA-srcsrcNHibernateTypeTypeFactory.cs 第 197 行。
    [3] 见 NHibernate-2.1.0.GA-srcsrcNHibernateDialectOracle8iDialect.cs 第 92 行。
    [4] 见 NHibernate-2.1.0.GA-srcsrcNHibernateDialectOracle8iDialect.cs 第 88 行。

  • 相关阅读:
    JDBC数据库访问操作的动态监测 之 p6spy
    ZeroC Ice启用SSL通讯的配置
    heartbeat在yum系发行版本的处理资料
    VisualStudio 调试Linux
    JVM之上的语言小集
    程序人生【一些经典的资料】
    读史知今、以史为鉴 【技术商业化】
    ipython notebook 浏览器中编写数学公式和现实
    大数据 云计算 等搜集的资料
    GO 1.5 代码编译安装 [centos7 64位]
  • 原文地址:https://www.cnblogs.com/aaa6818162/p/4631441.html
Copyright © 2020-2023  润新知