[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 就行了。

view sourceprint?

var query = Session.CreateQuery(@"select t from Region as t
                                   where t.Id = :Id")
                     .SetAnsiString("Id", id);

view sourceprint?

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

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

view sourceprint?

public class RegionMap : TreeNodeMap<Region>
{
    public RegionMap()
    {
        Table("INFRA_REGION");
        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<IPropertyInspector> 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
              .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\”,映射文件应该像这个样子:

view sourceprint?

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

时间: 2024-10-15 13:19:31

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

网站的服务器经常打不开或者速度超慢

关头词排名呈现转变的原因除了SEO搜索引擎改变算法等原因外,收集营销常识上网为巨匠总结一下导致网站环节词排名下降的原因:原因一.处事器不不变用 pnc    网站的服务器经常打不开或者速度超慢,城市导致网站被降权,使网站枢纽词排名下降.原因二.优化过度这种原因是斗劲常见导致要害词排名下降的原因,那优化过度默示在哪些方面呢?有关键词密渡过年夜,外链暴涨,一次性去失踪或者改换大量友情链接等等,这样情形网上有良多介绍的文章,在这里就不累述了.原因三.大量收录外链被删除搜索引擎的数据一段时刻都邑更新一次

GUID做主键真的合适吗

在一个分布式环境中,我们习惯使用GUID做主键,来保证全局唯一,然后,GUID做主键真的合适吗? 其实GUID做主键本身没有问题,微软的很多项目自带DB都是使用GUID做主键的,显然,这样做是没有问题的.然而,SQL Server默认会将主键设置为聚集索引,使用GUID做聚集索引就有问题了.很多时候程序员容易接受SQL Server这一默认设置,但无序GUID做聚集索引显然是低效的. 那么,我们在项目中如何避免这一问题呢? 主要的思路还是两方面——方案一,选择合适的列作为聚集索引:方案二,使用有

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

场景: 产品表数据量较大想用Guid做表的主键,并在此字段上建立聚簇索引. 因为Guid是随机生成的,生成的值大小是不确定的,每次生成的数可能很大,也可能很小.这样会影响插入的效率 1.NEWSEQUENTIALID和newid()的区别 NEWSEQUENTIALID() 和 NEWID()都可以产生uniqueidentifier类型的,GUID.NEWID()产生的GUID是无序的,随机的. 而NEWSEQUENTIALID()是SQL SERVER2005新特性,NEWSEQUENTIA

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

一.目标:整理数据 有的人有强迫症,他们会为一系列数据的断档而抓狂. 一方面,Id为3这一行确实发生过一些事情,为什么这个查询不返回Id为3的这一行?这条记录数据丢失了吗?那个Column到底是什么?我要为这条数据的丢失负责吗? 二.反模式:填充角落 大多数人对于断档的第一反应就是想要填补其中的空缺.对此,可能有两种做法: 1.不按照顺序分配编号 你可能想要在插入新行时,通过遍历表,将找到的第一个未分配的主键编号分配给新行,来代替原来自动分配的伪主键机制.随着你不断地插入新行,断档就被填补起来了

Mysql性能优化GUID主键

由于系统中大量使用GUID作为主键,导致数据文件大,数据查询速度慢.意向转为Int主键.先查看下单表测试GUID和Int分别作为主键的结果. 测试条件如下: AMD E1-1200 APU 1.4G 2GRam 100Lan, 250GB HD Mysql 5.5,innodb_buffer_pool_size = 512M 测试表order_dish_history:60个字段,共5个索引,4个guid索引,1个timestamp+PK索引用于表分区,其它字段两表完全相同:表分为12个分区每个

创建有序的数据库GUID 主键

新项目的数据库使用GUID作为数据库主键. 主要是考虑以下方面: 1.后续可能有数据迁移 2.服务器和客户端间的Sync.Merge等操作,避免数据冲突 3.主键的业务无关性 4.初始数据的一致性(比如基础数据字典,测试数据等) 以下是使用GUID创建有序uniqueidentifier主键的脚本,记录以下 CREATE TABLE TestTable( Guid uniqueidentifier not null default(newsequentialid()),Id int not nu

int 和guid做主键的时候性能的区别

1.在经常需要做数据迁移的系统中,建议用Guid.并且在相应的外键字段,也就是用来做连接查询的字段添加非聚集索引,对于改善性能有极大的好处.where条件的字段也可以适当添加非聚集索引. 2.在使用Guid类型作为主键时,数据类型应为uniqueidentifier,并且一定要记得取消主键的"聚集索引" 3.对于不需要做迁移,或小型系统,用int做主键还是很方便的,并且在效率方面还是有一定提升的. 原文地址http://blog.csdn.net/fox123871/article/d

MyBatis 返回insert操作主键

    应用场景 在向数据库插入数据时,需要保留插入数据的id,以便进行后续的update操作或者将id存入其他表作为外键.但是,在默认情况下,insert操作返回的是一个int值,它并非表示主键id,而是表示当前SQL语句影响的行数. 接下来,我们依次从options注解和SQL两个方面,了解MyBatis如何在使用MySQL和Oracle做insert插入操作时将返回的id绑定到对象中. Mybatis的@Options注解 mybatis的@Options注解能够拿到对象自增的id的值,能

Linux(Centos)下jdbc连接oracle速度超慢的问题

最近在centos下写个java swing程序,发现在linux用jdbc连接oracle及其缓慢,还经常失败.但是同样的程序在windows下运行就连接的非常快.网上搜索了很长时间都和我这情况没关系,偶然看到了下面的文章: 原文地址http://www.xuebuyuan.com/200181.html 解决使用JDBC连接orcale速度慢的问题 2012年04月01日 ⁄ 综合 ⁄ 共 341字 ⁄ 字号 小 中 大 ⁄ 评论关闭 使用java 开发程序,选用ojdbc14.jar的驱动