2012-03-30 9 views
7

में तेज़ी से है, मैं एक ऐसे अनुप्रयोग को विकसित कर रहा हूं जो छवियों और संबंधित मेटाडेटा को संग्रहीत करता हो। NHHernate का उपयोग करके एक निश्चित क्वेरी करते समय मैं मुद्दों में भाग ले रहा हूं। क्वेरी निषिद्ध रूप से लंबी (मेरी मशीन पर 31 सेकंड की तरह कुछ ले रही है) ले रही है, हालांकि SQL सर्वर प्रबंधन स्टूडियो में निष्पादित होने पर वही क्वेरी केवल सेकंड का एक अंश लेती है।क्वेरी क्लाइंट ऐप में बहुत अधिक समय लेती है लेकिन SQL सर्वर प्रबंधन स्टूडियो

मैं कम है और एक छोटे से परीक्षण आवेदन करने के लिए समस्या extraced है:

संस्थाओं:

टैग, ईद की (स्ट्रिंग, टैग मान ही)

public class Tag 
{ 
    public virtual string Id { get; set; } 
} 

मिलकर छवि, जिसमें आईडी (int), नाम (स्ट्रिंग) और टैग शामिल हैं (कई से कई, टैग उदाहरण)

public class Image 
{ 
    private Iesi.Collections.Generic.ISet<Tag> tags = new HashedSet<Tag>(); 

    public virtual int Id { get; set; } 

    public virtual string Name { get; set; } 

    public virtual IEnumerable<Tag> Tags 
    { 
     get { return tags; } 
    } 

    public virtual void AddTag(Tag tag) 
    { 
     tags.Add(tag); 
    } 
} 
मैं "कोड से मानचित्रण" का उपयोग कर रहा

निम्नलिखित मैपिंग के साथ:

<hibernate-configuration xmlns="urn:nhibernate-configuration-2.2"> 
    <session-factory> 
     <property name="dialect">NHibernate.Dialect.MsSql2008Dialect</property> 
     <property name="connection.connection_string_name">PrimaryDatabase</property> 
     <property name="format_sql">true</property> 
    </session-factory> 
    </hibernate-configuration> 
    <connectionStrings> 
    <add name="PrimaryDatabase" providerName="System.Data.SqlClient" connectionString="Data Source=.\SQLEXPRESS;Initial Catalog=PerfTest;Integrated Security=True" /> 
    </connectionStrings> 

मैं निम्नलिखित हासिल करना चाहते हैं:

public class TagMapping : ClassMapping<Tag> 
{ 
    public TagMapping() 
    { 
     Id(x => x.Id, map => map.Generator(Generators.Assigned)); 
    } 
} 

public class ImageMapping : ClassMapping<Image> 
{ 
    public ImageMapping() 
    { 
     Id(x => x.Id, map => map.Generator(Generators.Native)); 
     Property(x => x.Name); 
     Set(x => x.Tags, 
      map => map.Access(Accessor.Field), 
      map => map.ManyToMany(m2m => { })); 
    } 
} 

NHibernate/डेटाबेस विन्यास इस तरह दिखता है क्वेरी: मुझे उन सभी छवियां दें जहां नाम में एक विशिष्ट स्ट्रिंग है या जहां किसी टैग में एक विशिष्ट स्ट्रिंग है। उत्तरार्द्ध को खोजने के लिए मैं एक सबक्वायरी का उपयोग करता हूं जो मुझे मेल खाने वाले टैग के साथ सभी छवियों के आईडी देता है। तो अंत में खोज मानदंड हैं: छवि में एक विशिष्ट स्ट्रिंग वाला नाम है या इसकी आईडी सबक्वायरी द्वारा लौटाई गई है।

var term = "abc"; 
var mode = MatchMode.Anywhere; 

var imagesWithMatchingTag = QueryOver.Of<Image>() 
    .JoinQueryOver<Tag>(x => x.Tags) 
    .WhereRestrictionOn(x => x.Id).IsLike(term, mode) 
    .Select(x => x.Id); 

var qry = session.QueryOver<Image>() 
    .Where(Restrictions.On<Image>(x => x.Name).IsLike(term, mode) || 
      Subqueries.WhereProperty<Image>(x => x.Id).In(imagesWithMatchingTag)) 
    .List(); 

परीक्षण डेटाबेस (DBMS: एसक्यूएल सर्वर 2008 एक्सप्रेस आर 2) मैं इस क्वेरी के लिए इस परीक्षण के लिए विशेष रूप से बनाया गया था और कुछ और शामिल नहीं है चलाने

यहाँ कोड है कि क्वेरी को निष्पादित करता है। मैं इसे यादृच्छिक डेटा से भरा है: 10.000 छवियों (तालिका छवि), छवियों और टैग (तालिका टैग), यानी के बीच 4.000 टैग (तालिका टैग) और मोटे तौर पर 200.000 संघों। प्रत्येक छवि में लगभग 20 संबंधित टैग होते हैं। डेटाबेस

करें SQL NHibernate का उपयोग करने का दावा करता है:

SELECT 
    this_.Id as Id1_0_, 
    this_.Name as Name1_0_ 
FROM 
    Image this_ 
WHERE 
    (
     this_.Name like @p0 
     or this_.Id in (
      SELECT 
       this_0_.Id as y0_ 
      FROM 
       Image this_0_ 
      inner join 
       Tags tags3_ 
        on this_0_.Id=tags3_.image_key 
      inner join 
       Tag tag1_ 
        on tags3_.elt=tag1_.Id 
      WHERE 
       tag1_.Id like @p1 
     ) 
    ); 
@p0 = '%abc%' [Type: String (4000)], @p1 = '%abc%' [Type: String (4000)] 

इस क्वेरी मैं बना रहा हूं दिया उचित लग रहा है।

यदि मैं NHBernate का उपयोग करके यह क्वेरी चलाता हूं तो क्वेरी में लगभग 30+ सेकंड (NHibernate.AdoNet.AbstractBatcher - ExecuteReader took 32964 ms) लेते हैं और 98 इकाइयां लौटाते हैं।

हालांकि, अगर मैं Sql सर्वर प्रबंधन स्टूडियो के अंदर सीधे एक बराबर क्वेरी निष्पादित करें:

DECLARE @p0 nvarchar(4000) 
DECLARE @p1 nvarchar(4000) 

SET @p0 = '%abc%' 
SET @p1 = '%abc%'  

SELECT 
    this_.Id as Id1_0_, 
    this_.Name as Name1_0_ 
FROM 
    Image this_ 
WHERE 
    (
     this_.Name like @p0 
     or this_.Id in (
      SELECT 
       this_0_.Id as y0_ 
      FROM 
       Image this_0_ 
      inner join 
       Tags tags3_ 
        on this_0_.Id=tags3_.image_key 
      inner join 
       Tag tag1_ 
        on tags3_.elt=tag1_.Id 
      WHERE 
       tag1_.Id like @p1 
     ) 
    ); 

क्वेरी ज्यादा एक सेकंड से कम समय लगता है (और रिटर्न 98 के परिणामों की भी)।

इसके अलावा प्रयोगों:

यदि मैं केवल नाम से या केवल टैग द्वारा, यानी खोज .:

var qry = session.QueryOver<Image>() 
    .Where(Subqueries.WhereProperty<Image>(x => x.Id).In(imagesWithMatchingTag)) 
    .List(); 

या

var qry = session.QueryOver<Image>() 
    .Where(Restrictions.On<Image>(x => x.Name).IsLike(term, mode)) 
    .List(); 

प्रश्नों तेजी से कर रहे हैं।

मैं की तरह उपयोग नहीं करते हैं, लेकिन मेरे सबक्वेरी में एक सटीक मिलान:

var imagesWithMatchingTag = QueryOver.Of<Image>() 
    .JoinQueryOver<Tag>(x => x.Tags) 
    .Where(x => x.Id == term) 
    .Select(x => x.Id); 

क्वेरी तेज, भी है।

नाम के लिए मिलान मोड को सटीक रूप से बदलना कुछ भी नहीं बदलता है।

जब मैं कार्यक्रम डिबग और जब क्वेरी कामयाब कॉल स्टैक के शीर्ष निष्पादित हो रहा है थामने की तरह दिखता है:

[Managed to Native Transition] 
System.Data.dll!SNINativeMethodWrapper.SNIReadSync(System.Runtime.InteropServices.SafeHandle pConn, ref System.IntPtr packet, int timeout) + 0x53 bytes 
System.Data.dll!System.Data.SqlClient.TdsParserStateObject.ReadSni(System.Data.Common.DbAsyncResult asyncResult, System.Data.SqlClient.TdsParserStateObject stateObj) + 0xa3 bytes 
System.Data.dll!System.Data.SqlClient.TdsParserStateObject.ReadNetworkPacket() + 0x24 bytes 
System.Data.dll!System.Data.SqlClient.TdsParserStateObject.ReadBuffer() + 0x1f bytes  
System.Data.dll!System.Data.SqlClient.TdsParserStateObject.ReadByte() + 0x46 bytes 
System.Data.dll!System.Data.SqlClient.TdsParser.Run(System.Data.SqlClient.RunBehavior runBehavior, System.Data.SqlClient.SqlCommand cmdHandler, System.Data.SqlClient.SqlDataReader dataStream, System.Data.SqlClient.BulkCopySimpleResultSet bulkCopyHandler, System.Data.SqlClient.TdsParserStateObject stateObj) + 0x67 bytes  
System.Data.dll!System.Data.SqlClient.SqlDataReader.ConsumeMetaData() + 0x22 bytes 
System.Data.dll!System.Data.SqlClient.SqlDataReader.MetaData.get() + 0x57 bytes 
System.Data.dll!System.Data.SqlClient.SqlCommand.FinishExecuteReader(System.Data.SqlClient.SqlDataReader ds, System.Data.SqlClient.RunBehavior runBehavior, string resetOptionsString) + 0xe1 bytes 
... 

तो, मेरे सवाल कर रहे हैं:

  • क्यों इतने क्वेरी करता है एनएचबीर्नेट द्वारा किए जाने पर भी लंबे समय तक एसक्यूएल का इस्तेमाल समान होता है?
  • मैं अंतर से कैसे छुटकारा पा सकता हूं? क्या ऐसी कोई सेटिंग है जो इस व्यवहार का कारण बन सकती है?

मुझे पता है कि सामान्य रूप से क्वेरी दुनिया में सबसे कुशल चीज नहीं है, लेकिन यहां मुझे क्या हड़ताली है NHHernate और मैन्युअल क्वेरीिंग का उपयोग करने के बीच अंतर है। यहां निश्चित रूप से कुछ अजीब चल रहा है।

लंबी पोस्ट के लिए खेद है, लेकिन मैं इस मुद्दे के बारे में जितना संभव हो उतना शामिल करना चाहता था। आपकी मदद के लिए बहुत पहले धन्यवाद!

अद्यतन 1: मैं अधिक महत्वपूर्ण मूल्य के बिना NHProf साथ आवेदन परीक्षण किया है: NHProf पता चलता है कि निष्पादित एसक्यूएल

SELECT this_.Id as Id1_0_, 
     this_.Name as Name1_0_ 
FROM Image this_ 
WHERE (this_.Name like '%abc%' /* @p0 */ 
     or this_.Id in (SELECT this_0_.Id as y0_ 
         FROM Image this_0_ 
           inner join Tags tags3_ 
            on this_0_.Id = tags3_.image_key 
           inner join Tag tag1_ 
            on tags3_.elt = tag1_.Id 
         WHERE tag1_.Id like '%abc%' /* @p1 */)) 

है कौन सा है मैं वास्तव में क्या पहले पोस्ट (क्योंकि है कि क्या NHibernate लिखा है पहले स्थान पर इसके लॉग के लिए)।

यहाँ NHProf Screenshot of NHProf

का एक स्क्रीनशॉट चेतावनी समझा जा सकता है, लेकिन व्यवहार की व्याख्या नहीं है। यह वास्तव में मुख्य क्वेरी तेजी से पड़ता है जबकि

var imagesWithMatchingTag = QueryOver.Of<Image>() 
    .JoinQueryOver<Tag>(x => x.Tags) 
    .WhereRestrictionOn(x => x.Id).IsLike(term, mode) 
    .Select(x => x.Id); 

var ids = imagesWithMatchingTag.GetExecutableQueryOver(session).List<int>().ToArray(); 

var qry = session.QueryOver<Image>() 
    .Where(
      Restrictions.On<Image>(x => x.Name).IsLike(term, mode) || 
      Restrictions.On<Image>(x => x.Id).IsIn(ids)) 
    .List(); 

:

अद्यतन 2 @surfen उप क्वेरी के परिणामों पहले डीबी से बाहर निकलने और मुख्य क्वेरी में उन्हें वापस रहना sugested फिर, मैं इस दृष्टिकोण को नहीं लेना चाहूंगा क्योंकि यह असली दुनिया के अनुप्रयोग में इच्छित उपयोग के साथ अच्छी तरह फिट नहीं है। यह दिलचस्प है कि यह बहुत तेज है, यद्यपि। मैं अपेक्षा करता हूं कि सबक्वायरी दृष्टिकोण समान रूप से तेज़ हो जाए क्योंकि यह बाहरी क्वेरी पर निर्भर नहीं है।

अद्यतन 3 यह एनएचबेर्नेट से संबंधित प्रतीत नहीं होता है।

var cmdText = @"SELECT this_.Id as Id1_0_, 
         this_.Name as Name1_0_ 
       FROM Image this_ 
       WHERE (this_.Name like @p0 
          or this_.Id in 
         (SELECT this_0_.Id as y0_ 
         FROM Image this_0_ 
          inner join Tags tags3_ 
           on this_0_.Id = tags3_.image_key 
          inner join Tag tag1_ 
           on tags3_.elt = tag1_.Id 
         WHERE tag1_.Id like @p1));"; 

using (var con = new SqlConnection(ConfigurationManager.ConnectionStrings["PrimaryDatabase"].ConnectionString)) 
{ 
    con.Open(); 
    using (var txn = con.BeginTransaction()) 
    { 
     using (var cmd = new SqlCommand(cmdText, con, txn)) 
     { 
      cmd.CommandTimeout = 120; 
      cmd.Parameters.AddWithValue("p0", "%abc%"); 
      cmd.Parameters.AddWithValue("p1", "%abc%"); 

      using (var reader = cmd.ExecuteReader()) 
      { 
       while (reader.Read()) 
       { 
        Console.WriteLine("Match"); 
       } 
      } 

     } 
     txn.Commit(); 
    } 
} 

अद्यतन 4

क्वेरी-योजनाओं (ज़ूम करने के लिए क्लिक करें)::

धीरे क्वेरी Slow plan

अगर मैं सामान्य ADO.NET वस्तुओं का उपयोग कर क्वेरी चलाने मैं समान व्यवहार प्राप्त

फास्ट क्वेरी Fast plan

निश्चित रूप से योजना में एक अंतर है।

अद्यतन 5

यह वास्तव में लगता है के रूप में है कि Sql सर्वर सहसंबद्ध किया जा रहा मैं कुछ अलग करने की कोशिश की के रूप में व्यवहार करता है सबक्वेरी: मैं अपने आप में एक सबक्वेरी के नाम से संबंधित कसौटी ले जाया गया:

var term = "abc"; 
var mode = MatchMode.Anywhere; 

var imagesWithMatchingTag = QueryOver.Of<Image>() 
    .JoinQueryOver<Tag>(x => x.Tags) 
    .WhereRestrictionOn(x => x.Id).IsLike(term, mode) 
    .Select(x => x.Id); 

var imagesWithMatchingName = QueryOver.Of<Image>() 
    .WhereRestrictionOn(x => x.Name).IsLike(term, mode) 
    .Select(x => x.Id); 

var qry = session.QueryOver<Image>() 
    .Where(
     Subqueries.WhereProperty<Image>(x => x.Id).In(imagesWithMatchingName) ||   
     Subqueries.WhereProperty<Image>(x => x.Id).In(imagesWithMatchingTag) 
    ).List(); 

जेनरेट किए गए एसक्यूएल:

SELECT 
    this_.Id as Id1_0_, 
    this_.Name as Name1_0_ 
FROM 
    Image this_ 
WHERE 
    (
     this_.Id in (
      SELECT 
       this_0_.Id as y0_ 
      FROM 
       Image this_0_ 
      inner join 
       Tags tags3_ 
        on this_0_.Id=tags3_.image_key 
      inner join 
       Tag tag1_ 
        on tags3_.elt=tag1_.Id 
      WHERE 
       tag1_.Id like @p0 
     ) 
     or this_.Id in (
      SELECT 
       this_0_.Id as y0_ 
      FROM 
       Image this_0_ 
      WHERE 
       this_0_.Name like @p1 
     ) 
    ); 
@p0 = '%abc%' [Type: String (4000)], @p1 = '%abc%' [Type: String (4000)] 

यह और सहसंबंध को तोड़ने के लिए लगता है एक परिणाम क्वेरी beco के रूप में मेस "फास्ट" फिर से ("तेज" के रूप में "पल के लिए स्वीकार्य")। क्वेरी समय 30+ सेकंड से ~ 170ms तक चला गया। अभी भी एक हल्का सवाल नहीं है, लेकिन कम से कम मुझे यहां से जारी रखने की अनुमति देगा। मुझे पता है कि "like '%foo%'" कभी भी तेज नहीं होगा। यदि यह सबसे बुरी स्थिति में आता है तो भी मैं एक विशेष खोज सर्वर (लुसेन, सोलर) या वास्तविक पूर्ण पाठ खोज में जा सकता हूं।

अद्यतन 6 मैं क्वेरी बिल्कुल सबक्वेरी का उपयोग नहीं करने के लिए फिर से लिखने में सक्षम था:

var qry = session.QueryOver(() => img) 
    .Left.JoinQueryOver(x => x.Tags,() => tag) 
    .Where(
     Restrictions.Like(Projections.Property(() => img.Name), term, mode) || 
     Restrictions.Like(Projections.Property(() => tag.Id), term, mode)) 
    .TransformUsing(Transformers.DistinctRootEntity) 
    .List(); 

एसक्यूएल:

SELECT 
    this_.Id as Id1_1_, 
    this_.Name as Name1_1_, 
    tags3_.image_key as image1_3_, 
    tag1_.Id as elt3_, 
    tag1_.Id as Id0_0_ 
FROM 
    Image this_ 
left outer join 
    Tags tags3_ 
     on this_.Id=tags3_.image_key 
left outer join 
    Tag tag1_ 
     on tags3_.elt=tag1_.Id 
WHERE 
    (
     this_.Name like @p0 
     or tag1_.Id like @p1 
    ); 
@p0 = '%abc%' [Type: String (4000)], @p1 = '%abc%' [Type: String (4000)] 

हालांकि, क्वेरी अब संस्करण की तुलना में थोड़ा खराब प्रदर्शन करने वाला subqueries के साथ। मैं आगे की जांच करूंगा।

var qry = session.QueryOver<Image>() 
.Where(Restrictions.On<Image>(x => x.Name).IsLike(term, mode) || 
     Subqueries.WhereProperty<Image>(x => x.Id).In(imagesWithMatchingTag)) 
.List(); 

आप केवल पहली क्वेरी के लिए SQL प्रदान की:

+0

संभावित डुप्लिकेट: http://dba.stackexchange.com/q/9167/724 –

+0

@RowlandShaw एक डुप्लिकेट नहीं है क्योंकि यह समस्या सी # पक्ष में हो सकती है - जो इसे डीबीए – Mark

+1

@AndreLoker के लिए भी दायरे से बाहर कर देती है - क्या आपने यह देखने के लिए एक प्रोफाइलर चलाया है कि समय कहाँ लिया जाता है – Mark

उत्तर

2

मेरे शर्त है कि यह दूसरी क्वेरी धीमी है कि है। दूसरे के बारे में क्या? क्या आपने एसक्यूएल प्रबंधन स्टूडियो के तहत इसका परीक्षण किया था? @JoachimIsaksson के रूप में SQL सर्वर प्रोफाइलर का उपयोग करने के लिए यह पता लगाने के लिए सुझाव मिलता है कि एनएचबीर्नेट सर्वर से किनारे निष्पादित करता है।

ऐसा लगता है कि आप 97 image वस्तुओं को स्मृति में लोड कर रहे हैं। उनमें से प्रत्येक कितना बड़ा है?

संपादित

एक और शर्त है कि अपने पहले क्वेरी निष्पादित करता है दूसरी क्वेरी के लिए विज्ञापन भीतरी क्वेरी है। मेमोरी में टैग लोड करने के लिए पहली क्वेरी पर .ist() करने का प्रयास करें।

संपादित 2

क्वेरी से योजना बना रही है आपकी क्वेरी की तरह एक Correlated subquery के रूप में कहा जा रहा है यह वास्तव में लग रहा है। आपने कहा है कि इन प्रश्नों तेजी से कर रहे हैं:

var qry = session.QueryOver<Image>() 
.Where(Subqueries.WhereProperty<Image>(x => x.Id).In(imagesWithMatchingTag)) 
.List(); 

या

var qry = session.QueryOver<Image>() 
.Where(Restrictions.On<Image>(x => x.Name).IsLike(term, mode)) 
.List(); 

बस उन्हें संघ और आप उन दोनों को अलग से चल रहा है के रूप में एक ही परिणाम प्राप्त करना चाहिए। यह भी सुनिश्चित करें कि सभी शामिल कॉलम में इंडेक्स हैं।

यह आईएस इन (क्वेरी) के साथ पकड़ है - आप यह सुनिश्चित नहीं कर सकते कि डेटाबेस इसे कैसे निष्पादित करता है (जब तक कि आप इसे किसी निश्चित योजना का उपयोग करने के लिए मजबूर नहीं करते)। शायद आप बदल सकते हैं।() इसे JoinQueryOver() में किसी भी तरह से?

+0

केवल एक प्रश्न है। 'imagesWithMatchingTag' एक सबक्वायरी है। मैंने जो एसक्यूएल पोस्ट किया है वह सब कुछ है जिसे निष्पादित किया गया है। यदि आप मेरे प्रश्न को देखते हैं, तो आप देखेंगे कि 'छवि' ऑब्जेक्ट्स छोटे हैं: दो आदिम फ़ील्ड और एक हैश सेट 20 से अधिक प्रविष्टियों में है। –

+0

मैं देखता हूं। मेरे संपादन देखें। यह देखने के लिए कि क्या यह मदद करता है, पहली क्वेरी पर .ist() करने का प्रयास करें। – surfen

+0

क्या वह पहली क्वेरी को एसक्यूएल से बाहर नहीं खींच पाएगा और इसके बजाय कोड को आईडी से और आईडी की सूची पास कर देगा? क्या वह बदतर नहीं होगा? – Rup