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:\temp\"); })
          .BuildSessionFactory();

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

<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-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 行。

[NHibernate] Guid 作主键速度超慢的背后的更多相关文章

  1. 使用Guid做主键和int做主键性能比较

    使用Guid做主键和int做主键性能比较 在数据库的设计中我们常常用Guid或int来做主键,根据所学的知识一直感觉int做主键效率要高,但没有做仔细的测试无法 说明道理.碰巧今天在数据库的优化过程中 ...

  2. mysql批量insert速度超慢

    在进行大批量数据insert的时候,我使用的是hibernate的进行save,而数据库采用mysql.但是在save的时候,速度很慢.         刚开始以为是MYSQL进行DNS解析的问题,于 ...

  3. GUID做主键真的合适吗

    在一个分布式环境中,我们习惯使用GUID做主键,来保证全局唯一,然后,GUID做主键真的合适吗? 其实GUID做主键本身没有问题,微软的很多项目自带DB都是使用GUID做主键的,显然,这样做是没有问题 ...

  4. 基于SqlSugar的开发框架循序渐进介绍(4)-- 在数据访问基类中对GUID主键进行自动赋值处理

    我们在设计数据库表的时候,往往为了方便,主键ID一般采用字符串类型或者GUID类型,这样对于数据库表记录的迁移非常方便,而且有时候可以在处理关联记录的时候,提前对应的ID值.但有时候进行数据记录插入的 ...

  5. SQLSERVER如何使用递增排序的GUID做主键

    场景: 产品表数据量较大想用Guid做表的主键,并在此字段上建立聚簇索引. 因为Guid是随机生成的,生成的值大小是不确定的,每次生成的数可能很大,也可能很小.这样会影响插入的效率 1.NEWSEQU ...

  6. 开发反模式(GUID) - 伪键洁癖

    一.目标:整理数据 有的人有强迫症,他们会为一系列数据的断档而抓狂. 一方面,Id为3这一行确实发生过一些事情,为什么这个查询不返回Id为3的这一行?这条记录数据丢失了吗?那个Column到底是什么? ...

  7. 主键、外键、超键、候选键的区别【Written By KillerLegend】

    先说一下属性的定义: 表的每一行对应一个元组,表的每一列对应一个域.由于域可以相同,为了加以区分,必须对每列起一个唯一的名字,称为属性(Attribute). 再来看看几个键的定义: 超键:在关系模式 ...

  8. NHibernate联合主键详细示例

    使用NHibernate实现一对多,多对一的关联很是简单,可如果要用复合主键实现确实让人有些淡淡的疼.虽然很淡疼但还是要去抹平这个坑,在下不才,愿意尝试. 以示例进入正文,源码下载地址: 一.数据表关 ...

  9. MyBatis 返回insert操作主键

        应用场景 在向数据库插入数据时,需要保留插入数据的id,以便进行后续的update操作或者将id存入其他表作为外键.但是,在默认情况下,insert操作返回的是一个int值,它并非表示主键id ...

随机推荐

  1. SVN自动生成版本号信息

        在平时的多版本开发过程中,需要通过版本号来定位到源码版本,便于定位问题.常规工程实践是设置版本号为X.Y.Z.N,一般X表示主版本号,Y表示子版本号,我一般将Z设为0,N为本次提交的SVN版本 ...

  2. win8 关闭防火墙

    http://jingyan.baidu.com/article/b87fe19eddb4da5218356894.html

  3. Windows上使用sqlite3

    安装 去官网http://www.sqlite.org/download.html下载Windows下安装包,下载后,解压,设置环境变量 例如放在D:\sqlite3下,将D:\sqlite3加入环境 ...

  4. Android开发学习笔记-显示对话框

    private void ShowUpdateDialog() { Log.i("version", "shengji"); AlertDialog.Build ...

  5. springmvc表单验证

    http://blog.csdn.net/daryl715/article/details/1645880 http://blog.csdn.net/shuwei003/article/details ...

  6. phpVirtualBox – 用浏览器操作虚拟机

    摘自:https://code.google.com phpVirtualBox 一个开源的,VirtualBox的用户界面,用PHP编写的AJAX实现.作为一个现代的Web界面,它允许你远程访问和控 ...

  7. SpringBoot thymeleaf模板版本,thymeleaf模板更换版本

    SpringBoot thymeleaf模板版本 thymeleaf模板更换版本 修改thymeleaf模板版本 ================================ ©Copyright ...

  8. [Ubuntu] 如何设置静态 IP 和 DNS

    编辑 /etc/network/interfaces 来设置 IP 和 DNS 解析服务器: # interfaces() ) and ifdown() auto lo iface lo inet l ...

  9. 如何构建日均千万PV Web站点 (三) Sharding

    其实国内许多大型网站为了应对日益复杂的业务场景,通过使用分而治之的手段将整个网站业务分成不同的产品线,比如说国内那些大型购物交易网站它们都将自己的网站首页.商铺.订单.买家.卖家等拆分不同的产品线,分 ...

  10. 用shell查找某目录下的最大文件

    这是一个很有趣的问题,因为作为一个shell菜鸟,我第一时间是没有任何想法的.心里纳闷为什么这样的操作Linux居然没有直接的命令实现这样的查询. 很自然地,第一感觉就是用awk去实现,因为菜鸟我看a ...