程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> C# >> C#入門知識 >> Guid作主鍵速度超慢的背後

Guid作主鍵速度超慢的背後

編輯:C#入門知識

 最近遇到了一個讓人抓狂的性能問題。生產環境裡有一張表的數據量目前達到了 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 行。


 

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved