提升数据库查询的性能

<p></p>
<p class="MsoNormal" align="left"><span>数据库查询性能的提升也是涉及到开发中的各个阶段,在开发中选用正确的查询方法无疑是最基础也最简单的。<span lang="EN-US"> </span></span></p>
<p class="MsoNormal" align="left"><span lang="EN-US">1 </span><span>、<span lang="EN-US">SQL</span>语句的优化<span lang="EN-US"> </span></span></p>
<p class="MsoNormal" align="left"><span>使用正确的<span lang="EN-US">SQL</span>语句可以在很大程度上提高系统的查询性能。获得同样数据而采用不同方式的<span lang="EN-US">SQL</span>语句在性能上的差距可能是十分巨大的。<span lang="EN-US"> </span></span></p>
<p class="MsoNormal" align="left"><span>由于<span lang="EN-US">Hibernate</span>是对<span lang="EN-US">JDBC</span>的封装,<span lang="EN-US">SQL</span>语句的产生都是动态由<span lang="EN-US">Hibernate</span>自动完成的。<span lang="EN-US">Hibernate</span>产生<span lang="EN-US">SQL</span>语句的方式有两种:一种是通过开发人员编写的<span lang="EN-US">HQL</span>语句来生成,另一种是依据开发人员对关联对象的访问来自动生成相应的<span lang="EN-US">SQL</span>语句。<span lang="EN-US"> </span></span></p>
<p class="MsoNormal" align="left"><span>至于使用什么样的<span lang="EN-US">SQL</span>语句可以获得更好的性能要依据数据库的结构以及所要获取数据的具体情况来进行处理。在确定了所要执行的<span lang="EN-US">SQL</span>语句后,可以通过以下三个方面来影响<span lang="EN-US">Hibernate</span>所生成的<span lang="EN-US">SQL</span>语句:<span lang="EN-US"> </span></span></p>
<p class="MsoNormal" align="left"><span lang="EN-US">◆HQL</span><span>语句的书写方法。<span lang="EN-US"> </span></span></p>
<p class="MsoNormal" align="left"><span lang="EN-US">◆</span><span>查询时所使用的查询方法。<span lang="EN-US"> </span></span></p>
<p class="MsoNormal" align="left"><span lang="EN-US">◆</span><span>对象关联时所使用的抓取策略。<span lang="EN-US"> </span></span></p>
<p class="MsoNormal" align="left"><span lang="EN-US">2 </span><span>、使用正确的查询方法<span lang="EN-US"> </span></span></p>
<p class="MsoNormal" align="left"><span>在前面已经介绍过,执行数据查询功能的基本方法有两种:一种是得到单个持久化对象的<span lang="EN-US">get()</span>方法和<span lang="EN-US">load()</span>方法,另一种是<span lang="EN-US">Query</span>对象的<span lang="EN-US">list()</span>方法和<span lang="EN-US">iterator()</span>方法。在开发中应该依据不同的情况选用正确的方法。<span lang="EN-US"> </span></span></p>
<p class="MsoNormal" align="left"><span lang="EN-US">get()</span><span>方法和<span lang="EN-US">load()</span>方法的区别在于对二级缓存的使用上。<span lang="EN-US">load()</span>方法会使用二级缓存,而<span lang="EN-US">get()</span>方法在一级缓存没有找到的情况下会直接查询数据库,不会去二级缓存中查找。在使用中,对使用了二级缓存的对象进行查询时最好使用<span lang="EN-US">load()</span>方法,以充分利用二级缓存来提高检索的效率。<span lang="EN-US"> </span></span></p>
<p class="MsoNormal" align="left"><span lang="EN-US">list()</span><span>方法和<span lang="EN-US">iterator()</span>方法之间的区别可以从以下几个方面来进行比较。<span lang="EN-US"> </span></span></p>
<p class="MsoNormal" align="left"><span lang="EN-US">◆</span><span>执行的查询不同<span lang="EN-US"> </span></span></p>
<p class="MsoNormal" align="left"><span lang="EN-US">list()</span><span>方法在执行时,是直接运行查询结果所需要的查询语句,而<span lang="EN-US">iterator()</span>方法则是先执行得到对象<span lang="EN-US">ID</span>的查询,然后再根据每个<span lang="EN-US">ID</span>值去取得所要查询的对象。因此,对于<span lang="EN-US">list()</span>方式的查询通常只会执行一个<span lang="EN-US">SQL</span>语句,而对于<span lang="EN-US">iterator()</span>方法的查询则可能需要执行<span lang="EN-US">N+1</span>条<span lang="EN-US">SQL</span>语句<span lang="EN-US">(N</span>为结果集中的记录数<span lang="EN-US">)</span>。<span lang="EN-US"> </span></span></p>
<p class="MsoNormal" align="left"><span lang="EN-US">iterator()</span><span>方法只是可能执行<span lang="EN-US">N+1</span>条数据,具体执行<span lang="EN-US">SQL</span>语句的数量取决于缓存的情况以及对结果集的访问情况。<span lang="EN-US"> </span></span></p>
<p class="MsoNormal" align="left"><span lang="EN-US">◆</span><span>缓存的使用<span lang="EN-US"> </span></span></p>
<p class="MsoNormal" align="left"><span lang="EN-US">list()</span><span>方法只能使用二级缓存中的查询缓存,而无法使用二级缓存对单个对象的缓存<span lang="EN-US">(</span>但是会把查询出的对象放入二级缓存中<span lang="EN-US">)</span>。所以,除非重复执行相同的查询操作,否则无法利用缓存的机制来提高查询的效率。<span lang="EN-US"> </span></span></p>
<p class="MsoNormal" align="left"><span lang="EN-US">iterator()</span><span>方法则可以充分利用二级缓存,在根据<span lang="EN-US">ID</span>检索对象的时候会首先到缓存中查找,只有在找不到的情况下才会执行相应的查询语句。所以,缓存中对象的存在与否会影响到<span lang="EN-US">SQL</span>语句的执行数量。<span lang="EN-US"> </span></span></p>
<p class="MsoNormal" align="left"><span lang="EN-US">◆</span><span>对于结果集的处理方法不同<span lang="EN-US"> </span></span></p>
<p class="MsoNormal" align="left"><span lang="EN-US">list()</span><span>方法会一次获得所有的结果集对象,而且它会依据查询的结果初始化所有的结果集对象。这在结果集非常大的时候必然会占据非常多的内存,甚至会造成内存溢出情况的发生。<span lang="EN-US"> </span></span></p>
<p class="MsoNormal" align="left"><span lang="EN-US">iterator()</span><span>方法在执行时不会一次初始化所有的对象,而是根据对结果集的访问情况来初始化对象。因此在访问中可以控制缓存中对象的数量,以避免占用过多缓存,导致内存溢出情况的发生。使用<span lang="EN-US">iterator()</span>方法的另外一个好处是,如果只需要结果集中的部分记录,那么没有被用到的结果对象根本不会被初始化。所以,对结果集的访问情况也是调用<span lang="EN-US">iterator()</span>方法时执行数据库<span lang="EN-US">SQL</span>语句多少的一个因素。<span lang="EN-US"> </span></span></p>
<p class="MsoNormal" align="left"><span>所以,在使用<span lang="EN-US">Query</span>对象执行数据查询时应该从以上几个方面去考虑使用何种方法来执行数据库的查询操作。<span lang="EN-US">
</span></span></p>
<p class="MsoNormal" align="left"><strong><span lang="EN-US">Hibernate</span></strong><strong><span>优化方法四:使用正确的抓取策略</span></strong><span lang="EN-US"> </span></p>
<p class="MsoNormal" align="left"><span>所谓抓取策略<span lang="EN-US">(fetching strategy)</span>是指当应用程序需要利用关联关系进行对象获取的时候,<span lang="EN-US">Hibernate</span>获取关联对象的策略。抓取策略可以在<span lang="EN-US">O/R</span>映射的元数据中声明,也可以在特定的<span lang="EN-US">HQL</span>或条件查询中声明。<span lang="EN-US"> </span></span></p>
<p class="MsoNormal" align="left"><span lang="EN-US">Hibernate 3</span><span>定义了以下几种抓取策略。<span lang="EN-US"> </span></span></p>
<p class="MsoNormal" align="left"><span>连接抓取<span lang="EN-US">(Join fetching) </span></span></p>
<p class="MsoNormal" align="left"><span>连接抓取是指<span lang="EN-US">Hibernate</span>在获得关联对象时会在<span lang="EN-US">SELECT</span>语句中使用外连接的方式来获得关联对象。<span lang="EN-US"> </span></span></p>
<p class="MsoNormal" align="left"><span>查询抓取<span lang="EN-US">(Select fetching) </span></span></p>
<p class="MsoNormal" align="left"><span>查询抓取是指<span lang="EN-US">Hibernate</span>通过另外一条<span lang="EN-US">SELECT</span>语句来抓取当前对象的关联对象的方式。这也是通过外键的方式来执行数据库的查询。与连接抓取的区别在于,通常情况下这个<span lang="EN-US">SELECT</span>语句不是立即执行的,而是在访问到关联对象的时候才会执行。<span lang="EN-US"> </span></span></p>
<p class="MsoNormal" align="left"><span>子查询抓取<span lang="EN-US">(Subselect fetching) </span></span></p>
<p class="MsoNormal" align="left"><span>子查询抓取也是指<span lang="EN-US">Hibernate</span>通过另外一条<span lang="EN-US">SELECT</span>语句来抓取当前对象的关联对象的方式。与查询抓取的区别在于它所采用的<span lang="EN-US">SELECT</span>语句的方式为子查询,而不是通过外连接。<span lang="EN-US"> </span></span></p>
<p class="MsoNormal" align="left"><span>批量抓取<span lang="EN-US">(Batch fetching) </span></span></p>
<p class="MsoNormal" align="left"><span>批量抓取是对查询抓取的优化,它会依据主键或者外键的列表来通过单条<span lang="EN-US">SELECT</span>语句实现管理对象的批量抓取。<span lang="EN-US"> </span></span></p>
<p class="MsoNormal" align="left"><span>以上介绍的是<span lang="EN-US">Hibernate 3</span>所提供的抓取策略,也就是抓取关联对象的手段。为了提升系统的性能,在抓取关联对象的时机上,还有以下一些选择。<span lang="EN-US"> <br></span>立即抓取<span lang="EN-US">(Immediate fetching) </span></span></p>
<p class="MsoNormal" align="left"><span>立即抓取是指宿主对象被加载时,它所关联的对象也会被立即加载。<span lang="EN-US"> </span></span></p>
<p class="MsoNormal" align="left"><span>延迟集合抓取<span lang="EN-US">(Lazy collection fetching) </span></span></p>
<p class="MsoNormal" align="left"><span>延迟集合抓取是指在加载宿主对象时,并不立即加载它所关联的对象,而是到应用程序访问关联对象的时候才抓取关联对象。这是集合关联对象的默认行为。<span lang="EN-US"> </span></span></p>
<p class="MsoNormal" align="left"><span>延迟代理抓取<span lang="EN-US">(Lazy proxy fetching) </span></span></p>
<p class="MsoNormal" align="left"><span>延迟代理抓取是指在返回单值关联对象的情况下,并不在对其进行<span lang="EN-US">get</span>操作时抓取,而是直到调用其某个方法的时候才会抓取这个对象。<span lang="EN-US"> </span></span></p>
<p class="MsoNormal" align="left"><span>延迟属性加载<span lang="EN-US">(Lazy attribute fetching) </span></span></p>
<p class="MsoNormal" align="left"><span>延迟属性加载是指在关联对象被访问的时候才进行关联对象的抓取。<span lang="EN-US"> </span></span></p>
<p class="MsoNormal" align="left"><span>介绍了<span lang="EN-US">Hibernate</span>所提供的关联对象的抓取方法和抓取时机,这两个方面的因素都会影响<span lang="EN-US">Hibernate</span>的抓取行为,最重要的是要清楚这两方面的影响是不同的,不要将这两个因素混淆,在开发中要结合实际情况选用正确的抓取策略和合适的抓取时机。<span lang="EN-US"> </span></span></p>
<p class="MsoNormal" align="left"><span lang="EN-US">◆</span><span>抓取时机的选择<span lang="EN-US"> </span></span></p>
<p class="MsoNormal" align="left"><span>在<span lang="EN-US">Hibernate 3</span>中,对于集合类型的关联在默认情况下会使用延迟集合加载的抓取时机,而对于返回单值类型的关联在默认情况下会使用延迟代理抓取的抓取时机。<span lang="EN-US"> </span></span></p>
<p class="MsoNormal" align="left"><span>对于立即抓取在开发中很少被用到,因为这很可能会造成不必要的数据库操作,从而影响系统的性能。当宿主对象和关联对象总是被同时访问的时候才有可能会用到这种抓取时机。另外,使用立即连接抓取可以通过外连接来减少查询<span lang="EN-US">SQL</span>语句的数量,所以,也会在某些特殊的情况下使用。<span lang="EN-US"> </span></span></p>
<p class="MsoNormal" align="left"><span>然而,延迟加载又会面临另外一个问题,如果在<span lang="EN-US">Session</span>关闭前关联对象没有被实例化,那么在访问关联对象的时候就会抛出异常。处理的方法就是在事务提交之前就完成对关联对象的访问。<span lang="EN-US"> </span></span></p>
<p class="MsoNormal" align="left"><span>所以,在通常情况下都会使用延迟的方式来抓取关联的对象。因为每个立即抓取都会导致关联对象的立即实例化,太多的立即抓取关联会导致大量的对象被实例化,从而占用过多的内存资源。<span lang="EN-US"> </span></span></p>
<p class="MsoNormal" align="left"><span lang="EN-US">◆</span><span>抓取策略的选取<span lang="EN-US"> </span></span></p>
<p class="MsoNormal" align="left"><span>对于抓取策略的选取将影响到抓取关联对象的方式,也就是抓取关联对象时所执行的<span lang="EN-US">SQL</span>语句。这就要根据实际的业务需求、数据的数量以及数据库的结构来进行选择了。<span lang="EN-US"> </span></span></p>
<p class="MsoNormal" align="left"><span>在这里需要注意的是,通常情况下都会在执行查询的时候针对每个查询来指定对其合适的抓取策略。指定抓取策略的方法如下所示:<span lang="EN-US"> </span></span></p>
<p class="MsoNormal" align="left"><span lang="EN-US">User user = (User) session.createCriteria(User.class) </span></p>
<p class="MsoNormal" align="left"><span lang="EN-US">.setFetchMode("permissions", FetchMode.JOIN) </span></p>
<p class="MsoNormal" align="left"><span lang="EN-US">.add( Restrictions.idEq(userId) ) </span></p>
<p class="MsoNormal" align="left"><span lang="EN-US">.uniqueResult(); </span></p>
<p class="MsoNormal" align="left"><strong><span lang="EN-US">Hibernate</span></strong><strong><span>优化方法五:查询性能提升小结</span></strong><span> </span></p>
<p class="MsoNormal" align="left"><span>在本小节中介绍了查询性能提升的方法,关键是如何通过优化<span lang="EN-US">SQL</span>语句来提升系统的查询性能。查询方法和抓取策略的影响也是通过执行查询方式和<span lang="EN-US">SQL</span>语句的多少来改变系统的性能的。这些都属于开发人员所应该掌握的基本技能,避免由于开发不当而导致系统性能的低下。<span lang="EN-US"> </span></span></p>
<p class="MsoNormal" align="left"><span>在性能调整中,除了前面介绍的执行<span lang="EN-US">SQL</span>语句的因素外,对于缓存的使用也会影响系统的性能。通常来说,缓存的使用会增加系统查询的性能,而降低系统增加、修改和删除操作的性能<span lang="EN-US">(</span>因为要进行缓存的同步处理<span lang="EN-US">)</span>。所以,开发人员应该能够正确地使用有效的缓存来提高数据查询的性能,而要避免滥用缓存而导致的系统性能变低。在采用缓存的时候也应该注意调整自己的检索策略和查询方法,这三者配合起来才可以达到最优的性能。<span lang="EN-US"> </span></span></p>
<p class="MsoNormal" align="left"><span>另外,事务的使用策略也会影响到系统的性能。选取正确的事务隔离级别以及使用。</span></p>
<p></p>

你可能感兴趣的:(数据库)